如何在Android中提高Camera 2的图像质量?

时间:2017-01-28 14:09:47

标签: android android-camera android-camera2

我使用谷歌示例创建Android相机应用程序。 在几天内我无法解决输出图像质量问题。这是捕获照片/视频的基本片段。

public class Camera2BaseFragment extends Fragment implements View.OnTouchListener {

   protected static final SparseIntArray DEFAULT_ORIENTATIONS = new SparseIntArray();

   protected final String FRAGMENT_DIALOG = "dialog";
   protected Integer[] mExposureCompensation = {};
   protected String mCameraId = "0";
   protected float mFingerSpacing = 0f;
   protected double mZoomLevel = 1;
   protected int mCurrentProgress = 0;
   protected int mSensorOrientation;
   protected ImageView mToolbarBackIcon;
   protected TextView mTvZoomLevel;
   protected Rect mZoomRect;
   protected View mView;
   protected Camera2FitTextureView mTextureView;
   protected CameraCaptureSession mCaptureSession;
   protected CameraDevice mCameraDevice;
   protected Size mPreviewSize;
   protected HandlerThread mBackgroundThread;
   protected Handler mBackgroundHandler;
   protected CaptureRequest.Builder mPreviewRequestBuilder;
   protected Semaphore mCameraOpenCloseLock = new Semaphore(1);
   private CameraCharacteristics mCharacteristics;
   private float mRatio = 1.0f;
   @Override
   public View onCreateView(LayoutInflater inflater, ViewGroup container,
                            Bundle savedInstanceState) {
       return inflater.inflate(R.layout.fragment_camera2, container, false);
   }

   @Override
   public boolean onTouch(View v, MotionEvent event) {
       try {
           mCharacteristics = MainFragment.getCameraCharacteristics(getActivity());
           if (mCharacteristics == null) return true;
           if (mCharacteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM) == null)
               return true;
           float maxZoom = mCharacteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM) * 10;
           Rect m = mCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
           if (m == null) return true;
           float current_finger_spacing;
           if (event.getPointerCount() > 1) {
               // Multi touch logic
               current_finger_spacing = getFingerSpacing(event);
               if (mFingerSpacing != 0) {
                   if (current_finger_spacing > mFingerSpacing && maxZoom > mZoomLevel) {
                       //mZoomLevel++;
                       mZoomLevel = mZoomLevel + .5;
                   } else if (current_finger_spacing < mFingerSpacing && mZoomLevel > 1) {
                       //mZoomLevel--;
                       mZoomLevel = mZoomLevel - .5;
                   }
                   int minW = (int) (m.width() / maxZoom);
                   int minH = (int) (m.height() / maxZoom);
                   int difW = m.width() - minW;
                   int difH = m.height() - minH;
                   int cropW = difW / 100 * (int)mZoomLevel;
                   int cropH = difH / 100 * (int)mZoomLevel;
                   cropW -= cropW & 3;
                   cropH -= cropH & 3;
                   mZoomRect = new Rect(cropW, cropH, m.width() - cropW, m.height() - cropH);
                   mPreviewRequestBuilder.set(CaptureRequest.SCALER_CROP_REGION, mZoomRect);
                   mRatio = (int)mZoomLevel >= 1 ? ((float)mZoomLevel / 10) : (float) mZoomLevel;
                   setZoomLevelText(mRatio);
               }
               mFingerSpacing = current_finger_spacing;
               mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), null, null);
           } else {
               // Single touch logic
           }
       } catch (CameraAccessException | NullPointerException e) {
           //..
       }
       return true;
   }

   private void setZoomLevelText(float ratio) {
       if(ratio <= 1.0f) ratio = 1.0f;
       mTvZoomLevel.setText(String.format(Locale.US, "%.1fX", ratio));
   }

   private float getFingerSpacing(MotionEvent event) {
       float x = event.getX(0) - event.getX(1);
       float y = event.getY(0) - event.getY(1);
       return (float) Math.sqrt(x * x + y * y);
   }

   static class CompareSizesByArea implements Comparator<Size> {
       @Override
       public int compare(Size lhs, Size rhs) {
           return Long.signum((long) lhs.getWidth() * lhs.getHeight() -
                   (long) rhs.getWidth() * rhs.getHeight());
       }
   }
}

My Fragment for capture Photo

public class Camera2PhotoFragment extends Camera2BaseFragment
       implements View.OnClickListener, ActivityCompat.OnRequestPermissionsResultCallback {

   private static final int STATE_PREVIEW = 0;
   private static final int STATE_WAITING_LOCK = 1;
   private static final int STATE_WAITING_PRECAPTURE = 2;
   private static final int STATE_WAITING_NON_PRECAPTURE = 3;
   private static final int STATE_PICTURE_TAKEN = 4;
   private static final int MAX_PREVIEW_WIDTH = 1920;
   private static final int MAX_PREVIEW_HEIGHT = 1080;
   private ImageReader mImageReader;
   private File mFile = new File("ImagePath");
   private final ImageReader.OnImageAvailableListener mOnImageAvailableListener
           = new ImageReader.OnImageAvailableListener() {
       @Override
       public void onImageAvailable(ImageReader reader) {
           mBackgroundHandler.post(new ImageSaver(reader.acquireLatestImage(), mFile,
                   mOnImageSavedListener));
       }
   };
   private int mState = STATE_PREVIEW;
   private boolean isEnabledCameraImg = true;
   private final OnImageSavedListener mOnImageSavedListener = new OnImageSavedListener() {
       @Override
       public void onImageSavedSuccessfully() {
           // Do something with saved image
           isEnabledCameraImg = true;
       }
   };
   private CameraCaptureSession.CaptureCallback mCaptureCallback
           = new CameraCaptureSession.CaptureCallback() {

       private void process(CaptureResult result) {
           switch (mState) {
               case STATE_PREVIEW: {
                   break;
               }
               case STATE_WAITING_LOCK: {
                   Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);
                   if (afState == null) {
                       captureStillPicture();
                   } else if (CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED == afState ||
                           CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED == afState) {
                       Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
                       if (aeState == null ||
                               aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) {
                           mState = STATE_PICTURE_TAKEN;
                           captureStillPicture();
                       } else {
                           runPrecaptureSequence();
                       }
                   } else {
                       mState = STATE_PICTURE_TAKEN;
                       captureStillPicture();
                   }
                   break;
               }
               case STATE_WAITING_PRECAPTURE: {
                   Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
                   if (aeState == null ||
                           aeState == CaptureResult.CONTROL_AE_STATE_PRECAPTURE ||
                           aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED) {
                       mState = STATE_WAITING_NON_PRECAPTURE;
                   }
                   break;
               }
               case STATE_WAITING_NON_PRECAPTURE: {
                   Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
                   if (aeState == null || aeState != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) {
                       mState = STATE_PICTURE_TAKEN;
                       captureStillPicture();
                   }
                   break;
               }
           }
       }

       @Override
       public void onCaptureProgressed(@NonNull CameraCaptureSession session,
                                       @NonNull CaptureRequest request,
                                       @NonNull CaptureResult partialResult) {
           process(partialResult);
       }

       @Override
       public void onCaptureCompleted(@NonNull CameraCaptureSession session,
                                      @NonNull CaptureRequest request,
                                      @NonNull TotalCaptureResult result) {
           process(result);
       }
   };
   private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
       @Override
       public void onOpened(@NonNull CameraDevice cameraDevice) {
           mCameraOpenCloseLock.release();
           mCameraDevice = cameraDevice;
           createCameraPreviewSession();
       }
   };
   private final TextureView.SurfaceTextureListener mSurfaceTextureListener
           = new TextureView.SurfaceTextureListener() {
       @Override
       public void onSurfaceTextureAvailable(SurfaceTexture texture, int width, int height) {
           openCamera(width, height);
       }
       @Override
       public void onSurfaceTextureSizeChanged(SurfaceTexture texture, int width, int height) {
           configureTransform(width, height);
       }
   };

   public static Camera2PhotoFragment newInstance() {
       return new Camera2PhotoFragment();
   }

   @Override
   public View onCreateView(LayoutInflater inflater, ViewGroup container,
                            Bundle savedInstanceState) {
       super.onCreateView(inflater, container, savedInstanceState);
       return mView;
   }

   @Override
   public void onResume() {
       super.onResume();
       isEnabledCameraImg = true;
       if (mTextureView.isAvailable()) {
           openCamera(mTextureView.getWidth(), mTextureView.getHeight());
       } else {
           mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
       }
   }

   @Override
   public void onClick(View view) {
       switch (view.getId()) {
           case R.id.imgCameraTakePicture: {
                   takePicture();
               break;
           }
       }
   }

   private static Size chooseOptimalSize(Size[] choices, int textureViewWidth,
                                         int textureViewHeight, int maxWidth, int maxHeight, Size aspectRatio) {
       List<Size> bigEnough = new ArrayList<>();
       List<Size> notBigEnough = new ArrayList<>();
       int w = aspectRatio.getWidth();
       int h = aspectRatio.getHeight();
       for (Size option : choices) {
           if (option.getWidth() <= maxWidth && option.getHeight() <= maxHeight &&
                   option.getHeight() == option.getWidth() * h / w) {
               if (option.getWidth() >= textureViewWidth &&
                       option.getHeight() >= textureViewHeight) {
                   bigEnough.add(option);
               } else {
                   notBigEnough.add(option);
               }
           }
       }
       if (bigEnough.size() > 0) {
           return Collections.min(bigEnough, new CompareSizesByArea());
       } else if (notBigEnough.size() > 0) {
           return Collections.max(notBigEnough, new CompareSizesByArea());
       } else {
           return choices[0];
       }
   }

   private void setUpCameraOutputs(int width, int height) {
       Activity activity = getActivity();
       CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
       try {
           for (String cameraId : manager.getCameraIdList()) {
               CameraCharacteristics characteristics
                       = manager.getCameraCharacteristics(cameraId);
               Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);
               if (facing != null && facing == CameraCharacteristics.LENS_FACING_FRONT) {
                   continue;
               }
               StreamConfigurationMap map = characteristics.get(
                       CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
               if (map == null) {
                   continue;
               }
               Size largest = Collections.max(
                       Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)),
                       new CompareSizesByArea());
               mImageReader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(),
                       ImageFormat.JPEG, /*maxImages*/2);
               mImageReader.setOnImageAvailableListener(
                       mOnImageAvailableListener, mBackgroundHandler);
               int displayRotation = activity.getWindowManager().getDefaultDisplay().getRotation();
               mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
               boolean swappedDimensions = false;
               switch (displayRotation) {
                   case Surface.ROTATION_0: {
                       // do nothing
                   }
                   case Surface.ROTATION_180: {
                       if (mSensorOrientation == 90 || mSensorOrientation == 270) {
                           swappedDimensions = true;
                       }
                       break;
                   }
                   case Surface.ROTATION_90: {
                       // do nothing
                   }
                   case Surface.ROTATION_270: {
                       if (mSensorOrientation == 0 || mSensorOrientation == 180) {
                           swappedDimensions = true;
                       }
                       break;
                   }
               }

               Point displaySize = new Point();
               activity.getWindowManager().getDefaultDisplay().getSize(displaySize);
               int rotatedPreviewWidth = width;
               int rotatedPreviewHeight = height;
               int maxPreviewWidth = displaySize.x;
               int maxPreviewHeight = displaySize.y;
               if (swappedDimensions) {
                   rotatedPreviewWidth = height;
                   rotatedPreviewHeight = width;
                   maxPreviewWidth = displaySize.y;
                   maxPreviewHeight = displaySize.x;
               }
               if (maxPreviewWidth > MAX_PREVIEW_WIDTH) {
                   maxPreviewWidth = MAX_PREVIEW_WIDTH;
               }
               if (maxPreviewHeight > MAX_PREVIEW_HEIGHT) {
                   maxPreviewHeight = MAX_PREVIEW_HEIGHT;
               }
               mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class),
                       rotatedPreviewWidth, rotatedPreviewHeight, maxPreviewWidth,
                       maxPreviewHeight, largest);
               int orientation = getResources().getConfiguration().orientation;
               if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
                   mTextureView.setAspectRatio(
                           mPreviewSize.getWidth(), mPreviewSize.getHeight());
               } else {
                   mTextureView.setAspectRatio(
                           mPreviewSize.getHeight(), mPreviewSize.getWidth());
               }
               mCameraId = cameraId;
               return;
           }
       } catch (CameraAccessException e) {
           //..
       }
   }

   private void openCamera(int width, int height) {
       setUpCameraOutputs(width, height);
       configureTransform(width, height);
       Activity activity = getActivity();
       CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
       try {
           if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
               throw new RuntimeException("Time out waiting to lock camera opening.");
           }
           if (ActivityCompat.checkSelfPermission(getContext(), Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
               ActivityCompat.requestPermissions(getActivity(), new String[]{Manifest.permission.CAMERA}, REQUEST_OPEN_CAMERA);
               return;
           }
           manager.openCamera(mCameraId, mStateCallback, mBackgroundHandler);
       } catch (CameraAccessException | InterruptedException e) {
           e.printStackTrace();
       }
   }

   private void closeCamera() {
       //...
   }

   private void createCameraPreviewSession() {
       try {
           SurfaceTexture texture = mTextureView.getSurfaceTexture();
           assert texture != null;
           texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
           Surface surface = new Surface(texture);
           mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
           mPreviewRequestBuilder.addTarget(surface);
           if (mZoomRect != null) {
               mPreviewRequestBuilder.set(CaptureRequest.SCALER_CROP_REGION, mZoomRect);
           }
           mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()),
                   new CameraCaptureSession.StateCallback() {

                       @Override
                       public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
                           if (null == mCameraDevice) {
                               return;
                           }
                           mCaptureSession = cameraCaptureSession;
                           try {
                               mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
                                       CaptureRequest.CONTROL_AF_MODE_AUTO);
                               mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(),
                                       mCaptureCallback, mBackgroundHandler);
                           } catch (CameraAccessException e) {
                               e.printStackTrace();
                           }
                       }

                       @Override
                       public void onConfigureFailed(
                               @NonNull CameraCaptureSession cameraCaptureSession) {
                          //...
                       }
                   }, null
           );
       } catch (CameraAccessException e) {
           //..
       }
   }

   private void configureTransform(int viewWidth, int viewHeight) {
       Activity activity = getActivity();
       if (null == mTextureView || null == mPreviewSize || null == activity) {
           return;
       }
       int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
       Matrix matrix = new Matrix();
       RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
       RectF bufferRect = new RectF(0, 0, mPreviewSize.getHeight(), mPreviewSize.getWidth());
       float centerX = viewRect.centerX();
       float centerY = viewRect.centerY();
       if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {
           bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
           matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
           float scale = Math.max(
                   (float) viewHeight / mPreviewSize.getHeight(),
                   (float) viewWidth / mPreviewSize.getWidth());
           matrix.postScale(scale, scale, centerX, centerY);
           matrix.postRotate(90 * (rotation - 2), centerX, centerY);
       } else if (Surface.ROTATION_180 == rotation) {
           matrix.postRotate(180, centerX, centerY);
       }
       mTextureView.setTransform(matrix);
   }

   private void takePicture() {
       lockFocus();
   }

   private void lockFocus() {
       try {
           mState = STATE_WAITING_LOCK;
           mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback, mBackgroundHandler);
       } catch (CameraAccessException e) {
           //..
       }
   }

   private void runPrecaptureSequence() {
       try {
           mState = STATE_WAITING_PRECAPTURE;
           mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback,
                   mBackgroundHandler);
       } catch (CameraAccessException | NullPointerException e) {
           //..
       }
   }

   private void captureStillPicture() {
       try {
           final Activity activity = getActivity();
           if (null == activity || null == mCameraDevice) {
               return;
           }
           final CaptureRequest.Builder captureBuilder =
                   mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
           captureBuilder.addTarget(mImageReader.getSurface());
           if (mZoomRect != null) captureBuilder.set(CaptureRequest.SCALER_CROP_REGION, mZoomRect);
           if (mCurrentProgress != 0) {
               captureBuilder.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, mExposureCompensation[mCurrentProgress]);
           }
           captureBuilder.set(CaptureRequest.CONTROL_AF_MODE, CameraMetadata.CONTROL_AF_MODE_AUTO);
           // Orientation
           int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
           captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientation(rotation));
           captureBuilder.set(CaptureRequest.JPEG_QUALITY, (byte) 100);
           CameraCaptureSession.CaptureCallback CaptureCallback
                   = new CameraCaptureSession.CaptureCallback() {

               @Override
               public void onCaptureCompleted(@NonNull CameraCaptureSession session,
                                              @NonNull CaptureRequest request,
                                              @NonNull TotalCaptureResult result) {
                   isEnabledCameraImg = true;
               }
           };

           mCaptureSession.stopRepeating();
           mCaptureSession.capture(captureBuilder.build(), CaptureCallback, null);
           unlockFocus();
           if (mZoomRect != null)
               mPreviewRequestBuilder.set(CaptureRequest.SCALER_CROP_REGION, mZoomRect);
           mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
                   CaptureRequest.CONTROL_AF_MODE_AUTO);
           mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(),
                   mCaptureCallback, mBackgroundHandler);
       } catch (Throwable e) {
           //..
       }
   }

   private int getOrientation(int rotation) {
       return (DEFAULT_ORIENTATIONS.get(rotation) + mSensorOrientation + 270) % 360;
   }

   private void unlockFocus() {
       try {
           mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
                   CameraMetadata.CONTROL_AF_TRIGGER_IDLE);
           mState = STATE_PREVIEW;
           mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), mCaptureCallback,
                   mBackgroundHandler);
       } catch (Throwable e) {
           //..
       }
   }

   interface OnImageSavedListener {
       void onImageSavedSuccessfully();
   }

   private static class ImageSaver implements Runnable {

       private final Image mImage;
       private final File mFile;
       private final OnImageSavedListener mOnImageSavedListener;

       public ImageSaver(Image image, File file, OnImageSavedListener onImageSavedListener) {
           mImage = image;
           mFile = file;
           mOnImageSavedListener = onImageSavedListener;
       }

       @Override
       public void run() {
           ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
           byte[] bytes = new byte[buffer.remaining()];
           buffer.get(bytes);
           FileOutputStream output = null;
           try {
               output = new FileOutputStream(mFile);
               output.write(bytes);
               mOnImageSavedListener.onImageSavedSuccessfully();
           } catch (IOException e) {
               //..
           } finally {
               mImage.close();
               if (null != output) {
                   try {
                       output.close();
                   } catch (IOException e) {
                       //..
               }
           }
       }
   }
}

