将预览图像坐标映射到最终图像坐标

时间:2014-04-22 17:50:17

标签: android camera android-camera commonsware-cwac

我正在使用CommonsWare CWAC-Camera库(https://github.com/commonsguy/cwac-camera)来包装原生Android Camera API。

我想允许用户在拍照之前在预览图像上选择一个点,然后(准确地)在他们拍摄照片后在最终图像上挑选出相同的点。虽然这大致有效,但最终图像上的标记通常偏离目标约100-200像素。

首先,我通过在CameraView上设置OnTouchListener来捕获用户的原始选定点。然后我存储MotionEvent中的X和Y坐标。为了帮助进行视觉验证,我还会在拍摄照片之前立即在用户选择的坐标处绘制一个圆圈。

当用户拍摄照片时,我从SimpleCameraHost继承了覆盖saveImage,以便在将最终图像写入存储之前在最终图像上绘制相应的圆圈。为了计算在最终图像上绘制圆圈的位置,我正在执行以下操作:

  1. 创建与用于绘制预览的TextureView相同大小和形状的位图
  2. 在该位图上绘制一个圆圈,其坐标与屏幕上绘制的坐标相同
  3. 将标记位图缩放到最终图像大小(使用Matrix.ScaleToFit.Center)并将其叠加在实际最终照片图像上
  4. 然而,这还不够准确 - 至少,不是我正在测试的Nexus 7(2013)。我不确定在将标记添加到最终图像的位置计算中我缺少什么。

    演示此问题的简化示例如下:

    MainActivity.java:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    
        mCameraContainer = findViewById(R.id.camera_container);
        mCanvasView = (CanvasView)findViewById(R.id.canvas_view);
    
        FragmentManager fmgr = getFragmentManager();
        FragmentTransaction ft = fmgr.beginTransaction();
        mCameraFragment = (CameraFragment)fmgr.findFragmentByTag(CAMERA_FRAGMENT_TAG);
        if (null == mCameraFragment) {
            mCameraFragment = new CameraFragment();
            ft.add(R.id.camera_container,  mCameraFragment, CAMERA_FRAGMENT_TAG);
        }
        ft.commit();
    
        if (null == mCameraHost) {
            mCameraHost = new MyCameraHost(this);
        }
        mCameraFragment.setHost(mCameraHost);
    
        ViewTreeObserver ccObserver = mCameraContainer.getViewTreeObserver();
        ccObserver.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                CameraView cv = (CameraView)mCameraFragment.getView();
                View previewWidget = ((ViewGroup)cv).getChildAt(0);
                mCameraHost.setPreviewSize(previewWidget.getWidth(), previewWidget.getHeight());
    
                cv.setOnTouchListener(new OnTouchListener() {
                    @Override
                    public boolean onTouch(View v, MotionEvent e) {
                        mCanvasView.setPoint(e.getX(), e.getY());
                        mCameraHost.setPoint(e.getX(), e.getY());
                        return true;
                    }
                });
    
                mCameraContainer.getViewTreeObserver().removeOnGlobalLayoutListener(this);
            }
        });
    
        Button photoButton = (Button)findViewById(R.id.takePhotoButton);
        photoButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                mCameraFragment.takePicture();
            }
        });
    }
    

    MyCameraHost.java:

    @Override
    public void saveImage(PictureTransaction xact, byte[] image) {
        // decode the final image as a Bitmap
        BitmapFactory.Options opt = new BitmapFactory.Options();
        opt.inMutable = true;
        Bitmap imageBitmap = BitmapFactory.decodeByteArray(image, 0, image.length, opt);
    
        // draw a blank Bitmap with just the markup, using a canvas size equivalent to the preview view
        Bitmap markerBitmap = Bitmap.createBitmap(previewViewWidth, previewViewHeight, Bitmap.Config.ARGB_8888);
        Canvas c = new Canvas(markerBitmap);
        Paint p = new Paint();
        p.setStrokeWidth(10);
        p.setStyle(Style.FILL);
        p.setColor(Color.BLUE);
        c.drawCircle(previewMarkerX, previewMarkerY, 20, p);
    
        // scale the markup bitmap up to final-image size using Matrix.ScaleToFit.CENTER
        Matrix m = new Matrix();
        m.setRectToRect(new RectF(0, 0, previewViewWidth, previewViewHeight), new RectF(0, 0, imageBitmap.getWidth(), imageBitmap.getHeight()), Matrix.ScaleToFit.CENTER);
    
        // overlay the scaled marker Bitmap onto the image
        Canvas imageCanvas = new Canvas(imageBitmap);
        imageCanvas.drawBitmap(markerBitmap, m, null);
    
        // save the combined image
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        imageBitmap.compress(CompressFormat.JPEG, 70, bos);
        byte[] image2 = bos.toByteArray();
        super.saveImage(xact, image2);
    }
    

    布局XML:

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >
    
    <FrameLayout 
        android:id="@+id/camera_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
    
    <com.example.cwaccameratouchpoint.CanvasView 
        android:id="@+id/canvas_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@id/camera_container"
        android:layout_alignRight="@id/camera_container"
        android:layout_alignTop="@id/camera_container"
        android:layout_alignBottom="@id/camera_container" />        
    
    <Button
        android:id="@+id/takePhotoButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_alignParentRight="true"
        android:text="go" />
    
    </RelativeLayout>
    

    根据CommonsWare的评论,我更新了下面的saveImage以包含全出血偏移并拼出变换计算。这是一种改进,但仍然没有它应该的准确:

    // decode the final image as a Bitmap
    BitmapFactory.Options opt = new BitmapFactory.Options();
    opt.inMutable = true;
    Bitmap imageBitmap = BitmapFactory.decodeByteArray(image, 0, image.length, opt);
    int finalImageWidth = imageBitmap.getWidth();
    int finalImageHeight = imageBitmap.getHeight();
    
    // calculate selected point x and y values as applied to full-size final image
    // apply full-bleed-based offset, where xoffset and yoffset represent the
    // offset of the TextureView within its parent CameraView
    int bleedAdjustedX = (int)previewMarkerX - xoffset;
    int bleedAdjustedY = (int)previewMarkerY - yoffset;
    
    // calculate offset for change in aspect ratio
    // for now, assume portrait orientation only
    double finalImageAspectRatio = (double)finalImageHeight / (double)finalImageWidth;
    double aspectAdjustedWidth =  (double)previewViewHeight / finalImageAspectRatio;
    double aspectXOffset = (aspectAdjustedWidth - previewViewWidth) / 2;
    int aspectAdjustedX = bleedAdjustedX + (int)aspectXOffset;
    
    // scale adjusted coordinates for full-size image
    double normalizedAdjustedX = (double)aspectAdjustedX / aspectAdjustedWidth;
    double normalizedAdjustedY = (double)bleedAdjustedY / (double)previewViewHeight;
    double scaledX = normalizedAdjustedX * (double)finalImageWidth;
    double scaledY = normalizedAdjustedY * (double)finalImageHeight;
    
    // draw markup on final image
    Canvas c = new Canvas(imageBitmap);
    Paint p = new Paint();
    p.setStrokeWidth(10);
    p.setStyle(Style.FILL);
    p.setColor(Color.BLUE);
    c.drawCircle((float)scaledX, (float)scaledY, 20, p);
    
    // save marked-up image
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    imageBitmap.compress(CompressFormat.JPEG, 70, bos);
    byte[] image2 = bos.toByteArray();
    super.saveImage(xact, image2);
    

0 个答案:

没有答案