OpenCV相机方向问题

17

我有一个简单的项目,只需使用org.opencv.android.JavaCameraView显示相机。

我的问题是,默认情况下相机处于横屏模式,而我无法更改此设置,因为我需要定义CameraBridgeViewBase而不是常规相机意图。

以下是我的代码的一部分:

XML 代码:

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent" >

            <org.opencv.android.JavaCameraView
                android:layout_width="fill_parent"
                android:layout_height="300dp"
                android:visibility="gone"
                android:id="@+id/HelloOpenCvView"
                opencv:show_fps="true"
                opencv:camera_id="1" />


        </LinearLayout>  

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content" >


            <Button
                android:id="@+id/BtnVideo"
                android:layout_marginLeft="2dp"
                android:layout_marginRight="2dp"                    
                android:layout_width="0dp"
                style="@style/button"
                android:layout_height="wrap_content"
                android:layout_weight="1.00"
                android:text="@string/videoBtn"
                android:textSize="18dip" />


        </LinearLayout>   

Java 代码 :

 CameraBridgeViewBase mOpenCvCameraView;
    Button VideoButton;
 protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        overridePendingTransition(0, 0);

        VideoButton = (Button) this.findViewById(R.id.BtnVideo);

        VideoButton.setOnClickListener(onClickListener);

        mOpenCvCameraView= (CameraBridgeViewBase) findViewById(R.id.HelloOpenCvView);
        mOpenCvCameraView.setVisibility(SurfaceView.INVISIBLE);

    } 

        private OnClickListener onClickListener = new OnClickListener() {

            @Override
            public void onClick(View v) {
                    switch (v.getId()){

                        case R.id.BtnVideo:
                            if(mOpenCvCameraView.getVisibility() == SurfaceView.VISIBLE)
                            {
                                mOpenCvCameraView.setVisibility(SurfaceView.INVISIBLE);
                            }
                            else
                            {
                                mOpenCvCameraView.setVisibility(SurfaceView.VISIBLE);
                            }

                            break;
                        default :
                            break;
                    }

            }
        };


        public void onResume() {
            super.onResume();
            overridePendingTransition(0, 0);
            OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_2_4_3, this, mLoaderCallback);
        }
         public void onPause()
         {
             super.onPause();
             if (mOpenCvCameraView != null)
                 mOpenCvCameraView.disableView();
         }
         public void onDestroy() {
             super.onDestroy();
             if (mOpenCvCameraView != null)
                 mOpenCvCameraView.disableView();
         }
         public void onCameraViewStarted(int width, int height) {
         }

         public void onCameraViewStopped() {
         }
         public Mat onCameraFrame(CvCameraViewFrame inputFrame) {
             return inputFrame.rgba();
         }

        private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) {
            @Override
            public void onManagerConnected(int status) {
                switch (status) {
                    case LoaderCallbackInterface.SUCCESS:
                    {
                        //Log.i(TAG, "OpenCV loaded successfully");
                        mOpenCvCameraView.enableView();
                    } break;
                    default:
                    {
                        super.onManagerConnected(status);
                    } break;
                }
            }
        };

那我怎么可以更改默认方向呢?

谢谢!

7个回答

41

好的,我找到了这个解决方案:

首先我进入 JavaCameraView.java 类在 OpenCV Library - 2.4.5

然后在 initializeCamera() 函数中,在 mCamera.startPreview(); 之前添加了这两个函数:

            setDisplayOrientation(mCamera, 90);
            mCamera.setPreviewDisplay(getHolder());

第一个实现的函数如下:

protected void setDisplayOrientation(Camera camera, int angle){
    Method downPolymorphic;
    try
    {
        downPolymorphic = camera.getClass().getMethod("setDisplayOrientation", new Class[] { int.class });
        if (downPolymorphic != null)
            downPolymorphic.invoke(camera, new Object[] { angle });
    }
    catch (Exception e1)
    {
        e1.printStackTrace();
    }
}

我只是提醒一下,我正在使用OpenCV工作。

希望这能对某个人有所帮助。


