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

智能推荐

使用nginx解决浏览器跨域问题_nginx不停的xhr-程序员宅基地

文章浏览阅读1k次。通过使用ajax方法跨域请求是浏览器所不允许的,浏览器出于安全考虑是禁止的。警告信息如下:不过jQuery对跨域问题也有解决方案,使用jsonp的方式解决,方法如下:$.ajax({ async:false, url: 'http://www.mysite.com/demo.do', // 跨域URL ty..._nginx不停的xhr

在 Oracle 中配置 extproc 以访问 ST_Geometry-程序员宅基地

文章浏览阅读2k次。关于在 Oracle 中配置 extproc 以访问 ST_Geometry,也就是我们所说的 使用空间SQL 的方法,官方文档链接如下。http://desktop.arcgis.com/zh-cn/arcmap/latest/manage-data/gdbs-in-oracle/configure-oracle-extproc.htm其实简单总结一下,主要就分为以下几个步骤。..._extproc

Linux C++ gbk转为utf-8_linux c++ gbk->utf8-程序员宅基地

文章浏览阅读1.5w次。linux下没有上面的两个函数,需要使用函数 mbstowcs和wcstombsmbstowcs将多字节编码转换为宽字节编码wcstombs将宽字节编码转换为多字节编码这两个函数,转换过程中受到系统编码类型的影响,需要通过设置来设定转换前和转换后的编码类型。通过函数setlocale进行系统编码的设置。linux下输入命名locale -a查看系统支持的编码_linux c++ gbk->utf8

IMP-00009: 导出文件异常结束-程序员宅基地

文章浏览阅读750次。今天准备从生产库向测试库进行数据导入,结果在imp导入的时候遇到“ IMP-00009:导出文件异常结束” 错误,google一下,发现可能有如下原因导致imp的数据太大,没有写buffer和commit两个数据库字符集不同从低版本exp的dmp文件,向高版本imp导出的dmp文件出错传输dmp文件时,文件损坏解决办法:imp时指定..._imp-00009导出文件异常结束

python程序员需要深入掌握的技能_Python用数据说明程序员需要掌握的技能-程序员宅基地

文章浏览阅读143次。当下是一个大数据的时代,各个行业都离不开数据的支持。因此,网络爬虫就应运而生。网络爬虫当下最为火热的是Python,Python开发爬虫相对简单,而且功能库相当完善,力压众多开发语言。本次教程我们爬取前程无忧的招聘信息来分析Python程序员需要掌握那些编程技术。首先在谷歌浏览器打开前程无忧的首页,按F12打开浏览器的开发者工具。浏览器开发者工具是用于捕捉网站的请求信息,通过分析请求信息可以了解请..._初级python程序员能力要求

Spring @Service生成bean名称的规则(当类的名字是以两个或以上的大写字母开头的话,bean的名字会与类名保持一致)_@service beanname-程序员宅基地

文章浏览阅读7.6k次,点赞2次,收藏6次。@Service标注的bean,类名:ABDemoService查看源码后发现,原来是经过一个特殊处理:当类的名字是以两个或以上的大写字母开头的话,bean的名字会与类名保持一致public class AnnotationBeanNameGenerator implements BeanNameGenerator { private static final String C..._@service beanname

随便推点

二叉树的各种创建方法_二叉树的建立-程序员宅基地

文章浏览阅读6.9w次,点赞73次,收藏463次。1.前序创建#include&lt;stdio.h&gt;#include&lt;string.h&gt;#include&lt;stdlib.h&gt;#include&lt;malloc.h&gt;#include&lt;iostream&gt;#include&lt;stack&gt;#include&lt;queue&gt;using namespace std;typed_二叉树的建立

解决asp.net导出excel时中文文件名乱码_asp.net utf8 导出中文字符乱码-程序员宅基地

文章浏览阅读7.1k次。在Asp.net上使用Excel导出功能,如果文件名出现中文,便会以乱码视之。 解决方法: fileName = HttpUtility.UrlEncode(fileName, System.Text.Encoding.UTF8);_asp.net utf8 导出中文字符乱码

笔记-编译原理-实验一-词法分析器设计_对pl/0作以下修改扩充。增加单词-程序员宅基地

文章浏览阅读2.1k次,点赞4次,收藏23次。第一次实验 词法分析实验报告设计思想词法分析的主要任务是根据文法的词汇表以及对应约定的编码进行一定的识别,找出文件中所有的合法的单词,并给出一定的信息作为最后的结果,用于后续语法分析程序的使用;本实验针对 PL/0 语言 的文法、词汇表编写一个词法分析程序,对于每个单词根据词汇表输出: (单词种类, 单词的值) 二元对。词汇表:种别编码单词符号助记符0beginb..._对pl/0作以下修改扩充。增加单词

android adb shell 权限,android adb shell权限被拒绝-程序员宅基地

文章浏览阅读773次。我在使用adb.exe时遇到了麻烦.我想使用与bash相同的adb.exe shell提示符,所以我决定更改默认的bash二进制文件(当然二进制文件是交叉编译的,一切都很完美)更改bash二进制文件遵循以下顺序> adb remount> adb push bash / system / bin /> adb shell> cd / system / bin> chm..._adb shell mv 权限

投影仪-相机标定_相机-投影仪标定-程序员宅基地

文章浏览阅读6.8k次,点赞12次,收藏125次。1. 单目相机标定引言相机标定已经研究多年,标定的算法可以分为基于摄影测量的标定和自标定。其中,应用最为广泛的还是张正友标定法。这是一种简单灵活、高鲁棒性、低成本的相机标定算法。仅需要一台相机和一块平面标定板构建相机标定系统,在标定过程中,相机拍摄多个角度下(至少两个角度,推荐10~20个角度)的标定板图像(相机和标定板都可以移动),即可对相机的内外参数进行标定。下面介绍张氏标定法(以下也这么称呼)的原理。原理相机模型和单应矩阵相机标定,就是对相机的内外参数进行计算的过程,从而得到物体到图像的投影_相机-投影仪标定

Wayland架构、渲染、硬件支持-程序员宅基地

文章浏览阅读2.2k次。文章目录Wayland 架构Wayland 渲染Wayland的 硬件支持简 述: 翻译一篇关于和 wayland 有关的技术文章, 其英文标题为Wayland Architecture .Wayland 架构若是想要更好的理解 Wayland 架构及其与 X (X11 or X Window System) 结构;一种很好的方法是将事件从输入设备就开始跟踪, 查看期间所有的屏幕上出现的变化。这就是我们现在对 X 的理解。 内核是从一个输入设备中获取一个事件,并通过 evdev 输入_wayland

推荐文章

热门文章

相关标签