Android多媒体技术(一)Camera实时视频采集预览、拍照、JPEG图片方向的处理_android camera 拍摄照片 jpg-程序员宅基地

技术标签: 多媒体  技术  视频  安卓开发  安卓基础  

转载文章:http://blog.csdn.net/andrexpert/article/details/54388929

Camera实时视频采集预览、拍照、JPEG图片方向的处理                      

 

   作者:   

 蒋东国

   时间:

 2017年1月12日 星期四                                          

   应用来源:  

 hzzbj

   博客地址:

 http://blog.csdn.net/andrexpert/article/details/54388929

   

  手机摄像头(Camera)的图像数据来源于摄像头硬件的图像传感器,这个图像传感器被固定到手机上后会有一个默认的取景方向,这个取景方向恰好是当手机向左侧横放时的方向,其坐标原点位于手机横放时的左上角。手机的正常方向和图像传感器默认取景方向示意图如下:


1. Camera预览方向处理
  从上面的示意图可知,图像传感器的取景方向与手机正常方向成90度夹角,按理来说,当我们以正常的手机方向打开相机(Camera)时,看到的预览图像应该是横向的。但是,当我们打开系统相机后,看到的预览图像却是正常的,即预览图像与手机方向一致。这是因为系统自带的相机在Android系统底层根据当前手机屏幕的方向对图像传感器采集到的数据进行了旋转,所以无论我们怎么旋转手机屏幕,看到的相机预览图片始终是”正常”的。而对于自定义的相机,如果没有对图像传感器采集的图片进行旋转处理,那么看到的预览图片就是横向的,效果如下图所示:


  为了解决自定义相机预览方向不正常情况,Android系统提供了一个API来手动设置Camera的预览方向,即Camera.setDisplayOrientation(int rotateDegree),默认情况下该方法的值为0,与图像传感器取景方向一致。旋转方法:

 首先,通过Display的getOrientation()获得当前手机的方向,如Surface.ROTATION_0表示手机竖屏时正常方向、Surface.ROTATION_90表示手机方向向右手边横向放置等(沿顺时针判断)。其中,Display display = getWindowManager().getDefaultDisplay()获得。
 其次,对于后置摄像头来说,它的预览成像为CameraInfo.orientatio- phoneDegree,但由于这个值可能为负,角度值不能为负故需要加上360求正;对于前置摄像头(front camera)来说,它的预览图像在旋转之前是水平翻转的,也就是前置摄像头的预览成像是沿图像的中央垂直线翻转过来,就像用户照镜子一样的效果。因此,在得到前置摄像头的旋转角度后(rotation = CameraInfo.orientatio + degrees),还需要对其进行水平翻转(rotation = 360-rotation),即取rotation的负数即可,但是由于旋转的角度不能是负数,因此再加上360求正。其中,CameraInfo.orientatio是图像感应器相对于手机竖直正常方向的角度值、手机方向为相对于竖直正常方向沿顺时针转动的方向值。另外,当我们得到前后置摄像头旋转的方向后还需要对360求余,以防止旋转的角度超过一周360度的情况。

具体代码如下:

  1. private int getPreviewRotateDegree(){  
  2.     int phoneDegree = 0;  
  3.     int result = 0;  
  4.     //获得手机方向  
  5.     int phoneRotate =getWindowManager().getDefaultDisplay().getOrientation();  
  6.     //得到手机的角度  
  7.     switch (phoneRotate) {  
  8.         case Surface.ROTATION_0: phoneDegree = 0break;        //0  
  9.         case Surface.ROTATION_90: phoneDegree = 90break;      //90  
  10.         case Surface.ROTATION_180: phoneDegree = 180break;    //180  
  11.         case Surface.ROTATION_270: phoneDegree = 270break;    //270  
  12.     }  
  13.     //分别计算前后置摄像头需要旋转的角度  
  14.     Camera.CameraInfo cameraInfo = new CameraInfo();  
  15.     if(isFrontCamera){            
  16.         Camera.getCameraInfo(Camera.CameraInfo.CAMERA_FACING_FRONT, cameraInfo);  
  17.         result = (cameraInfo.orientation + phoneDegree) % 360;  
  18.         result = (360 - result) % 360;  
  19.     }else{  
  20.         Camera.getCameraInfo(Camera.CameraInfo.CAMERA_FACING_BACK, cameraInfo);  
  21.         result = (cameraInfo.orientation - phoneDegree +360) % 360;  
  22.     }  
  23.     return result;  
  24. }  
  25.   
  26. //进行Camera预览旋转  
  27. Camera mCamera = Camera.open();  
  28. int rotateDegree = getPreviewRotateDegree();  
  29. mCamera.setDisplayOrientation(rotateDegree);  