4
使用您的代码后,它运行得非常好。但是,在我的实际安卓程序中,我在 onCameraFrame 中有一些代码来修改相机的 RGBA 输出。屏幕上的显示方式仍然没有变化。您知道原因吗?我尝试保存修改后的 MAT,在保存的文件中看起来很好,但只是屏幕预览是错误的。 - John Yang
2
我找到了另一个解决方案,您可以在这里看到: http://answers.opencv.org/question/20325/how-can-i-change-orientation-without-ruin-camera/ - user2235615
1
你可以使用我的解决方案,适用于每个OpenCV版本: http://answers.opencv.org/question/20325/how-can-i-change-orientation-without-ruin-camera/ - user2235615
1
它报错说似乎你的手机不支持相机或被阻止了! - Abid
3
@user2235615,您的代码可以运行,但问题在于以纵向方向时,框架没有充满整个屏幕,而且看起来过于拉伸(更宽)。或者在横向方向上无法正常工作。有没有其他解决方案? - UltimateDevil
显示剩余2条评论

10
我使用的是OpenCV 3.1版本。在CameraBridgeViewBase类的deliverAndDrawFrame方法中绘制位图时,我通过应用转换来解决了问题。希望这对您有所帮助。
在CameraBridgeViewBase.java文件中:
//I added new field
private final Matrix mMatrix = new Matrix();

//added updateMatrix method 
private void updateMatrix() {
    float hw = this.getWidth() / 2.0f;
    float hh = this.getHeight() / 2.0f;
    boolean isFrontCamera = Camera.CameraInfo.CAMERA_FACING_FRONT == mCameraIndex;
    mMatrix.reset();
    if (isFrontCamera) {
        mMatrix.preScale(-1, 1, hw, hh);
    }
    mMatrix.preTranslate(hw, hh);
    if (isFrontCamera)
        mMatrix.preRotate(270);
    else
        mMatrix.preRotate(90);
    mMatrix.preTranslate(-hw, -hh);
}

//then We need call updateMatrix on layout
@Override
public void layout(int l, int t, int r, int b) {
    super.layout(l, t, r, b);
    updateMatrix();
}

//I think we should also call updateMatrix on measure
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    updateMatrix();
}


//then We need update deliverAndDrawFrame
protected void deliverAndDrawFrame(CvCameraViewFrame frame) {
    //....Origin Code...

    //Set matrix before OpenCV draw bitmap
    int saveCount = canvas.save();
    canvas.setMatrix(mMatrix);

    //Begin OpenCv origin source
    if (mScale != 0) {
        canvas.drawBitmap(mCacheBitmap, new Rect(0,0,mCacheBitmap.getWidth(), mCacheBitmap.getHeight()),
             new Rect((int)((canvas.getWidth() - mScale*mCacheBitmap.getWidth()) / 2),
             (int)((canvas.getHeight() - mScale*mCacheBitmap.getHeight()) / 2),
             (int)((canvas.getWidth() - mScale*mCacheBitmap.getWidth()) / 2 + mScale*mCacheBitmap.getWidth()),
             (int)((canvas.getHeight() - mScale*mCacheBitmap.getHeight()) / 2 + mScale*mCacheBitmap.getHeight())), null);
    } else {
         canvas.drawBitmap(mCacheBitmap, new Rect(0,0,mCacheBitmap.getWidth(), mCacheBitmap.getHeight()),
             new Rect((canvas.getWidth() - mCacheBitmap.getWidth()) / 2,
             (canvas.getHeight() - mCacheBitmap.getHeight()) / 2,
             (canvas.getWidth() - mCacheBitmap.getWidth()) / 2 + mCacheBitmap.getWidth(),
             (canvas.getHeight() - mCacheBitmap.getHeight()) / 2 + mCacheBitmap.getHeight()), null);
    }
    //End OpenCv origin source

    //Restore canvas after draw bitmap
    canvas.restoreToCount(saveCount);

    //....Origin code...
}



