1 Camera预览角度处理
开发Camera过程中会遇到Camera拍照,获取照片后可以上传照片或者展示给用户。
Camera的图像数据来源于摄像头硬件的图像传感器,这个图像传感器被固定到手机上后会有一个默认的方向,一般默认方向是当手机左侧横放时(手机横放并且手机顶部在左侧)。由于默认图片传感器为横向,大部分手机拍照则是竖向,所以得到的数据依然会是横向的,这时就需要对图片进行旋转。
图像传感器的取景方向与手机正常方向成90读夹角。
由于Camera默认是横向的,竖向拍照时得到的照片和预览的照片会有所不同,因为预览可以利用setDisplayOrientation设置预览角度调节预览图片,但是setDisplayOrientation只是改变了预览的角度,对于拍摄生成的图片依然会拿到原来的未被旋转和默认图片传感器方向相同的数据。而对于前置摄像头预览得到的图片会比后置摄像头多一个镜面效果,两者都需要对拍摄生成的图片进行旋转处理才能得到正常的符合眼睛所看到的预览图片的样式。
解决预览方向问题:setDisplayOrientation(int rotateDegree),默认情况下该方法的值为0,与图像传感器取景方向一致,竖向使用手机时只需要设置setDisplayOrientation(90)一般就可以解决预览问题
对于后置摄像头想要正常预览旋转角度应该设置为Camera默认角度减去屏幕朝向角度,这个值可能为负,角度值不能为负故需要加上360求正然后取余;对于前置摄像头拍照时可以明显发现预览图片是左右翻转的,也就是前置摄像头的预览成像是沿图像的中央垂直线翻转过来,形成镜面效果。所以前置摄像头求旋转角度为摄像头的默认角度加上手机屏幕旋转角度,之后进行镜面操作利用360度减去上面求到的值(翻转),然后加上360度(防止出现负数)之后对360度取余(预览是镜面的,但是最终拿到的data数据不是镜面翻转的,为保持和预览的一致性需要对图片进行镜面操作)。
如何计算setDisplayOrientation需要翻转的角度,每次重新预览或者SurfaceView变化都需要重新求角度:
public static int calculateCameraPreviewOrientation(Activity activity) {Camera.CameraInfo info = new Camera.CameraInfo();Camera.getCameraInfo(mCameraID, info);int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();int degrees = 0;switch (rotation) {case Surface.ROTATION_0:degrees = 0;break;case Surface.ROTATION_90:degrees = 90;break;case Surface.ROTATION_180:degrees = 180;break;case Surface.ROTATION_270:degrees = 270;break;}int result;if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {result = (info.orientation + degrees) % 360;result = (360 - result) % 360;} else {result = (info.orientation - degrees + 360) % 360;}mOrientation = result;return result;
}
setDisplayOrientation 只对预览有作用,不会影响其他数据。
info.orientation默认为90度,屏幕竖屏默认为0。
如果不设置预览角度:
设置了预览角度
2 拍照生成图片角度处理
利用Camera拍照时,读取图片数据存储为图片,取到的数据直接来源于图像传感器采集到的图像数据,预览由于setDisplayOrientation 正常但原始处于依然没有变化,所以保存的图片不与预览时看到的画面方向一致,而是与图像传感器的方向一致。竖着拍照得到的照片看起来是横向的,当横着拍照时,得到的照片看起来才是正常的。前置摄像头的预览有镜面效果,但从图像传感器采集到的数据却没有镜面效果,所以生成的图片需要镜面操作。
后置摄像头得到的图片,未经任何处理:
前置摄像头得到的图片未经任何处理:
上面得到的图片是Activity设置了竖向显示,然后手机屏幕0度(手机顶部在上)状态进行拍照。如果拍照过程中旋转手机,此时可以发现预览图片还是正常的但是保存的图片却发生了旋转。上一部分文章解决了预览图片的旋转角度问题,当拍照过程中旋转,预览可以很好地处理的,但拍摄生成的图片还是从原始图像传感器获取,它的方向会随手机位置的变化(特别是方向)而变化,所以需要对手机屏幕方向进行实时监听,然后对保存图片做相应操作。
手机顶部朝右拍摄:
利用sensor就可以实现对手机方向的判断,由于手机可能在任意角度,但是传感器返回的数值多种多样,但是最终利用传感器获取到的屏幕方向只能是0,90,180,270四种,所以需要有个阈值来确定手机方向。
判断手机旋转角度:
@Override
public void onSensorChanged(SensorEvent event) {//手机移动一段时间后静止,然后静止一段时间后进行对焦// 读取加速度传感器数值,values数组0,1,2分别对应x,y,z轴的加速度if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {int x = (int) event.values[0];int y = (int) event.values[1];int z = (int) event.values[2];}mSensorRotation = calculateSensorRotation(event.values[0],event.values[1]);
}public int calculateSensorRotation(float x, float y) {//x是values[0]的值,X轴方向加速度,从左侧向右侧移动,values[0]为负值;从右向左移动,values[0]为正值//y是values[1]的值,Y轴方向加速度,从上到下移动,values[1]为负值;从下往上移动,values[1]为正值//不考虑Z轴上的数据,if (Math.abs(x) > 6 && Math.abs(y) < 4) {if (x > 6) {return 270;} else {return 90;}} else if (Math.abs(y) > 6 && Math.abs(x) < 4) {if (y > 6) {return 0;} else {return 180;}}return -1;
}
保存图片时做的变换:
{final Bitmap result;if (data != null && data.length > 0) {Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);Matrix matrix = new Matrix();
//利用传感器获取当前屏幕方向对应角度 加上 开始预览是角度int rotation = (calculateCameraPreviewOrientation(Main23Activity.this) + mSensorRotation) % 360 ;if (mCameraID == Camera.CameraInfo.CAMERA_FACING_BACK) {
//如果是后置摄像头因为没有镜面效果直接旋转特定角度matrix.setRotate(rotation);} else {
//如果是前置摄像头需要做镜面操作,然后对图片做镜面postScale(-1, 1)//因为镜面效果需要360-rotation,才是前置摄像头真正的旋转角度rotation = (360 - rotation) % 360;matrix.setRotate(rotation);matrix.postScale(-1, 1);}result = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);saveBitmap(path+ "focusdemo2.jpg",result);} else {result = null;}}
}/*** 设置预览角度,setDisplayOrientation本身只能改变预览的角度* previewFrameCallback以及拍摄出来的照片是不会发生改变的,拍摄出来的照片角度依旧不正常的* 拍摄的照片需要自行处理*/
public int calculateCameraPreviewOrientation(Activity activity) {Camera.CameraInfo info = new Camera.CameraInfo();Camera.getCameraInfo(mCameraID, info);int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();int degrees = 0;switch (rotation) {case Surface.ROTATION_0:degrees = 0;break;case Surface.ROTATION_90:degrees = 90;break;case Surface.ROTATION_180:degrees = 180;break;case Surface.ROTATION_270:degrees = 270;break;}int result;if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {result = (info.orientation + degrees) % 360;result = (360 - result) % 360;} else {result = (info.orientation - degrees + 360) % 360;}System.out.println("=======info.orientation==========="+info.orientation + degrees);mOrientation = result;return result;
}
PS:处理保存图片的旋转也有人利用图片的ExifInterface 信息,首先读取图片被旋转的角度,然后再进行相应旋转。
但测试发现很多手机获取到的图片的ExifInterface的旋转信息为0(因为只要我们对图片进行了处理,类似压缩,图片的旋转角度就会恢复到默认0度),所以还是要利用上面的方式处理。
3 利用上面的公式的实例代码
public class Main23Activity extends AppCompatActivity implements SurfaceHolder.Callback ,SensorEventListener{private static int mOrientation = 0;private static int mCameraID = Camera.CameraInfo.CAMERA_FACING_BACK;private CustomSurfaceView mSurfaceView;private SurfaceHolder mSurfaceHolder;private Camera mCamera;private boolean havePermission = false;private Button btnFocus;private Button btnTakePic;private Button btnRestar;private Button btnChange;private LinearLayout mContainer;private int useWidth;private int useHeight;private SensorManager mSensorManager;private Sensor mSensor;private int mSensorRotation = 0;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main23);mContainer = findViewById(R.id.container);btnFocus = findViewById(R.id.focus);btnTakePic = findViewById(R.id.takepic);btnRestar = findViewById(R.id.restar);btnChange = findViewById(R.id.change);btnChange.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {switchCamera();}});btnRestar.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {/* if(mCamera != null){mCamera.startPreview();}*/// mSurfaceView.scrollBy(10,10);mContainer.animate().scaleX(0.4f).scaleY(0.7f);// mContainer.animate().rotation(50);}});btnFocus.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {if (mCamera != null && mCameraID == Camera.CameraInfo.CAMERA_FACING_BACK){mCamera.autoFocus(new Camera.AutoFocusCallback() {@Overridepublic void onAutoFocus(boolean success, Camera camera) {if(success){Toast.makeText(Main23Activity.this,"对焦成功",Toast.LENGTH_SHORT).show();}else{}}});}}});btnTakePic.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {if(mCamera!= null){mCamera.takePicture(null, null, new Camera.PictureCallback() {@Overridepublic void onPictureTaken(byte[] data, Camera camera) {// 获取Jpeg图片,并保存在sd卡上String path = Environment.getExternalStorageDirectory().getPath() +"/focus/";File pathDir = new File(path);if (!pathDir.exists()){pathDir.mkdir();}File pictureFile = new File(path+ "focusdemo.jpg");File pictureFile2 = new File(path+ "focusdemo2.jpg");if (pictureFile.exists()){pictureFile.delete();}if (pictureFile2.exists()){pictureFile2.delete();}try {FileOutputStream fos = new FileOutputStream(pictureFile);fos.write(data);fos.close();} catch (Exception e) {}{final Bitmap result;if (data != null && data.length > 0) {Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);Matrix matrix = new Matrix();int rotation = (calculateCameraPreviewOrientation(Main23Activity.this) + mSensorRotation) % 360 ;if (mCameraID == Camera.CameraInfo.CAMERA_FACING_BACK) {matrix.setRotate(rotation);} else {rotation = (360 - rotation) % 360;matrix.setRotate(rotation);matrix.postScale(-1, 1);}result = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);saveBitmap(path+ "focusdemo2.jpg",result);} else {result = null;}}}});}}});mSensorManager = (SensorManager) Main23Activity.this.getSystemService(Activity.SENSOR_SERVICE);mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);// 加速度// Android 6.0相机动态权限检查,省略了if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)== PackageManager.PERMISSION_GRANTED &&ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)== PackageManager.PERMISSION_GRANTED) {havePermission = true;init();} else {havePermission = false;ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.CAMERA,Manifest.permission.WRITE_EXTERNAL_STORAGE}, 100);}}public int calculateSensorRotation(float x, float y) {//x是values[0]的值,X轴方向加速度,从左侧向右侧移动,values[0]为负值;从右向左移动,values[0]为正值//y是values[1]的值,Y轴方向加速度,从上到下移动,values[1]为负值;从下往上移动,values[1]为正值//不考虑Z轴上的数据,if (Math.abs(x) > 6 && Math.abs(y) < 4) {if (x > 6) {return 270;} else {return 90;}} else if (Math.abs(y) > 6 && Math.abs(x) < 4) {if (y > 6) {return 0;} else {return 180;}}return -1;}@Overrideprotected void onStop() {super.onStop();mSensorManager.unregisterListener(this,mSensor);}public void releaseCamera(){if (mCamera != null) {mSurfaceHolder.removeCallback(this);mCamera.setPreviewCallback(null);mCamera.stopPreview();mCamera.lock();mCamera.release();mCamera = null;}}public void switchCamera(){if(mCameraID == Camera.CameraInfo.CAMERA_FACING_BACK){mCameraID = Camera.CameraInfo.CAMERA_FACING_FRONT;}else{mCameraID = Camera.CameraInfo.CAMERA_FACING_BACK;}try {initCamera();} catch (Exception e) {e.printStackTrace();}}public void init(){if(mSurfaceView == null){mSurfaceView = findViewById(R.id.surfaceview);mSurfaceView.setCustomEvent(new CustomSurfaceView.ONTouchEvent() {@Overridepublic void onTouchEvent(MotionEvent event) {handleFocus(event, mCamera);}});mSurfaceHolder = mSurfaceView.getHolder();mSurfaceHolder.addCallback(this);WindowManager wm = (WindowManager) Main23Activity.this.getSystemService(Context.WINDOW_SERVICE);int width = wm.getDefaultDisplay().getWidth();LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) mSurfaceView.getLayoutParams();layoutParams.width = width;layoutParams.height = width*4/3;useWidth = width;useHeight = width*4/3;mSurfaceView.setLayoutParams(layoutParams);}}private void initCamera() {if (mCamera != null){releaseCamera();System.out.println("===================releaseCamera=============");}mCamera = Camera.open(mCameraID);System.out.println("===================openCamera=============");if (mCamera != null){try {mCamera.setPreviewDisplay(mSurfaceHolder);} catch (IOException e) {e.printStackTrace();}Camera.Parameters parameters = mCamera.getParameters();parameters.setRecordingHint(true);{//设置获取数据parameters.setPreviewFormat(ImageFormat.NV21);//parameters.setPreviewFormat(ImageFormat.YUV_420_888);//通过setPreviewCallback方法监听预览的回调:mCamera.setPreviewCallback(new Camera.PreviewCallback() {@Overridepublic void onPreviewFrame(byte[] bytes, Camera camera) {//这里面的Bytes的数据就是NV21格式的数据,或者YUV_420_888的数据}});}if(mCameraID == Camera.CameraInfo.CAMERA_FACING_BACK){parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);}mCamera.setParameters(parameters);calculateCameraPreviewOrientation(this);Camera.Size tempSize = setPreviewSize(mCamera, useHeight,useWidth);{//此处可以处理,获取到tempSize,如果tempSize和设置的SurfaceView的宽高冲突,重新设置SurfaceView的宽高}setPictureSize(mCamera, useHeight,useWidth);mCamera.setDisplayOrientation(mOrientation);int degree = calculateCameraPreviewOrientation(Main23Activity.this);mCamera.setDisplayOrientation(degree);mCamera.startPreview();}}@Overridepublic void surfaceCreated(SurfaceHolder holder) {}@Overridepublic void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {//当SurfaceView变化时也需要做相应操作,这里未做相应操作if (havePermission){initCamera();}}@Overridepublic void surfaceDestroyed(SurfaceHolder holder) {mCamera.stopPreview();}private void setPictureSize(Camera camera ,int expectWidth,int expectHeight){Camera.Parameters parameters = camera.getParameters();Point point = new Point(expectWidth, expectHeight);Camera.Size size = findProperSize(point,parameters.getSupportedPreviewSizes());parameters.setPictureSize(size.width, size.height);camera.setParameters(parameters);}private Camera.Size setPreviewSize(Camera camera, int expectWidth, int expectHeight) {Camera.Parameters parameters = camera.getParameters();Point point = new Point(expectWidth, expectHeight);Camera.Size size = findProperSize(point,parameters.getSupportedPictureSizes());parameters.setPictureSize(size.width, size.height);camera.setParameters(parameters);return size;}/*** 找出最合适的尺寸,规则如下:* 1.将尺寸按比例分组,找出比例最接近屏幕比例的尺寸组* 2.在比例最接近的尺寸组中找出最接近屏幕尺寸且大于屏幕尺寸的尺寸* 3.如果没有找到,则忽略2中第二个条件再找一遍,应该是最合适的尺寸了*/private static Camera.Size findProperSize(Point surfaceSize, List<Camera.Size> sizeList) {if (surfaceSize.x <= 0 || surfaceSize.y <= 0 || sizeList == null) {return null;}int surfaceWidth = surfaceSize.x;int surfaceHeight = surfaceSize.y;List<List<Camera.Size>> ratioListList = new ArrayList<>();for (Camera.Size size : sizeList) {addRatioList(ratioListList, size);}final float surfaceRatio = (float) surfaceWidth / surfaceHeight;List<Camera.Size> bestRatioList = null;float ratioDiff = Float.MAX_VALUE;for (List<Camera.Size> ratioList : ratioListList) {float ratio = (float) ratioList.get(0).width / ratioList.get(0).height;float newRatioDiff = Math.abs(ratio - surfaceRatio);if (newRatioDiff < ratioDiff) {bestRatioList = ratioList;ratioDiff = newRatioDiff;}}Camera.Size bestSize = null;int diff = Integer.MAX_VALUE;assert bestRatioList != null;for (Camera.Size size : bestRatioList) {int newDiff = Math.abs(size.width - surfaceWidth) + Math.abs(size.height - surfaceHeight);if (size.height >= surfaceHeight && newDiff < diff) {bestSize = size;diff = newDiff;}}if (bestSize != null) {return bestSize;}diff = Integer.MAX_VALUE;for (Camera.Size size : bestRatioList) {int newDiff = Math.abs(size.width - surfaceWidth) + Math.abs(size.height - surfaceHeight);if (newDiff < diff) {bestSize = size;diff = newDiff;}}return bestSize;}private static void addRatioList(List<List<Camera.Size>> ratioListList, Camera.Size size) {float ratio = (float) size.width / size.height;for (List<Camera.Size> ratioList : ratioListList) {float mine = (float) ratioList.get(0).width / ratioList.get(0).height;if (ratio == mine) {ratioList.add(size);return;}}List<Camera.Size> ratioList = new ArrayList<>();ratioList.add(size);ratioListList.add(ratioList);}/*** 排序* @param list*/private static void sortList(List<Camera.Size> list) {Collections.sort(list, new Comparator<Camera.Size>() {@Overridepublic int compare(Camera.Size pre, Camera.Size after) {if (pre.width > after.width) {return 1;} else if (pre.width < after.width) {return -1;}return 0;}});}/*** 设置预览角度,setDisplayOrientation本身只能改变预览的角度* previewFrameCallback以及拍摄出来的照片是不会发生改变的,拍摄出来的照片角度依旧不正常的* 拍摄的照片需要自行处理*/public int calculateCameraPreviewOrientation(Activity activity) {Camera.CameraInfo info = new Camera.CameraInfo();Camera.getCameraInfo(mCameraID, info);int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();int degrees = 0;switch (rotation) {case Surface.ROTATION_0:degrees = 0;break;case Surface.ROTATION_90:degrees = 90;break;case Surface.ROTATION_180:degrees = 180;break;case Surface.ROTATION_270:degrees = 270;break;}int result;if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {result = (info.orientation + degrees) % 360;result = (360 - result) % 360;} else {result = (info.orientation - degrees + 360) % 360;}System.out.println("=======info.orientation==========="+info.orientation + degrees);mOrientation = result;return result;}@Overrideprotected void onResume() {super.onResume();mSensorManager.registerListener(this, mSensor,SensorManager.SENSOR_DELAY_NORMAL);if (havePermission && mCamera != null)mCamera.startPreview();}@Overrideprotected void onPause() {super.onPause();if (havePermission && mCamera != null)mCamera.stopPreview();}@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {super.onRequestPermissionsResult(requestCode, permissions, grantResults);switch (requestCode) {// 相机权限case 100:havePermission = true;init();break;}}/*** 转换对焦区域* 范围(-1000, -1000, 1000, 1000)*/private Rect calculateTapArea(float x, float y, int width, int height, float coefficient) {float focusAreaSize = 200;int areaSize = (int) (focusAreaSize * coefficient);int surfaceWidth = width;int surfaceHeight = height;int centerX = (int) (x / surfaceHeight * 2000 - 1000);int centerY = (int) (y / surfaceWidth * 2000 - 1000);int left = clamp(centerX - (areaSize / 2), -1000, 1000);int top = clamp(centerY - (areaSize / 2), -1000, 1000);int right = clamp(left + areaSize, -1000, 1000);int bottom = clamp(top + areaSize, -1000, 1000);return new Rect(left, top, right, bottom);}//不大于最大值,不小于最小值private int clamp(int x, int min, int max) {if (x > max) {return max;}if (x < min) {return min;}return x;}private void handleFocus(MotionEvent event, Camera camera) {int viewWidth = useWidth;int viewHeight = useHeight;Rect focusRect = calculateTapArea(event.getX(), event.getY(), viewWidth, viewHeight,1.0f);//一定要首先取消camera.cancelAutoFocus();Camera.Parameters params = camera.getParameters();if (params.getMaxNumFocusAreas() > 0) {List<Camera.Area> focusAreas = new ArrayList<>();focusAreas.add(new Camera.Area(focusRect, 800));params.setFocusAreas(focusAreas);} else {//focus areas not supported}//首先保存原来的对焦模式,然后设置为macro,对焦回调后设置为保存的对焦模式final String currentFocusMode = params.getFocusMode();params.setFocusMode(Camera.Parameters.FOCUS_MODE_MACRO);camera.setParameters(params);camera.autoFocus(new Camera.AutoFocusCallback() {@Overridepublic void onAutoFocus(boolean success, Camera camera) {//回调后 还原模式Camera.Parameters params = camera.getParameters();params.setFocusMode(currentFocusMode);camera.setParameters(params);if(success){Toast.makeText(Main23Activity.this,"对焦区域对焦成功",Toast.LENGTH_SHORT).show();}}});}@Overridepublic void onSensorChanged(SensorEvent event) {//手机移动一段时间后静止,然后静止一段时间后进行对焦// 读取加速度传感器数值,values数组0,1,2分别对应x,y,z轴的加速度if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {int x = (int) event.values[0];int y = (int) event.values[1];int z = (int) event.values[2];}mSensorRotation = calculateSensorRotation(event.values[0],event.values[1]);}@Overridepublic void onAccuracyChanged(Sensor sensor, int accuracy) {}public void saveBitmap(String filePath ,Bitmap mBitmap) {File f = new File(filePath);FileOutputStream fOut = null;try {fOut = new FileOutputStream(f);} catch (FileNotFoundException e) {e.printStackTrace();}mBitmap.compress(Bitmap.CompressFormat.PNG, 100, fOut);try {fOut.flush();} catch (IOException e) {e.printStackTrace();}try {fOut.close();} catch (IOException e) {e.printStackTrace();}}
}
后置摄像头拍摄的原图和 处理后的图
前置摄像头拍摄的原图和处理后的图: