在Android上做机器视觉,不可避免地都会用到OpenCV for Android库,视频流的捕获和预览通常使用 JavaCamera2View 控件,但这个控件在横屏下运作良好,竖屏下问题很多。

安卓手机只有在横屏下,其镜头传感器才是原始状态,竖屏需要进行旋转,否则看起来还是横的。

为了保持旋转不变性,最佳策略是采用正方形的预览区域,有些手机的镜头的尺寸不一定有正方形的输出尺寸,比如以vivo Y32为例,屏幕的分辨率是1600x720,镜头并没有720x720的输出,这种情况下我们可以找到一个最接近的960x720,然后将输出剪裁为720x720。

下面详细描述修改要点。

首先,修改 CameraBridgeViewBase.java,增加一个成员变量:

    // 正方形边长,大于0表示竖屏,将屏幕显示区域改为正方形,方便任意旋转
    public int mSideLengthSquare = 0;  // 0则为横屏,不需要额外处理

这个判断在 JavaCamera2View.java文件完成:

    protected boolean connectCamera(int width, int height) {
        Log.i(LOGTAG, "PreviewSize(" + width + "x" + height + ")");
        startBackgroundThread();
        initializeCamera();
        try {
            // 判断是否竖屏的逻辑
            if(width < height){  // 原始预览区域的宽度比高度小,说明是竖屏
                mSideLengthSquare = width;
            }
            ...
        }
        ...
    }

修改文件的CameraBridgeViewBase.java文件的calculateCameraFrameSize()函数:

        if(mSideLengthSquare > 0){  // 如果是竖屏
            // 找出最接近正方形边长的输出尺寸:
            int minDx = 1024*100;
            for (Object size : supportedSizes) {
                int width = accessor.getWidth(size);
                int height = accessor.getHeight(size);
                Log.d(TAG, "trying size 0: " + width + "x" + height);
                if(width >= mSideLengthSquare && height >= mSideLengthSquare){
                    int dx = (width - mSideLengthSquare) + (height - mSideLengthSquare);
                    if(dx < minDx){
                        calcWidth = (int) width;
                        calcHeight = (int) height;
                        minDx = dx;
                    }
                }
            }
        }
        else {
                ...
        }

注意在调用计算尺寸后,还要将缩放比例设为0,我们正方形区域没必要缩放,如果不设置下面这个语句,将会缩小显示:

mScale = 0;

修改CameraBridgeViewBase.java文件的AllocateCache()函数,如忘记修改将会崩溃:

    protected void AllocateCache()
    {
        Log.i(TAG, "AllocateCache size: " + mFrameWidth + "x" + mFrameHeight);
        // mCacheBitmap = Bitmap.createBitmap(mFrameWidth, mFrameHeight, Bitmap.Config.ARGB_8888);
        if(mSideLengthSquare > 0) {  // 如果是竖屏,则输出的是正方形预览
            mCacheBitmap = Bitmap.createBitmap(mSideLengthSquare, mSideLengthSquare, Bitmap.Config.ARGB_8888);
        }
        else{
            mCacheBitmap = Bitmap.createBitmap(mFrameWidth, mFrameHeight, Bitmap.Config.ARGB_8888);
        }
    }

然后是修改CameraBridgeViewBase.java文件的deliverAndDrawFrame()函数:

    protected void deliverAndDrawFrame(CvCameraViewFrame frame) {
        Mat modified;

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

        if(mSideLengthSquare > 0) {  // 将帧画面剪裁为正方形
            int w0 = modified.cols();
            int h0 = modified.rows();
            if (w0 != mSideLengthSquare || h0 != mSideLengthSquare) {
                int x0 = 0;
                int y0 = 0;
                if (w0 > mSideLengthSquare) {
                    x0 = (w0 - mSideLengthSquare) / 2;
                    w0 = mSideLengthSquare;
                }
                if (h0 > mSideLengthSquare) {
                    y0 = (h0 - mSideLengthSquare) / 2;
                    h0 = mSideLengthSquare;
                }
                org.opencv.core.Rect rect = new org.opencv.core.Rect(x0, y0, w0, h0);
                modified = new Mat(modified, rect);
            }
        }

        ...
    }

最后,则是你自己的C++代码了,别忘了将画面Mat旋转90度,这个是最容易的部分,就不贴代码了。

上述代码具有极大的通用性,不需要设置尺寸而取最大的正方形,是一个很好的策略。用很多手机测试过,效果很棒,CPU占用极小,可以保持30+的帧率不变。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