1. 程式人生 > >android中使用OpenCV之呼叫裝置攝像頭

android中使用OpenCV之呼叫裝置攝像頭

使用opencv去訪問android裝置攝像頭, C++庫是無法獲取到android裝置硬體的,所有需要藉助Opencv對android提供的java庫進行訪問android裝置攝像頭。在opencv官方下載AndroidSDK,匯入專案中就可以使用了。

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width
="match_parent" android:layout_height="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto">
<!--JavaCameraView最終繼承自SurfaceView,顯示攝像頭幀資料的展示--> <org.opencv.android.JavaCameraView android:id="@+id/cameraView" android:layout_width="match_parent" android:layout_height
="match_parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintTop_toTopOf="parent" app:camera_id="back"//後置攝像頭 />
</android.support.constraint.ConstraintLayout
>

JavaCameraView繼承自CameraBridgeViewBase,CameraBridgeViewBase又繼承自SurfaceView,JavaCameraView是可以顯示攝像頭捕獲到的幀資料的View,CameraBridgeViewBase類中CvCameraViewListener2介面提供了攝像頭onCameraViewStarted、onCameraViewStopped以及onCameraFrame回撥。我們要對攝像頭捕獲的每一幀資料進行操作就需要再OnCameraFrame回撥中進行處理。通過一個native函式,在將攝像頭的每一幀資料地址傳給C++,對幀資料進行操作。程式碼如下:

