最近在做一个3D图片采集与展示。

主要功能为:自定义Camera(google 已经摈弃了Camera, 推荐使用Camera2,后续篇幅,我将会用Camera2取代Camera),围绕一个物体360度录制一个视频,然后在该视频抽取一定数量的帧,保存为图片存放。最后在一个Activity页面展示第一张图片,通过滑动或点击切换下一张图片,从而形成用图片展示的3D效果。该项目主要的目的是采集3D图片素材,然后上传到服务器处理,最终在用户客户端或网页端展示是通过OpenGL ES处理而来。

技术要点:

1.在SurfaceView 展示Camera的时候,如果不按照camera支持的尺寸比例,那么预览会出现拉伸。

mCamera.getParameters().getSupportedPreviewSizes();
mCamera.getParameters().getSupportedPictureSizes();

可以通过debug查看该摄像头支持哪些预览框大小、图片大小。如果要做适配,需要通过轮询获取自己所需要范围大小。通常情况下,是支持全屏大小,也就是该手机的像素尺寸。
如果你想把预览框设置成正方形,原则上是不行的(本人目前没有找到相应的方法,求大牛指示),那我们可以先用全屏的摄像框尺寸,然后通过在层叠隐藏部分区域形成正方形,
再获取图像时根据隐藏的区域做相应的截图,就能得到你想要的任何效果了。(如上图示例)

在预览中也有些小问题,比如说Camera默认是横向取景,你需要 mCamera.setDisplayOrientation(90); 同时图片保存的时候需要再旋转90度,才能达到预览的效果。

2.在录像时,也会遇到一些问题图片会有拉伸,跟预览的时候不一致。这时,应该获取设备所支持的参数。

CamcorderProfile mProfile = null;
if(CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_1080P)) {
mProfile = CamcorderProfile.get(CamcorderProfile.QUALITY_1080P);

}else if(CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_720P)){
mProfile = CamcorderProfile.get(CamcorderProfile.QUALITY_720P);
}else {
mProfile = CamcorderProfile.get(CamcorderProfile.QUALITY_480P);
}
主要还是要根据自己的实际情况去获取,然后再设置参数:

mMediaRecorder.setProfile(mProfile);//不知道为什么,直接这么设置,不起作用,要下面set 才行

//mMediaRecorder.setProfile(mProfile);//不知道为什么,直接这么设置,不起作用,要下面set 才行
mMediaRecorder.setOutputFormat(mProfile.fileFormat);// 视频输出格式
mMediaRecorder.setVideoFrameRate(mProfile.fileFormat);// 设置录制的视频帧率
mMediaRecorder.setVideoSize(mHeight, mWidth);;// 设置分辨率:
mMediaRecorder.setVideoEncodingBitRate(mProfile.videoBitRate);// 设置帧频率,然后就清晰了
mMediaRecorder.setVideoEncoder(mProfile.videoCodec);// 视频录制格式

3. 从视频抽帧, 每一帧是bitmap形式返回。从Java接口提供的抽帧方法,

MediaMetadataRetriever 类
getFrameAtTime(long timeUs)
通过时间去抽帧,而每一帧的时间也未必相隔是均匀的,这导致抽出来的帧会有重复,这需要再深入研究(后续再研究)。
具体方法为:

/**
* 获取视频关键帧
*/
public void getFrameFromVideo(String filePath, String dirName){ mFile = new File(filePath);
retriever = new MediaMetadataRetriever();
retriever.setDataSource(filePath);
fileLength = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION); long lengthLong = Long.parseLong(fileLength) *1000/ (Constants.Num_Frame - 1);
String prefixName = Utils.randomCapital(5);//图片名前缀
int k = 0;
for (long i = 0; i< Long.parseLong(fileLength)*1000+lengthLong; i=i+lengthLong){
k++;
Bitmap bitmap = retriever.getFrameAtTime(i);
if(bitmap != null){
Matrix matrix = new Matrix();
matrix.reset();
matrix.setRotate(90);//图片默认是横盘的,转90度变竖屏
if(bitmap.getWidth() >bitmap.getHeight()){
bitmap = Bitmap.createBitmap(bitmap,(bitmap.getWidth() - bitmap.getHeight())/2,0, bitmap.getHeight(), bitmap.getHeight(),matrix, true);
}else{
bitmap = Bitmap.createBitmap(bitmap,0,(bitmap.getHeight()-bitmap.getWidth())/2, bitmap.getWidth(), bitmap.getWidth(),matrix, true);
}
BitmapUtils.saveBitmap(bitmap, dirName, prefixName + k + ".jpg");
}
if(k < 50){
sendMessageForProgress(dirName,k,true);
}else {
sendMessageForProgress(dirName,k,false);
}
}
//删除视频文件
mFile.delete();
}

  

最后呈现一下,预览与录制视频的代码吧。RecordVideoActivity

public class RecordVideoActivity extends AppCompatActivity implements View.OnClickListener
, SurfaceHolder.Callback, MediaRecorder.OnErrorListener{ private ProgressBar mProgressbar;
private SurfaceView mSurfaceView;
private MediaRecorder mMediaRecorder;// 录制视频的类
private SurfaceHolder mSurfaceHolder;
private Camera mCamera;
private Timer mTimer;// 计时器
private boolean isOpenCamera = true;// 是否一开始就打开摄像头
private final static int mRecordMaxTime = 20;// 一次拍摄最长时间
private OnRecordFinishListener mOnRecordFinishListener;// 录制完成回调接口
private int mTimeCount;// 时间计数
private File mVecordFile = null;// 文件
private int mWidth = 0;// 视频分辨率宽度
private int mHeight = 0;// 视频分辨率高度
private boolean isStarting = false; @Override protected void onCreate(Bundle savedInstanceState) {
requestWindowFeature(Window.FEATURE_NO_TITLE);// 去掉标题栏
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);// 设置全屏
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_record_video);
initView();
} private void initView() {
mProgressbar = (ProgressBar) findViewById(R.id.progressBar);
mProgressbar.setMax(mRecordMaxTime);
findViewById(R.id.btn_start).setOnClickListener(this);
mSurfaceView = (SurfaceView)findViewById(R.id.surfaceView);
mSurfaceHolder = mSurfaceView.getHolder();// 取得holder
mSurfaceHolder.addCallback(this); // holder加入回调接口
mSurfaceHolder.setKeepScreenOn(true);
} /**
* 初始化摄像头
*/
private void initCamera(int width, int height) {
if (mCamera != null) {
freeCameraResource();
}
try {
mCamera = Camera.open();
if (mCamera == null)
return; mCamera.setDisplayOrientation(90);//摄像头默认是横向,需要调整角度变成竖直
mCamera.setPreviewDisplay(mSurfaceHolder);
Camera.Parameters parameters = mCamera.getParameters();// 获得相机参数
mWidth = width;
mHeight = height; parameters.setPreviewSize(height, width); // 设置预览图像大小
parameters.set("orientation", "portrait");
List<String> focusModes = parameters.getSupportedFocusModes();
if (focusModes.contains("continuous-video")) {
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
}
mCamera.getParameters().getSupportedPreviewSizes();
mCamera.getParameters().getSupportedPictureSizes(); mCamera.setParameters(parameters);// 设置相机参数
mCamera.startPreview();// 开始预览
mCamera.unlock();//解锁,赋予录像权限 } catch (Exception e) {
e.printStackTrace();
freeCameraResource();
}
} @Override
protected void onDestroy() {
super.onDestroy();
stop();
} /**
* 释放摄像头资源
*/
private void freeCameraResource() {
if (mCamera != null) {
mCamera.setPreviewCallback(null);
mCamera.stopPreview();
mCamera.lock();
mCamera.release();
mCamera = null;
}
} /**
* 录制前,初始化
*/
private void initRecord() {
try {
mMediaRecorder = new MediaRecorder();
mMediaRecorder.reset();
if(mCamera != null)
mMediaRecorder.setCamera(mCamera);
mMediaRecorder.setOnErrorListener(this);
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);// 视频源 CamcorderProfile mProfile = null;
if(CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_1080P)) {
mProfile = CamcorderProfile.get(CamcorderProfile.QUALITY_1080P); }else if(CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_720P)){
mProfile = CamcorderProfile.get(CamcorderProfile.QUALITY_720P);
}else {
mProfile = CamcorderProfile.get(CamcorderProfile.QUALITY_480P);
}
// mMediaRecorder.setProfile(mProfile);//不知道为什么,直接这么设置,不起作用,要下面set 才行
mMediaRecorder.setOutputFormat(mProfile.fileFormat);// 视频输出格式
mMediaRecorder.setVideoFrameRate(mProfile.fileFormat);// 设置录制的视频帧率
mMediaRecorder.setVideoSize(mHeight, mWidth);;// 设置分辨率:
mMediaRecorder.setVideoEncodingBitRate(mProfile.videoBitRate);// 设置帧频率,然后就清晰了
mMediaRecorder.setVideoEncoder(mProfile.videoCodec);// 视频录制格式
// } mMediaRecorder.setOutputFile(mVecordFile.getAbsolutePath()); mMediaRecorder.prepare();
mMediaRecorder.start();
} catch (Exception e) {
e.printStackTrace();
}
} /**
* 开始录制视频
*/
public void startRecord(final OnRecordFinishListener onRecordFinishListener) {
isStarting = true;
this.mOnRecordFinishListener = onRecordFinishListener;
createRecordDir();
try {
initRecord();
mTimeCount = 0;// 时间计数器重新赋值
mTimer = new Timer();
mTimer.schedule(new TimerTask() { @Override
public void run() {
// TODO Auto-generated method stub
mTimeCount++;
mProgressbar.setProgress(mTimeCount);// 设置进度条
if (mTimeCount == mRecordMaxTime) {// 达到指定时间,停止拍摄
stop();
if (mOnRecordFinishListener != null)
mOnRecordFinishListener.onRecordFinish();
}
}
}, 0, 1000);
} catch (Exception e) {
e.printStackTrace();
}
} /**
* 停止拍摄
*/
public void stop() {
stopRecord();
releaseRecord();
freeCameraResource(); } /**
* 停止录制
*/
public void stopRecord() {
mProgressbar.setProgress(0);
if (mTimer != null)
mTimer.cancel();
if (mMediaRecorder != null) {
// 设置后不会崩
mMediaRecorder.setOnErrorListener(null);
mMediaRecorder.setPreviewDisplay(null);
try {
mMediaRecorder.stop();
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (RuntimeException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
} /**
* 释放资源
*/
private void releaseRecord() {
if (mMediaRecorder != null) {
mMediaRecorder.setOnErrorListener(null);
try {
mMediaRecorder.release();
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
mMediaRecorder = null;
} /**
* 创建目录与文件
*/
private void createRecordDir() {
File FileDir = new File(Environment.getExternalStorageDirectory() + File.separator+"RecordVideo/");
if (!FileDir.exists()) {
FileDir.mkdirs();
}
// 创建文件
try {
mVecordFile = new File(FileDir.getAbsolutePath()+"/test.mp4");
Log.d("Path:", mVecordFile.getAbsolutePath());
} catch (Exception e) {
e.printStackTrace();
}
} OnRecordFinishListener recordFinishListener = new OnRecordFinishListener() {
@Override
public void onRecordFinish() {
Intent intent = new Intent(RecordVideoActivity.this, MainActivity.class);
intent.putExtra("filePath", mVecordFile.getAbsolutePath());
setResult(Activity.RESULT_OK, intent);
finish();
}
}; @Override
public void onBackPressed() {
if(!isStarting){
finish();
}
} @Override
public void onClick(View v) {
switch (v.getId()){
case R.id.btn_start:
if(!isStarting)
startRecord(recordFinishListener);
break;
}
} @Override
public void surfaceCreated(SurfaceHolder holder) { } @Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
initCamera(width,height);
} @Override
public void surfaceDestroyed(SurfaceHolder holder) {
freeCameraResource();
} @Override
public void onError(MediaRecorder mr, int what, int extra) {
try {
if (mr != null)
mr.reset();
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
} /**
* 录制完成回调接口
*/
public interface OnRecordFinishListener {
void onRecordFinish();
} }
   最后提供源代码:https://github.com/xiaoxiaoqingyi/3DShow

3D图片采集与展示(SurfaceView 自适应 Camera, 录制视频, 抽取帧)的更多相关文章

  1. Camera 录制视频的实现

    使用 Camera 录制视频, 实现步骤如下: 需要权限: android.permission.CAMERA android.permission.RECORD_AUDIO android.perm ...

  2. 错误:Camera录制视频(6.0错误),5.1正常,7.1正常 (java.lang.RuntimeException: start failed.at android.media.MediaRecorder.native_start(Native Method))

    Process: com.example.mycamera2, PID: 24086 java.lang.RuntimeException: start failed. at android.medi ...

  3. 用java实现给图片增加图片水印或者文字水印(也支持视频图像帧添加水印)

    javaCV图像处理系列: javaCV图像处理之1:实时视频添加文字水印并截取视频图像保存成图片,实现文字水印的字体.位置.大小.粗度.翻转.平滑等操作 javaCV图像处理之2:实时视频添加图片水 ...

  4. javaCV开发详解之4:转流器实现(也可作为本地收流器、推流器,新增添加图片及文字水印,视频图像帧保存),实现rtsp/rtmp/本地文件转发到rtmp流媒体服务器(基于javaCV-FFMPEG)

    javaCV系列文章: javacv开发详解之1:调用本机摄像头视频 javaCV开发详解之2:推流器实现,推本地摄像头视频到流媒体服务器以及摄像头录制视频功能实现(基于javaCV-FFMPEG.j ...

  5. 精致3D图片切换效果,最适合企业产品展示

    这是一个精致的立体图片切换效果,特别适合企业产品展示,可立即用于实际项目中.支持导航和自动播放功能, 基于 CSS3 实现,推荐使用最新的 Chrome,Firefox 和 Safari 浏览器浏览效 ...

  6. Android 高级UI设计笔记14:Gallery(画廊控件)之 3D图片浏览

    1. 利用Gallery组件实现 3D图片浏览器的功能,如下: 2. 下面是详细的实现过程如下: (1)这里我是测试性代码,我的图片是自己添加到res/drawable/目录下的,如下: 但是开发中不 ...

  7. 9个超绚丽的HTML5 3D图片动画特效

    在Web 1.0时代,我们的网页中图片数量非常少,而且都是以静态图片为主.HTML5的出现,推动了Web 2.0的发展,同时也催生出了很多绚丽的HTML5图片动画特效,特别是有些还有3D的动画效果.本 ...

  8. WPF技术触屏上的应用系列(一): 3D 图片(照片)墙、柱面墙(凹面墙或者叫远景墙、凸面墙或者叫近景墙)实现

    原文:WPF技术触屏上的应用系列(一): 3D 图片(照片)墙.柱面墙(凹面墙或者叫远景墙.凸面墙或者叫近景墙)实现 去年某客户单位要做个大屏触屏应用,要对档案资源进行展示之用.客户端是Window7 ...

  9. 基于ZedBoard的Webcam设计(一):USB摄像头(V4L2接口)的图片采集【转】

    转自:http://www.cnblogs.com/surpassal/archive/2012/12/19/zed_webcam_lab1.html 一直想把USB摄像头接到Zedboard上,搭建 ...

随机推荐

  1. (转)c#缓存介绍

    在 ASP.NET 提供的许多特性中,缓存支持无疑是最值得欣赏的特性.相比 ASP.NET 的所有其他特性,缓存对应用程序的性能具有最大的潜在影响,利用缓存和其他机制,ASP.NET 开发人员可以接受 ...

  2. (转)SQL Server 2005附加2008的数据库

    1. 生成for 2005版本的数据库脚本  2008 的manger studio  -- 打开"对象资源管理器"(没有的话按F8), 连接到你的实例  -- 右键要转到2005 ...

  3. Swift - guard关键字(守护)

    guard语句和if语句有点类似,都是根据其关键字之后的表达式的布尔值决定下一步执行什么.但与if语句不同的是,guard语句只会有一个代码块,不像if语句可以if else多个代码块. 那么guar ...

  4. R0:前瞻

    原文链接http://www.wangafu.net/~nickm/libevent-book/Ref0_meta.html Libevent使用手册:前瞻 总览: Libevent是一个用来写高性能 ...

  5. include,include_once,require,require_once的区别

    1.include,require在其被调用的位置处包含一个文件. 2.include_once,require_once函数的作用与include相同,不过它会首先验证是否已包含该文件.如果已经包含 ...

  6. 解决APP中fragment重叠问题

    由于内存重启,导致的frgament重叠,其原因就是FragmentState没有保存Fragment的显示状态,即mHidden,导致页面重启后,该值为默认的false,即show状态,所以导致了F ...

  7. SQL语句中的乘号

    在ADO中,我们需要在SQL语句中使用乘法运算,可是添加'*'以后执行程序总是会出错,这是因为‘*’与sql中的‘*’关键字重合了,所以编译会出错. 解决办法:将乘法运算放到sql语句外面,将结果放入 ...

  8. Eclipse项目导入Android Stuio 配置出现 Timeout waiting to lock buildscript class cache for build file 'H:\studioproject\Generic_SN\build.gradle'

     Eclipse项目导入Android Stuio 配置出现 Error:Timeout waiting to lock buildscript class cache for build file  ...

  9. Linux/Unix工具与正则表达式的POSIX规范

    http://www.infoq.com/cn/news/2011/07/regular-expressions-6-POSIX 对正则表达式有基本了解的读者,一定不会陌生『\d』.『[a-z]+』之 ...

  10. Detecting an Ajax request in PHP

    1:index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset=&q ...