在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+的帧率不变。