private int getPreviewRotateDegree(){
    int phoneDegree = 0;
    int result = 0;
    //获得手机方向
    int phoneRotate =getWindowManager().getDefaultDisplay().getOrientation();
    //得到手机的角度
    switch (phoneRotate) {
        case Surface.ROTATION_0: phoneDegree = 0; break;        //0
        case Surface.ROTATION_90: phoneDegree = 90; break;      //90
        case Surface.ROTATION_180: phoneDegree = 180; break;    //180
        case Surface.ROTATION_270: phoneDegree = 270; break;    //270
    }
    //分别计算前后置摄像头需要旋转的角度
    Camera.CameraInfo cameraInfo = new CameraInfo();
    if(isFrontCamera){          
        Camera.getCameraInfo(Camera.CameraInfo.CAMERA_FACING_FRONT, cameraInfo);
        result = (cameraInfo.orientation + phoneDegree) % 360;
        result = (360 - result) % 360;
    }else{
        Camera.getCameraInfo(Camera.CameraInfo.CAMERA_FACING_BACK, cameraInfo);
        result = (cameraInfo.orientation - phoneDegree +360) % 360;
    }
    return result;
}

//进行Camera预览旋转
Camera mCamera = Camera.open();
int rotateDegree = getPreviewRotateDegree();
mCamera.setDisplayOrientation(rotateDegree);

  注意:上述方法适用于默认预览为竖屏应用,setDisplayOrientation (int degrees)只对预览时旋转图片有效,但对onPreviewFrame(byte[],Camera)、JPEG拍照、视频录制的图片旋转无效。

2.Camera拍照方向处理
  由于使用Camera进行拍照时,是直接将图像传感器采集到的图像数据直接存储到Sdcard卡,它通常不与预览时看到的画面方向一致,而是与图像传感器的方向一致。也就是说,当我们竖着拿着手机拍摄时,得到的照片看起来是不正常的(横向的),这是因为竖着拿着手机正好与图像传感器的方向相差了90度;当横着拿着手机拍摄时,得到的照片看起来才是正常的,。效果如下图所示。


  Camera拍摄照片方向的处理与手机的方向紧密相关,而由于拍摄照片时手机的方向是不确定的,因此需要手机的方向感应器(OrientationEventListener)来捕获手机的实时旋转角度,当手机方向发现偏转时OrientationEventListener的onOrientationChanged(int orientation)方法会立即被回调,orientation即为实时变化的角度。旋转方法:
  首先,为了使相机对方向不那么敏感,可以采用一个范围来限定手机当前方向的角度值,比如当手机的方向处于45度~ 90度时,我们就认定手机当前转动的角度为90度,依次类推得到手机大概的方向角度值。
  其次,计算前后置摄像头需要旋转的角度。Camera的预览效果是获得图像传感器采集的图像数据后再将其显示在显示屏上,而拍摄照片则是直接将图像传感器采集的图像数据保存到Sdcard上,因此,它们处理旋转时的角度计算是不同的。由于图像传感器的取景方向与手机竖直方向恰好相差90度,因此,对于后置摄像头来说,其旋转的角度应该手机实际变化的角度加上图像传感器与手机之间的夹角,即mOrientation=cameraInfo.orientation +phoneDegree;对于前置摄像头来说,旋转的角度mOrientation=cameraInfo.orientation – phoneDegree。以手机方向改变270度为例,效果如下图(2)所示,后置摄像头需旋转的角度为(270+90),可见刚好为360度使摄像头与图像传感器方向一致,那么旋转的角度进行求余处理后刚好为0。由于前置摄像头是水平翻转的,因此需要对需要进行水平翻转处理,也就是180度的问题,最终旋转的角度为|(90-270)|=180。

  

具体代码如下:

  1. private void startOrientationListener() {  
  2. OrientationEventListener mOrEventListener = new OrientationEventListener(mContext) {  
  3. @Override  
  4. public void onOrientationChanged(int orientation) {  
  5. //计算手机当前方向的角度值  
  6. int phoneDegree = 0;  
  7. if (((orientation >= 0) && (orientation <= 45))|| (orientation > 315) &&(orientation<=360)) {  
  8. phoneDegree = 0;  
  9. else if ((orientation > 45) && (orientation <= 135)) {  
  10. phoneDegree = 90;  
  11. else if ((orientation > 135) && (orientation <= 225)) {  
  12. phoneDegree = 180;  
  13. else if ((orientation > 225) && (orientation <= 315)) {  
  14. phoneDegree = 270;  
  15. }  
  16. //分别计算前后置摄像头需要旋转的角度  
  17. Camera.CameraInfo cameraInfo = new CameraInfo();  
  18. if(mFragment.isFrontCamera()){  
  19. Camera.getCameraInfo(Camera.CameraInfo.CAMERA_FACING_FRONT, cameraInfo);  
  20. mOrientation = (cameraInfo.orientation - phoneDegree +360) % 360;  
  21. }else{  
  22. Camera.getCameraInfo(Camera.CameraInfo.CAMERA_FACING_BACK, cameraInfo);  
  23. mOrientation = (cameraInfo.orientation + phoneDegree) % 360;  
  24. }  
  25. };  
  26. //启动方向感应器  
  27. mOrEventListener.enable();  
  28. }  
private void startOrientationListener() {
OrientationEventListener mOrEventListener = new OrientationEventListener(mContext) {
@Override
public void onOrientationChanged(int orientation) {
//计算手机当前方向的角度值
int phoneDegree = 0;
if (((orientation >= 0) && (orientation <= 45))|| (orientation > 315) &&(orientation<=360)) {
phoneDegree = 0;
} else if ((orientation > 45) && (orientation <= 135)) {
phoneDegree = 90;
} else if ((orientation > 135) && (orientation <= 225)) {
phoneDegree = 180;
} else if ((orientation > 225) && (orientation <= 315)) {
phoneDegree = 270;
}
//分别计算前后置摄像头需要旋转的角度
Camera.CameraInfo cameraInfo = new CameraInfo();
if(mFragment.isFrontCamera()){
Camera.getCameraInfo(Camera.CameraInfo.CAMERA_FACING_FRONT, cameraInfo);
mOrientation = (cameraInfo.orientation - phoneDegree +360) % 360;
}else{
Camera.getCameraInfo(Camera.CameraInfo.CAMERA_FACING_BACK, cameraInfo);
mOrientation = (cameraInfo.orientation + phoneDegree) % 360;
}
};
//启动方向感应器
mOrEventListener.enable();
}
注意:由于上述涉及的角度值都是正数且不大于360度,因此,需要对相关角度进行求正和求余处理。
3.JPEG图片方向处理
  有这么一种情况,如果有一款自定义相机的拍照功能忘记处理图片旋转的问题,那么我们
在使用的过程中就会看到拍下的JPEG照片显示方向“不正常”。针对于这种情况,可以通过Android API提供的ExifInterface接口来解决,该接口存储了指定JPEG图片的详细信息,比如拍摄时的角度、曝光度、分辨率等等。旋转方法:
  首先,根据图片路径创建一个ExifInterface对象,再调用其getAttributeInt(
ExifInterface.TAG_ORIENTATION,ExifInterface.ORIENTATION_NORMAL)方法,得到JPEG拍摄时的角度。

  其次,调用Matrix的postRotate(degree)方法对图片进行旋转,然后再使用Bitmap.createBitmap方法得到最终的位图对象。
  具体代码如下:

  1. public static int getPictureDegress(String filePath) {  
  2. int degree = 0;  
  3. ExifInterface exifInterface = null;  
  4. try {  
  5. exifInterface = new ExifInterface(filePath);  
  6. catch (IOException e) {  
  7. e.printStackTrace();  
  8. }  
  9. if (exifInterface != null) {  
  10. //获得图片拍摄角度,第二个的作用是如果这个属性不存在,则作为默认值返回  
  11. int orientation = exifInterface.getAttributeInt(  
  12. ExifInterface.TAG_ORIENTATION,  
  13. ExifInterface.ORIENTATION_NORMAL);  
  14. switch (orientation) {  
  15. case ExifInterface.ORIENTATION_ROTATE_90:  
  16. degree = 90;  
  17. break;  
  18. case ExifInterface.ORIENTATION_ROTATE_180:  
  19. degree = 180;  
  20. break;  
  21. case ExifInterface.ORIENTATION_ROTATE_270:  
  22. degree = 270;  
  23. break;  
  24. default:  
  25. degree = 0;  
  26. break;  
  27. }  
  28. return degree;  
  29. }  
  30. return 0;  
  31. }  
  32. /** 旋转图片 
  33. * @param imgPath 原图路径 
  34. * @ param imgPath 
  35. */  
  36. public static Bitmap setBitmapDegreeZero(String imgPath) {  
  37. Bitmap mBitmap = null;  
  38. int degree = getPictureDegress(imgPath);  
  39. if (degree != 0) {  
  40. mBitmap = BitmapFactory.decodeFile(imgPath);  
  41. Matrix matrix = new Matrix();  
  42. matrix.postRotate(degree);  
  43. mBitmap = Bitmap.createBitmap(mBitmap, 00, mBitmap.getWidth(),  
  44. mBitmap.getHeight(), matrix, true);  
  45. }  
  46. return mBitmap;  
  47. }  
public static int getPictureDegress(String filePath) {
int degree = 0;
ExifInterface exifInterface = null;
try {
exifInterface = new ExifInterface(filePath);
} catch (IOException e) {
e.printStackTrace();
}
if (exifInterface != null) {
//获得图片拍摄角度,第二个的作用是如果这个属性不存在,则作为默认值返回
int orientation = exifInterface.getAttributeInt(
ExifInterface.TAG_ORIENTATION,
ExifInterface.ORIENTATION_NORMAL);
switch (orientation) {
case ExifInterface.ORIENTATION_ROTATE_90:
degree = 90;
break;
case ExifInterface.ORIENTATION_ROTATE_180:
degree = 180;
break;
case ExifInterface.ORIENTATION_ROTATE_270:
degree = 270;
break;
default:
degree = 0;
break;
}
return degree;
}
return 0;
}
/** 旋转图片
* @param imgPath 原图路径
* @ param imgPath
*/
public static Bitmap setBitmapDegreeZero(String imgPath) {
Bitmap mBitmap = null;
int degree = getPictureDegress(imgPath);
if (degree != 0) {
mBitmap = BitmapFactory.decodeFile(imgPath);
Matrix matrix = new Matrix();
matrix.postRotate(degree);
mBitmap = Bitmap.createBitmap(mBitmap, 0, 0, mBitmap.getWidth(),
mBitmap.getHeight(), matrix, true);
}
return mBitmap;
}

效果演示:


关于参考资料:
Android开发实践:掌握Camera的预览方向和拍照方向
http://www.tuicool.com/articles/AnUBBnR


版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/wyjlovecr7/article/details/78784112

智能推荐

oracle 12c 集群安装后的检查_12c查看crs状态-程序员宅基地

文章浏览阅读1.6k次。安装配置gi、安装数据库软件、dbca建库见下:http://blog.csdn.net/kadwf123/article/details/784299611、检查集群节点及状态:[root@rac2 ~]# olsnodes -srac1 Activerac2 Activerac3 Activerac4 Active[root@rac2 ~]_12c查看crs状态

解决jupyter notebook无法找到虚拟环境的问题_jupyter没有pytorch环境-程序员宅基地

文章浏览阅读1.3w次,点赞45次,收藏99次。我个人用的是anaconda3的一个python集成环境,自带jupyter notebook,但在我打开jupyter notebook界面后,却找不到对应的虚拟环境,原来是jupyter notebook只是通用于下载anaconda时自带的环境,其他环境要想使用必须手动下载一些库:1.首先进入到自己创建的虚拟环境(pytorch是虚拟环境的名字)activate pytorch2.在该环境下下载这个库conda install ipykernelconda install nb__jupyter没有pytorch环境

国内安装scoop的保姆教程_scoop-cn-程序员宅基地

文章浏览阅读5.2k次,点赞19次,收藏28次。选择scoop纯属意外,也是无奈,因为电脑用户被锁了管理员权限,所有exe安装程序都无法安装,只可以用绿色软件,最后被我发现scoop,省去了到处下载XXX绿色版的烦恼,当然scoop里需要管理员权限的软件也跟我无缘了(譬如everything)。推荐添加dorado这个bucket镜像,里面很多中文软件,但是部分国外的软件下载地址在github,可能无法下载。以上两个是官方bucket的国内镜像,所有软件建议优先从这里下载。上面可以看到很多bucket以及软件数。如果官网登陆不了可以试一下以下方式。_scoop-cn

Element ui colorpicker在Vue中的使用_vue el-color-picker-程序员宅基地

文章浏览阅读4.5k次,点赞2次,收藏3次。首先要有一个color-picker组件 <el-color-picker v-model="headcolor"></el-color-picker>在data里面data() { return {headcolor: ’ #278add ’ //这里可以选择一个默认的颜色} }然后在你想要改变颜色的地方用v-bind绑定就好了,例如:这里的:sty..._vue el-color-picker

迅为iTOP-4412精英版之烧写内核移植后的镜像_exynos 4412 刷机-程序员宅基地

文章浏览阅读640次。基于芯片日益增长的问题,所以内核开发者们引入了新的方法,就是在内核中只保留函数,而数据则不包含,由用户(应用程序员)自己把数据按照规定的格式编写,并放在约定的地方,为了不占用过多的内存,还要求数据以根精简的方式编写。boot启动时,传参给内核,告诉内核设备树文件和kernel的位置,内核启动时根据地址去找到设备树文件,再利用专用的编译器去反编译dtb文件,将dtb还原成数据结构,以供驱动的函数去调用。firmware是三星的一个固件的设备信息,因为找不到固件,所以内核启动不成功。_exynos 4412 刷机

Linux系统配置jdk_linux配置jdk-程序员宅基地

文章浏览阅读2w次,点赞24次,收藏42次。Linux系统配置jdkLinux学习教程,Linux入门教程(超详细)_linux配置jdk

随便推点

matlab(4):特殊符号的输入_matlab微米怎么输入-程序员宅基地

文章浏览阅读3.3k次,点赞5次,收藏19次。xlabel('\delta');ylabel('AUC');具体符号的对照表参照下图:_matlab微米怎么输入

C语言程序设计-文件(打开与关闭、顺序、二进制读写)-程序员宅基地

文章浏览阅读119次。顺序读写指的是按照文件中数据的顺序进行读取或写入。对于文本文件,可以使用fgets、fputs、fscanf、fprintf等函数进行顺序读写。在C语言中,对文件的操作通常涉及文件的打开、读写以及关闭。文件的打开使用fopen函数,而关闭则使用fclose函数。在C语言中,可以使用fread和fwrite函数进行二进制读写。‍ Biaoge 于2024-03-09 23:51发布 阅读量:7 ️文章类型:【 C语言程序设计 】在C语言中,用于打开文件的函数是____,用于关闭文件的函数是____。

Touchdesigner自学笔记之三_touchdesigner怎么让一个模型跟着鼠标移动-程序员宅基地

文章浏览阅读3.4k次,点赞2次,收藏13次。跟随鼠标移动的粒子以grid(SOP)为partical(SOP)的资源模板,调整后连接【Geo组合+point spirit(MAT)】,在连接【feedback组合】适当调整。影响粒子动态的节点【metaball(SOP)+force(SOP)】添加mouse in(CHOP)鼠标位置到metaball的坐标,实现鼠标影响。..._touchdesigner怎么让一个模型跟着鼠标移动

【附源码】基于java的校园停车场管理系统的设计与实现61m0e9计算机毕设SSM_基于java技术的停车场管理系统实现与设计-程序员宅基地

文章浏览阅读178次。项目运行环境配置:Jdk1.8 + Tomcat7.0 + Mysql + HBuilderX(Webstorm也行)+ Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。项目技术:Springboot + mybatis + Maven +mysql5.7或8.0+html+css+js等等组成,B/S模式 + Maven管理等等。环境需要1.运行环境:最好是java jdk 1.8,我们在这个平台上运行的。其他版本理论上也可以。_基于java技术的停车场管理系统实现与设计

Android系统播放器MediaPlayer源码分析_android多媒体播放源码分析 时序图-程序员宅基地

文章浏览阅读3.5k次。前言对于MediaPlayer播放器的源码分析内容相对来说比较多,会从Java-&amp;amp;gt;Jni-&amp;amp;gt;C/C++慢慢分析,后面会慢慢更新。另外,博客只作为自己学习记录的一种方式,对于其他的不过多的评论。MediaPlayerDemopublic class MainActivity extends AppCompatActivity implements SurfaceHolder.Cal..._android多媒体播放源码分析 时序图

java 数据结构与算法 ——快速排序法-程序员宅基地

文章浏览阅读2.4k次,点赞41次,收藏13次。java 数据结构与算法 ——快速排序法_快速排序法