//After that We can see that the camera preview is so small, the easiest way is change mScale Value (should we change mScale to "private" instead "protected" ?)
protected void deliverAndDrawFrame(CvCameraViewFrame frame) {
    //....Origin Code...

    //Set matrix before OpenCV draw bitmap to screen
    int saveCount = canvas.save();
    canvas.setMatrix(mMatrix);


    //Change mScale to "Aspect to fill"
    mScale = Math.max((float) canvas.getHeight() / mCacheBitmap.getWidth(), (float) canvas.getWidth() / mCacheBitmap.getHeight());

    //Begin OpenCv origin source
    if (mScale != 0) {
        canvas.drawBitmap(mCacheBitmap, new Rect(0,0,mCacheBitmap.getWidth(), mCacheBitmap.getHeight()),
             new Rect((int)((canvas.getWidth() - mScale*mCacheBitmap.getWidth()) / 2),
             (int)((canvas.getHeight() - mScale*mCacheBitmap.getHeight()) / 2),
             (int)((canvas.getWidth() - mScale*mCacheBitmap.getWidth()) / 2 + mScale*mCacheBitmap.getWidth()),
             (int)((canvas.getHeight() - mScale*mCacheBitmap.getHeight()) / 2 + mScale*mCacheBitmap.getHeight())), null);
    } else {
         canvas.drawBitmap(mCacheBitmap, new Rect(0,0,mCacheBitmap.getWidth(), mCacheBitmap.getHeight()),
             new Rect((canvas.getWidth() - mCacheBitmap.getWidth()) / 2,
             (canvas.getHeight() - mCacheBitmap.getHeight()) / 2,
             (canvas.getWidth() - mCacheBitmap.getWidth()) / 2 + mCacheBitmap.getWidth(),
             (canvas.getHeight() - mCacheBitmap.getHeight()) / 2 + mCacheBitmap.getHeight()), null);
    }
    //End OpenCv origin source

    //Restore canvas after draw bitmap
    canvas.restoreToCount(saveCount);

    //....Origin Code...
}

您可以在此处获取完整的源代码: https://gist.github.com/thongdoan/d73267eb58863f70c77d1288fe5cd3a4

8
谢谢您的贡献。我认为只指出您所做的更改比粘贴整个类会更有用。 - Jose Gómez
1
这个答案正在Meta上讨论。 - Script47
抱歉大家等了这么久才回复,我已经编辑了我的答案。 - Thong Doan
1
这个可以工作,但是对于我来说无法缩放相机以适应屏幕。 - mheavers
1
@Jesús - 好久不见了 - 我找到了适合我的解决方案,并在这里写了一个小教程:https://heartbeat.fritz.ai/working-with-the-opencv-camera-for-android-rotating-orienting-and-scaling-c7006c3e1916 - mheavers

10

问题是绘制代码没有检查相机参数。在 "CameraBridgeViewBase" 类的 "deliverAndDrawFrame" 函数中,Mat 在 Surface view 上被绘制。

通过对 "CameraBridgeViewBase" 类进行非常简单的修改,我们可以创建一个函数来旋转位图的绘制方式。

int userRotation= 0;

public void setUserRotation(int userRotation) {
    this.userRotation = userRotation;
}

/**
 * This method shall be called by the subclasses when they have valid
 * object and want it to be delivered to external client (via callback) and
 * then displayed on the screen.
 * @param frame - the current frame to be delivered
 */
protected void deliverAndDrawFrame(CvCameraViewFrame frame) {
    Mat modified;

    if (mListener != null) {
        modified = mListener.onCameraFrame(frame);
    } else {
        modified = frame.rgba();
    }

    boolean bmpValid = true;
    if (modified != null) {
        try {
            Utils.matToBitmap(modified, mCacheBitmap);
        } catch(Exception e) {
            Log.e(TAG, "Mat type: " + modified);
            Log.e(TAG, "Bitmap type: " + mCacheBitmap.getWidth() + "*" + mCacheBitmap.getHeight());
            Log.e(TAG, "Utils.matToBitmap() throws an exception: " + e.getMessage());
            bmpValid = false;
        }
    }

    if (bmpValid && mCacheBitmap != null) {
        Canvas canvas = getHolder().lockCanvas();
        if (canvas != null) {
            canvas.drawColor(Color.parseColor("#8BC34A"), PorterDuff.Mode.SRC_IN);
 //this is the rotation part
            canvas.save();
            canvas.rotate(userRotation,  (canvas.getWidth()/ 2),(canvas.getHeight()/ 2));

            if (mScale != 0) {
                canvas.drawBitmap(mCacheBitmap, new Rect(0,0,mCacheBitmap.getWidth(), mCacheBitmap.getHeight()),
                     new Rect((int)((canvas.getWidth() - mScale*mCacheBitmap.getWidth()) / 2),
                     (int)((canvas.getHeight() - mScale*mCacheBitmap.getHeight()) / 2),
                     (int)((canvas.getWidth() - mScale*mCacheBitmap.getWidth()) / 2 + mScale*mCacheBitmap.getWidth()),
                     (int)((canvas.getHeight() - mScale*mCacheBitmap.getHeight()) / 2 + mScale*mCacheBitmap.getHeight())), null);
            } else {
                 canvas.drawBitmap(mCacheBitmap, new Rect(0,0,mCacheBitmap.getWidth(), mCacheBitmap.getHeight()),
                     new Rect((canvas.getWidth() - mCacheBitmap.getWidth()) / 2,
                     (canvas.getHeight() - mCacheBitmap.getHeight()) / 2,
                     (canvas.getWidth() - mCacheBitmap.getWidth()) / 2 + mCacheBitmap.getWidth(),
                     (canvas.getHeight() - mCacheBitmap.getHeight()) / 2 + mCacheBitmap.getHeight()), null);
            }

            if (mFpsMeter != null) {
                mFpsMeter.measure();
                mFpsMeter.draw(canvas, 20, 30);
            }
//remember to restore the canvas 
            canvas.restore();
            getHolder().unlockCanvasAndPost(canvas);
        }
    }
}

