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

智能推荐

info级别日志与debug_debug中的计算是否在info级别也会跑-程序员宅基地

文章浏览阅读6.3k次。日志默认info级别debug日志不会打印,但是会执行日志填充的数据例如:logger.debug("日志输出",2*10); 1. 2*10会先执行出结果,然后继续往下走2. 在ch.qos.logback.classic.Logger#filterAndLog_1方法中判断是否符合级别要求是否需要输出3.如图:..._debug中的计算是否在info级别也会跑

Third calibration example - Calibration using Heikkil�'s data (planar and non-planar calibration rig-程序员宅基地

文章浏览阅读1.4k次。Similarly to the previous example, let us apply our calibration engine onto the data that comes with the originalcalibration toolbox of Heikkil� from the University of Oulu. Once again. do not bothe_non-planar calibration

物联网常用的网络协议:MQTT、AMQP、HTTP、CoAP、LwM2M_lmm2m和mqtt-程序员宅基地

文章浏览阅读1w次,点赞10次,收藏63次。物联网常用的网络协议:MQTT、AMQP、HTTP、CoAP、LwM2M物联网设备间沟通的语言,就是网络协议。设备间想相互交流,通信双方必须使用同一种“语言”。比如说你和中国人问好说’你好‘、日本人问好要说‘こんにちは’、和英国人问好要说‘hello’.说起网络协议,你可能马上就想到了 HTTP 协议。是的,在日常的 Web 开发中,我们总是需要跟它打交道,因为 HTTP 协议是互联网的主流网络协议。类似地,应用在互联网中的网络协议,还有收发电子邮件的 POP3 、SMTP 和 IMAP 协议,以及_lmm2m和mqtt

fortran使用MKL函数库中的geev计算一般矩阵的特征值与特征向量_fortran求矩阵特征值-程序员宅基地

文章浏览阅读7.4k次,点赞4次,收藏20次。这篇博文简要记录一下使用MKL函数库计算一般矩阵的特征值与特征向量对形如对称矩阵或是埃尔米特等特殊矩阵有其对应的子程序,在这里先不涉及。有需求的可以自行查阅MKL官方文档下面给出本次示例代码:代码使用f95接口。f77借口参数太多,笔者太懒<不过懒惰是创新的原动力^_^>program testGeev use lapack95 implicit..._fortran求矩阵特征值

Numpy, Scipy, Matplotlib基本用法_np.imresize-程序员宅基地

文章浏览阅读147次。学习内容来自:Numpy Tutorial文章目录Array SlicingArray IndexingMathematical ManipulationBroadcastingImage Processing基本的用法课程里面说的挺详细了。 特别记录一些需要关注的点。Array Slicing使用固定数字进行array寻址会导致数组降维。y = np.random.random((3,..._np.imresize

蓝桥杯 历届试题 回文数字 C++_c++蓝桥杯 回文数-程序员宅基地

文章浏览阅读355次。题目阅览 观察数字:12321,123321 都有一个共同的特征,无论从左到右读还是从右向左读,都是相同的。这样的数字叫做:回文数字。  本题要求你找到一些5位或6位的十进制数字。满足如下要求:  该数字的各个数位之和等于输入的整数。  输入格式  一个正整数 n (10<n<100), 表示要求满足的数位和。  输出格式若干行,每行包含一个满足要求的5位或6位整数。  数字按从小到大的顺序排列。  如果没有满足条件的,输出:-1样例输入144样例输出199899_c++蓝桥杯 回文数

随便推点

Java生成二维码,扫描并跳转到指定的网站_java扫二维码进入自己制作的网页-程序员宅基地

文章浏览阅读6.2k次,点赞3次,收藏13次。需要的pom文件 &lt;dependency&gt; &lt;groupId&gt;com.google.zxing&lt;/groupId&gt; &lt;artifactId&gt;core&lt;/artifactId&gt; &lt;version&gt;3.1.0&lt;/version&gt;_java扫二维码进入自己制作的网页

python:多波段遥感影像分离成单波段影像_一个多波段影像分解成多个单波段影像-程序员宅基地

文章浏览阅读650次。在遥感图像处理中,我们经常需要将多波段遥感影像拆分成多个单波段图像,以便进行各种分析和后续处理。本篇博客将介绍一个用Python编写的程序,该程序可以读取多波段遥感影像,将其拆分为单波段图像,并保存为单独的文件。本程序使用GDAL库来处理遥感影像数据,以及NumPy库来进行数组操作。结果如下图所示,选中的影像为输入的多波段影像,其他影像分别为拆分后的多波段影像。_一个多波段影像分解成多个单波段影像

移动硬盘突然在电脑上无法显示_电脑无法显示移动硬盘-程序员宅基地

文章浏览阅读5.1k次,点赞2次,收藏4次。0前言一直用的好好的移动硬盘突然不显示了,前段时间因为比较忙,一直没顾得上管它,趁这个假期,好好捅咕了一番,总算是弄好了,特此将解决的过程记录如下:1.问题描述 1.我的移动硬盘在其他人的电脑上能够正常显示和使用 2.其他移动硬盘在我电脑上能够正常的显示和使用 3.在我的电脑上,该移动硬盘,既不显示盘符,磁盘管理 又不显示该磁盘2.问题分析1.我的移动硬盘能够在其他人电脑上_电脑无法显示移动硬盘

Linux开机启动过程(16):start_kernel()->rest_init()启动成功_linux 标志着kernel启动完成-程序员宅基地

文章浏览阅读1k次。Kernel initialization. Part 10.在原文的基础上添加了5.10.13部分的源码解读。End of the linux kernel initialization processThis is tenth part of the chapter about linux kernel initialization process and in the previous part we saw the initialization of the RCU and stopped o_linux 标志着kernel启动完成

Scala安装和开发环境配置教程_scala安装及环境配置-程序员宅基地

文章浏览阅读5.3k次,点赞5次,收藏23次。Scala语言概述:Scala语言是一门以Java虚拟机为运行环境,支持面向对象和函数式编程的静态语言,java语言是面向对象的,所以代码写起来就会相对比较模块儿,而函数式编程语言相对比较简洁_scala安装及环境配置

深扒人脸识别60年技术发展史_人脸识别发展历史-程序员宅基地

文章浏览阅读2.4k次。“他来听我的演唱会,门票换了手铐一对”。最近歌神张学友变阿SIR,演唱会上频频抓到罪犯,将人脸识别技术又一次推到了大众的视线中。要说人脸识别技术的爆发,当属去年9月份苹果iPhone x的发布,不再需要指纹,只需要扫描面部就可以轻松解锁手机。任何技术一旦进入智能手机这个消费市场,尤其是被苹果这个标志性的品牌采用,就意味着它将成为一种趋势,一个智能设备的标配。在智能手机快速崛起的这几年,其密码锁..._人脸识别发展历史