public class CameraOpenCVActivity extends AppCompatActivity implements CameraBridgeViewBase.CvCameraViewListener2 {//
    private JavaCameraView cameraView;
    private Mat rgba;
    static {
        System.loadLibrary("native-lib");
    }
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_camera_opencv);
        cameraView = findViewById(R.id.cameraView);
        cameraView.setCvCameraViewListener(this);
        //許可權請求
        RxPermissions rxPermission = new RxPermissions(this);
        rxPermission
                .requestEachCombined(Manifest.permission.CAMERA,
                        Manifest.permission.READ_EXTERNAL_STORAGE)
                .subscribe(permission -> {
                    if (permission.granted) {
                    } else if (permission.shouldShowRequestPermissionRationale) {
                    } else {
                    }
                });
    }
    //NDK對每一幀資料進行操作
    public static native void nativeRgba(long jrgba);

    /**
     *  當攝像機預覽開始時,這個方法就會被呼叫。在呼叫該方法之後,框架將通過onCameraFrame()回撥向客戶端傳送。
     *
     * @param width  - 幀的寬度
     * @param height - 幀的高度
     */
    @Override
    public void onCameraViewStarted(int width, int height) {
        //定義Mat物件
        rgba = new Mat(width, height, CvType.CV_8UC4);
    }

    /**
     * 當攝像機預覽由於某種原因被停止時,這個方法就會被呼叫。
     *在呼叫這個方法之後,不會通過onCameraFrame()回撥來傳遞任何幀。
     */
    @Override
    public void onCameraViewStopped() {
        rgba.release();
    }

    /**
     * 當需要完成框架的交付時,將呼叫此方法。
     *返回值-是一個修改後的幀,需要在螢幕上顯示。
     * TODO: pass the parameters specifying the format of the frame (BPP, YUV or RGB and etc)
     *
     * @param inputFrame
     */
    @Override
    public Mat onCameraFrame(CameraBridgeViewBase.CvCameraViewFrame inputFrame) {
        rgba = inputFrame.rgba();
        //得到當前一幀影象的記憶體地址
        long addr = rgba.getNativeObjAddr();
        //對一幀影象進行處理
        nativeRgba(addr);
        //得到一幀灰度圖
//        rgba = inputFrame.gray();
        return rgba;
    }


    @Override
    protected void onResume() {
        super.onResume();
        if (!OpenCVLoader.initDebug()) {
        } else {
            cameraView.enableView();
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (cameraView != null) {
            cameraView.disableView();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (cameraView != null) {
            cameraView.disableView();
        }
    }
}

在C++中對每一幀資料進行單通道處理,並做了SobelX和Y方向的邊緣檢測,程式碼和結果如下:

 Mat &img = *(Mat*)jrgba;
    Mat out;
    cvtColor(img,out,COLOR_RGBA2GRAY);
    Mat outimgX,outimgY,outXY;
    //x方向sobel梯度
    Sobel(out,outimgX,CV_8U,1,0);
    //y方向sobel梯度
    Sobel(out,outimgY,CV_8U,0,1);
    //合併梯度
    addWeighted(outimgX,0.5,outimgY,0.5,0,outXY);
    uchar *ptr = img.ptr(0);
    uchar *outPtr = outXY.ptr(0);
    for (int i = 0; i < img.rows * img.cols; ++i) {
        ptr[4*i+0] = outPtr[I];
        ptr[4*i+1] = outPtr[I];
        ptr[4*i+2] = outPtr[I];
    }

Screenshot_2018-02-07-23-37-12-648.png

當前我使用的是後置攝像頭,我們又發現了一個問題,幀資料是顯示出來,但是影象是橫著的,怎麼正過來呢?很簡單,我們既然獲取到了每一幀資料的矩陣,那麼我們直接就可以對矩陣進行操作。原理與程式碼如下:

 Mat &img = *(Mat*)jrgba;

    Mat imgT(height,width,CV_8UC4);
    Mat imgF(height,width,CV_8UC4);
//    //圖片倒置
    transpose(img,imgT);
    /**
   * 重定義圖片大小
   * 第三個引數dsize:size格式的圖片大小
   * 第四個引數dx:x方向的圖片縮放比
   * 第五個引數dy:y方向的縮放比
   * 第六個引數:插值方式,一般情況使用預設值(線性插值)
   * dsize不為0的時候,dx和dy是無效的
   *
   */
    resize(imgT,imgF,Size(width,height));
    /**
     * 翻轉圖片
     * 第三個引數,0以x軸進行翻轉,1以y軸翻轉,-1以xy同時翻轉
     */
    flip(imgF,img,1);

Screenshot_2018-02-07-23-30-53-933.png

哈哈!影象正過來了吧!當前是後置攝像頭,那麼前置攝像頭會不會也是這樣的解決辦法呢?那我們程式碼擼起,看那看會如何呢?

Screenshot_2018-02-07-23-00-17-594.png

此時我們發現,用於後置攝像頭相同的操作去解決,並沒有達到預期的效果,很明顯影象是倒置的並且左右方向也是反的。於是乎,深思之後,發現前置攝像頭與後置攝像頭的影象其實就相當於一個照鏡子的過程,我們可以在倒置矩陣之後,再進行一次X,Y方向上的同時矩陣翻轉,不就可以了,一切想明白了之後,試了試,果然達到了預期目標效果。影象與程式碼如下:

 Mat &img = *(Mat*)jrgba;
    Mat imgT(height,width,CV_8UC4);
    Mat imgF(height,width,CV_8UC4);
//    //圖片倒置
    transpose(img,imgT);
    /**
   * 重定義圖片大小
   * 第三個引數dsize:size格式的圖片大小
   * 第四個引數dx:x方向的圖片縮放比
   * 第五個引數dy:y方向的縮放比
   * 第六個引數:插值方式,一般情況使用預設值(線性插值)
   * dsize不為0的時候,dx和dy是無效的
   *
   */
    resize(imgT,imgF,Size(width,height));
    /**
     * 翻轉圖片
     * 第三個引數,0以x軸進行翻轉,1以y軸翻轉,-1以xy同時翻轉
     */
    flip(imgF,img,-1);//進行XY方向同時翻轉

Screenshot_2018-02-07-23-12-00-300.png

哈哈!我又離人臉識別進了一步,很開森的結束了這篇部落格!

本文章著作版權所屬:微笑面對,請關注我的CSDN部落格:部落格地址