相机预览和保存的图像之间存在图像质量差异。 enter image description here 在左侧:相机预览的屏幕截图(带缩放) 在右侧:捕获/保存的图像。左侧的图像更好。

如何为输出图像获得良好的图像质量?可能是缩放问题吗?

我在相机2文档中发现了CaptureRequest的以下常量:

captureBuilder.set(CaptureRequest.EDGE_MODE, CameraMetadata.EDGE_MODE_HIGH_QUALITY);
captureBuilder.set(CaptureRequest.SHADING_MODE, CameraMetadata.SHADING_MODE_HIGH_QUALITY);
captureBuilder.set(CaptureRequest.TONEMAP_MODE, CameraMetadata.TONEMAP_MODE_HIGH_QUALITY);
captureBuilder.set(CaptureRequest.COLOR_CORRECTION_ABERRATION_MODE, CameraMetadata.COLOR_CORRECTION_ABERRATION_MODE_HIGH_QUALITY);
captureBuilder.set(CaptureRequest.COLOR_CORRECTION_MODE, CameraMetadata.COLOR_CORRECTION_ABERRATION_MODE_HIGH_QUALITY);
captureBuilder.set(CaptureRequest.HOT_PIXEL_MODE, CameraMetadata.HOT_PIXEL_MODE_HIGH_QUALITY);
captureBuilder.set(CaptureRequest.NOISE_REDUCTION_MODE, CameraMetadata.NOISE_REDUCTION_MODE_HIGH_QUALITY);
captureBuilder.set(CaptureRequest.LENS_OPTICAL_STABILIZATION_MODE, CameraMetadata.LENS_OPTICAL_STABILIZATION_MODE_ON);

为了测试,我在代码的代码行下面添加了所有这些常量: captureBuilder.set(CaptureRequest.JPEG_QUALITY,(byte)100);

但这并没有解决我的问题。

有人在相机2上有类似的图像质量问题,或者有人知道这个问题的原因吗?

感谢您的回复!

如果需要,我可以在github上传我的代码。

0 个答案:

没有答案