我尝试过最常见的解决方案,使用Core.flip函数旋转Mat但会消耗大量资源,这个解决方案不会影响检测和性能,只会改变图像在画布上绘制的方式。

希望这可以帮到你。


1
这对我非常有效,但不会考虑旋转后需要发生的任何画布调整大小。 - mheavers
它可以工作,但预览不是全屏显示。 - Rohaitas Tanoli

9

在你的onCameraFrame方法中尝试这个。

mRgba = inputFrame.rgba();
Mat mRgbaT = mRgba.t();
Core.flip(mRgbaT, matRgbaFlip, 1);
Imgproc.resize(matRgbaFlip, matRgbaFlip, mRgba.size());
mRgbaT.release();
return matRgbaFlip;

编辑:修复内存泄漏,释放在该方法中创建的Mat,并将另一个作为全局变量移动。


3
这个功能可行,但几秒钟后会关闭应用程序。不确定原因,在控制台中没有看到任何致命错误。 - Abhinav Raja
1
应用程序崩溃可能是由于内存泄漏引起的。在返回之前必须释放mRgba。此外,将mRgbaT创建为全局变量解决了问题。 - donmezburak

1
首先,不要从基类创建实例,而是从扩展类获取实例。
//CameraBridgeViewBase mOpenCvCameraView;
JavaCameraView mOpenCvCameraView;

值得一提的是,CameraBridgeViewBase.java已经有一个Surface Holder,因此让我们使用它来替代创建Surface Texture。
因此,其次,在initializeCamera()函数中编辑JavaCameraView.java,通过用Surface Holder替换Surface Texture。
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {

    //mSurfaceTexture = new SurfaceTexture(MAGIC_TEXTURE_ID);
    //mCamera.setPreviewTexture(mSurfaceTexture);

    mCamera.setPreviewDisplay(getHolder());

} else
    mCamera.setPreviewDisplay(null);

最后一步是在不添加任何特殊函数的情况下设置方向。在同一个函数initializeCamera()中,在调用startPreview()之前调用setDisplayOrientation(degrees)。
/* Finally we are ready to start the preview */
Log.d(TAG, "startPreview");
mCamera.setDisplayOrientation(90);
mCamera.startPreview();

2
这个可以运行,但它会停止物体检测器的工作。 - qed

0

在OpenCV 3.4.3版本中

我通过在JavaCameraView.java类的initializeCamera()方法中进行以下更改来修复它。

setDisplayOrientation(mCamera, 0);

操作步骤:

  1. 打开JavaCameraView.java文件。
  2. 搜索“setDisplayOrientation”。
  3. 将方向角度设置为0(默认值为90)。

在3.4.16版本中对我无效。 - Rohaitas Tanoli

-9

在 AndroidManifest.xml 文件中,android:screenOrientation 属性的值应该有所帮助。

android:screenOrientation="portrait"


4
他询问的是相机的方向,而不是屏幕的方向。 - Bennyz
我也遇到了同样的问题,相机一直处于竖屏模式。我不得不手动更改方向才能解决问题。 - patel deven

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接