OpenCV學習筆記(七)—— OpenCV for Android實時影象處理
在上篇中我們已經實現了相機開啟和實時影象資訊的獲取,那麼接下來我們可以嘗試在獲取的影象資訊進行一些處理,然後實時顯示出來,在這裡我們要完成的的幾種處理:
灰化、Canny邊緣檢測、Hist直方圖計算、Sobel邊緣檢測、SEPIA(色調變換)、ZOOM放大鏡、PIXELIZE畫素化
一、修改佈局介面:
由於這裡我們需要切換不同的影象處理模式,所以這裡我們需要在介面上放置一個按鈕,我們可以放置很多個按鈕,每個按鈕對應一種處理模式,但是這裡我們也可以只放置一個按鈕,每次點選按鈕就切換一次,迴圈切換模式:
activity_main.xml
檢視預覽圖:<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:opencv="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <org.opencv.android.JavaCameraView android:layout_width="fill_parent" android:layout_height="fill_parent" android:id="@+id/camera_view" opencv:show_fps="true" opencv:camera_id="any"/> <RelativeLayout android:layout_width="fill_parent" android:layout_height="fill_parent" android:gravity="bottom|center_horizontal"> <Button android:id="@+id/deal_btn" android:layout_width="100dp" android:layout_height="40dp" android:layout_marginBottom="20dp" android:text="處理"/> </RelativeLayout> </FrameLayout>
二、獲取按鈕元件並監聽按鈕點選:
1.宣告一個Button物件用於繫結上面的按鈕元件和一個狀態標誌位用於儲存當前狀態:
//按鈕元件
private Button mButton;
//當前處理狀態
private static int Cur_State = 0;
2.在OnCreate中繫結按鈕和按鈕點選監聽:
這裡的狀態標誌位Cur_State與影象處理的每個型別對應,0對應預設狀態,也就是顯示原圖,1-7分別對應:灰化、Canny邊緣檢測、Hist直方圖計算、Sobel邊緣檢測、SEPIA(色調變換)、ZOOM放大鏡、PIXELIZE畫素化mButton = (Button) findViewById(R.id.deal_btn); mButton.setOnClickListener(new OnClickListener(){ @Override public void onClick(View v) { if(Cur_State<8){ //切換狀態 Cur_State ++; }else{ //恢復初始狀態 Cur_State = 0; } } });
三、影象資訊獲取儲存、處理和顯示:
1.在OpenCV中一般都是使用Mat型別來儲存影象等矩陣資訊,所以我們可以宣告一個Mat物件用來作為實時幀影象的快取物件:
//快取相機每幀輸入的資料
private Mat mRgba;
2.物件例項化以及基本屬性的設定,包括:長度、寬度和影象型別標誌:public void onCameraViewStarted(int width, int height) {
// TODO Auto-generated method stub
mRgba = new Mat(height, width, CvType.CV_8UC4);
}
3.物件賦值,這裡只對原圖和灰化兩種情況進行了處理,其他的處理後續再新增:
/**
* 影象處理都寫在此處
*/
@Override
public Mat onCameraFrame(CvCameraViewFrame inputFrame) {
switch (Cur_State) {
case 1:
//灰化處理
Imgproc.cvtColor(inputFrame.gray(), mRgba, Imgproc.COLOR_GRAY2RGBA,4);
break;
default:
//顯示原圖
mRgba = inputFrame.rgba();
break;
}
//返回處理後的結果資料
return mRgba;
}
4.由於用物件儲存影象資料的話,資料會儲存到記憶體中,所以結束的時候需要進行資料釋放,不然可能導致崩潰:
@Override
public void onCameraViewStopped() {
// TODO Auto-generated method stub
mRgba.release();
}
5.執行檢視效果:
正常模式:
灰化圖:
四、其他處理及結果:
在以上的例子中我們已經完成了預覽圖的灰化處理,那麼接下來我們把其他處理都新增到程式碼中,檢視效果。由於在2.x版本中使用到的部分方法已經發生了變化,如:在OpenCV 3.1.0中org.opencv.core.Core類中的方法line和rectangle都已失效,可以用org.opencv.imgproc.Imgproc中的line和rectangle來代替:
1. MainActivity.java原始碼:
package com.linsh.opencv_test;
import java.util.Arrays;
import org.opencv.android.BaseLoaderCallback;
import org.opencv.android.CameraBridgeViewBase;
import org.opencv.android.OpenCVLoader;
import org.opencv.android.CameraBridgeViewBase.CvCameraViewFrame;
import org.opencv.android.CameraBridgeViewBase.CvCameraViewListener2;
import org.opencv.android.LoaderCallbackInterface;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.MatOfFloat;
import org.opencv.core.MatOfInt;
import org.opencv.core.Point;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.imgproc.Imgproc;
import android.R.string;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.widget.Button;
import android.view.View;
import android.view.View.OnClickListener;
public class MainActivity extends Activity implements CvCameraViewListener2{
private String TAG = "OpenCV_Test";
//OpenCV的相機介面
private CameraBridgeViewBase mCVCamera;
//快取相機每幀輸入的資料
private Mat mRgba,mTmp;
//按鈕元件
private Button mButton;
//當前處理狀態
private static int Cur_State = 0;
private Size mSize0;
private Mat mIntermediateMat;
private MatOfInt mChannels[];
private MatOfInt mHistSize;
private int mHistSizeNum = 25;
private Mat mMat0;
private float[] mBuff;
private MatOfFloat mRanges;
private Point mP1;
private Point mP2;
private Scalar mColorsRGB[];
private Scalar mColorsHue[];
private Scalar mWhilte;
private Mat mSepiaKernel;
/**
* 通過OpenCV管理Android服務,非同步初始化OpenCV
*/
BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) {
@Override
public void onManagerConnected(int status){
switch (status) {
case LoaderCallbackInterface.SUCCESS:
Log.i(TAG,"OpenCV loaded successfully");
mCVCamera.enableView();
break;
default:
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mCVCamera = (CameraBridgeViewBase) findViewById(R.id.camera_view);
mCVCamera.setCvCameraViewListener(this);
mButton = (Button) findViewById(R.id.deal_btn);
mButton.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View v) {
if(Cur_State<8){
//切換狀態
Cur_State ++;
}else{
//恢復初始狀態
Cur_State = 0;
}
}
});
}
@Override
public void onResume() {
super.onResume();
if (!OpenCVLoader.initDebug()) {
Log.d(TAG,"OpenCV library not found!");
} else {
Log.d(TAG, "OpenCV library found inside package. Using it!");
mLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS);
}
};
@Override
public void onDestroy() {
if(mCVCamera!=null){
mCVCamera.disableView();
}
};
@Override
public void onCameraViewStarted(int width, int height) {
// TODO Auto-generated method stub
mRgba = new Mat(height, width, CvType.CV_8UC4);
mTmp = new Mat(height, width, CvType.CV_8UC4);
mIntermediateMat = new Mat();
mSize0 = new Size();
mChannels = new MatOfInt[] { new MatOfInt(0), new MatOfInt(1), new MatOfInt(2) };
mBuff = new float[mHistSizeNum];
mHistSize = new MatOfInt(mHistSizeNum);
mRanges = new MatOfFloat(0f, 256f);
mMat0 = new Mat();
mColorsRGB = new Scalar[] { new Scalar(200, 0, 0, 255), new Scalar(0, 200, 0, 255), new Scalar(0, 0, 200, 255) };
mColorsHue = new Scalar[] {
new Scalar(255, 0, 0, 255), new Scalar(255, 60, 0, 255), new Scalar(255, 120, 0, 255), new Scalar(255, 180, 0, 255), new Scalar(255, 240, 0, 255),
new Scalar(215, 213, 0, 255), new Scalar(150, 255, 0, 255), new Scalar(85, 255, 0, 255), new Scalar(20, 255, 0, 255), new Scalar(0, 255, 30, 255),
new Scalar(0, 255, 85, 255), new Scalar(0, 255, 150, 255), new Scalar(0, 255, 215, 255), new Scalar(0, 234, 255, 255), new Scalar(0, 170, 255, 255),
new Scalar(0, 120, 255, 255), new Scalar(0, 60, 255, 255), new Scalar(0, 0, 255, 255), new Scalar(64, 0, 255, 255), new Scalar(120, 0, 255, 255),
new Scalar(180, 0, 255, 255), new Scalar(255, 0, 255, 255), new Scalar(255, 0, 215, 255), new Scalar(255, 0, 85, 255), new Scalar(255, 0, 0, 255)
};
mWhilte = Scalar.all(255);
mP1 = new Point();
mP2 = new Point();
// Fill sepia kernel
mSepiaKernel = new Mat(4, 4, CvType.CV_32F);
mSepiaKernel.put(0, 0, /* R */0.189f, 0.769f, 0.393f, 0f);
mSepiaKernel.put(1, 0, /* G */0.168f, 0.686f, 0.349f, 0f);
mSepiaKernel.put(2, 0, /* B */0.131f, 0.534f, 0.272f, 0f);
mSepiaKernel.put(3, 0, /* A */0.000f, 0.000f, 0.000f, 1f);
}
@Override
public void onCameraViewStopped() {
// TODO Auto-generated method stub
mRgba.release();
mTmp.release();
}
/**
* 影象處理都寫在此處
*/
@Override
public Mat onCameraFrame(CvCameraViewFrame inputFrame) {
mRgba = inputFrame.rgba();
Size sizeRgba = mRgba.size();
int rows = (int) sizeRgba.height;
int cols = (int) sizeRgba.width;
Mat rgbaInnerWindow;
int left = cols / 8;
int top = rows / 8;
int width = cols * 3 / 4;
int height = rows * 3 / 4;
switch (Cur_State) {
case 1:
//灰化處理
Imgproc.cvtColor(inputFrame.gray(), mRgba, Imgproc.COLOR_GRAY2RGBA,4);
break;
case 2:
//Canny邊緣檢測
mRgba = inputFrame.rgba();
Imgproc.Canny(inputFrame.gray(), mTmp, 80, 100);
Imgproc.cvtColor(mTmp, mRgba, Imgproc.COLOR_GRAY2RGBA, 4);
break;
case 3:
//Hist直方圖計算
Mat hist = new Mat();
int thikness = (int) (sizeRgba.width / (mHistSizeNum + 10) / 5);
if(thikness > 5) thikness = 5;
int offset = (int) ((sizeRgba.width - (5*mHistSizeNum + 4*10)*thikness)/2);
// RGB
for(int c=0; c<3; c++) {
Imgproc.calcHist(Arrays.asList(mRgba), mChannels[c], mMat0, hist, mHistSize, mRanges);
Core.normalize(hist, hist, sizeRgba.height/2, 0, Core.NORM_INF);
hist.get(0, 0, mBuff);
for(int h=0; h<mHistSizeNum; h++) {
mP1.x = mP2.x = offset + (c * (mHistSizeNum + 10) + h) * thikness;
mP1.y = sizeRgba.height-1;
mP2.y = mP1.y - 2 - (int)mBuff[h];
Imgproc.line(mRgba, mP1, mP2, mColorsRGB[c], thikness);
}
}
// Value and Hue
Imgproc.cvtColor(mRgba, mTmp, Imgproc.COLOR_RGB2HSV_FULL);
// Value
Imgproc.calcHist(Arrays.asList(mTmp), mChannels[2], mMat0, hist, mHistSize, mRanges);
Core.normalize(hist, hist, sizeRgba.height/2, 0, Core.NORM_INF);
hist.get(0, 0, mBuff);
for(int h=0; h<mHistSizeNum; h++) {
mP1.x = mP2.x = offset + (3 * (mHistSizeNum + 10) + h) * thikness;
mP1.y = sizeRgba.height-1;
mP2.y = mP1.y - 2 - (int)mBuff[h];
Imgproc.line(mRgba, mP1, mP2, mWhilte, thikness);
}
break;
case 4:
//Sobel邊緣檢測
Mat gray = inputFrame.gray();
Mat grayInnerWindow = gray.submat(top, top + height, left, left + width);
rgbaInnerWindow = mRgba.submat(top, top + height, left, left + width);
Imgproc.Sobel(grayInnerWindow, mIntermediateMat, CvType.CV_8U, 1, 1);
Core.convertScaleAbs(mIntermediateMat, mIntermediateMat, 10, 0);
Imgproc.cvtColor(mIntermediateMat, rgbaInnerWindow, Imgproc.COLOR_GRAY2BGRA, 4);
grayInnerWindow.release();
rgbaInnerWindow.release();
break;
case 5:
//SEPIA(色調變換)
rgbaInnerWindow = mRgba.submat(top, top + height, left, left + width);
Core.transform(rgbaInnerWindow, rgbaInnerWindow, mSepiaKernel);
rgbaInnerWindow.release();
break;
case 6:
//ZOOM放大鏡
Mat zoomCorner = mRgba.submat(0, rows / 2 - rows / 10, 0, cols / 2 - cols / 10);
Mat mZoomWindow = mRgba.submat(rows / 2 - 9 * rows / 100, rows / 2 + 9 * rows / 100, cols / 2 - 9 * cols / 100, cols / 2 + 9 * cols / 100);
Imgproc.resize(mZoomWindow, zoomCorner, zoomCorner.size());
Size wsize = mZoomWindow.size();
Imgproc.rectangle(mZoomWindow, new Point(1, 1), new Point(wsize.width - 2, wsize.height - 2), new Scalar(255, 0, 0, 255), 2);
zoomCorner.release();
mZoomWindow.release();
break;
case 7:
//PIXELIZE畫素化
rgbaInnerWindow = mRgba.submat(top, top + height, left, left + width);
Imgproc.resize(rgbaInnerWindow, mIntermediateMat, mSize0, 0.1, 0.1, Imgproc.INTER_NEAREST);
Imgproc.resize(mIntermediateMat, rgbaInnerWindow, rgbaInnerWindow.size(), 0., 0., Imgproc.INTER_NEAREST);
rgbaInnerWindow.release();
break;
default:
//顯示原圖
mRgba = inputFrame.rgba();
break;
}
//返回處理後的結果資料
return mRgba;
}
}
2.效果圖:
Canny邊緣檢測:
Hist直方圖計算:
Sobel邊緣檢測:
SEPIA(色調變換):
ZOOM放大鏡:
PIXELIZE畫素化: