Android筆記③--OpenCV實現簡易相機
原文:https://zhuanlan.zhihu.com/p/28422134
前言:項目需要,需要在開發板上實現視頻監控以及拍照的功能。由於android.hardware.camera已被Google棄用,而camera2又不能在開發板上愉快地玩耍(4.0.3系統),因此只能通過OpenCV實現。
在使用OpenCV實現的過程中,使用的是最簡單的方法,即通過OpenCV Manager進行動態庫的鏈接,且實現最簡單的幀預覽以及圖片保存(即拍照)的功能,以下爲完整步驟,開發平臺爲Android Studio 2.2.3,使用的OpenCV版本爲opencv4android 3.2.0。
- 下載OpenCV並導入工程,下載得到的opencv-3.2.0-android-sdk.zip解壓,新建項目,左上角“File-New-Import Module...”,在彈出的窗口中打開解壓得到的文件中的“sdk\java”,然後next,finish,此時會提示build錯誤,打開“OpenCVLibrary310\build.gradle”修改與項目的build.gradle一致,並sync again即可導入成功。
- 在手機上安裝下載的zip中apk的對應版本,此時還需要把opencv要用到的動態庫放到項目中,否則運行會出錯,具體如下:將下載解壓得到的文件中的“sdk\native\libs\”複製一份到項目的“app\src\main”中,並重命名爲jniLibs即可,以上步驟完成後項目目錄如下:
- 此時OpenCV的開發環境已經搭建好,接下來開始我們主要內容的編寫。首先打開佈局文件activity_main(默認),佈局如下:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:opencv="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<org.opencv.android.JavaCameraView
android:id="@+id/camera_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
opencv:show_fps="true"
opencv:camera_id="any" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_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>
- 這裏的JavaCameraView就是OpenCV裏的預覽部件,然後我們再添加了一個拍照的按鍵。
- 然後我們打開AndroidManifest.xml,添加我們需要的權限,攝像頭和內部儲存,這個也十分重要:
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
- 完成後我們開始正式編寫MainActivity。
public class MainActivity extends AppCompatActivity implements CameraBridgeViewBase.CvCameraViewListener2 {
private String TAG = "OpenCV_Test";
//OpenCV的相機接口
private CameraBridgeViewBase mCVCamera;
//緩存相機每幀輸入的數據
private Mat mRgba;
private Button button;
/**通過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);
//拍照按鍵
button = (Button) findViewById(R.id.deal_btn);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(mRgba != null) {
if(!mRgba.empty()) {
Mat inter = new Mat(mRgba.width(), mRgba.height(), CvType.CV_8UC4);
//將四通道的RGBA轉爲三通道的BGR,重要!!
Imgproc.cvtColor(mRgba, inter, Imgproc.COLOR_RGBA2BGR);
File sdDir = null;
//判斷是否存在機身內存
boolean sdCardExist = Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
if(sdCardExist) {
//獲得機身儲存根目錄
sdDir = Environment.getExternalStorageDirectory();
}
//將拍攝準確時間作爲文件名
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
String filename = sdf.format(new Date());
String filePath = sdDir + "/Pictures/OpenCV/" + filename + ".png";
//將轉化後的BGR矩陣內容寫入到文件中
Imgcodecs.imwrite(filePath, inter);
Toast.makeText(MainActivity.this, "圖片保存到: "+ filePath, Toast.LENGTH_SHORT).show();
}
}
}
});
}
@Override
protected void onResume() {
/***強制橫屏***/
if (getRequestedOrientation() != ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
} else {
//橫屏後才加載部件,否則會FC
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);
}
}
super.onResume();
}
@Override
protected void onDestroy() {
if(mCVCamera != null) {
mCVCamera.disableView();
}
super.onDestroy();
}
//對象實例化及基本屬性的設置,包括長度、寬度和圖像類型標誌
public void onCameraViewStarted(int width, int height) {
mRgba = new Mat(height, width, CvType.CV_8UC4);
}
/**圖像處理都寫在這裏!!!**/
@Override
public Mat onCameraFrame(CameraBridgeViewBase.CvCameraViewFrame inputFrame) {
mRgba = inputFrame.rgba(); //一定要有!!!不然數據保存不進MAT中!!!
//直接返回輸入視頻預覽圖的RGB數據並存放在Mat數據中
return mRgba;
}
//結束時釋放
@Override
public void onCameraViewStopped() {
mRgba.release();
mTmp.release();
}
}
對以上內容的一些說明:
- onCameraViewStarted方法對對象進行初始化,這裏使用Mat類的對象mRgba,默認是RGBA格式。
- onCameraFrame方法爲圖像處理的主要部分,今後如需添加圖像處理的內容就寫在此方法中,可用switch-case結構進入不同的處理方法。該方法的返回值爲一個Mat對象,我們這裏不做處理,即將傳入的Mat對象原樣返回。
- 對於圖像保存部分,首先我們通過File類的對象設置文件路徑,然後使用Imgproc.cvtColor方法,將之前的RGBA格式的Mat轉爲BGR格式,原因是我們保存的是一個三通道的8/16位的png格式的圖像,但是之前對於圖像處理所使用的Mat類是一個四通道的RGBA格式,因此需要進行轉換,即RGBA2BGR,否則保存的圖像文件顏色會出現問題。
- 在這裏我通過OpenCV裏面的Imgcodecs.imwrite將Mat類型的對象保存到本地文件中,省去了再創建Bitmap類對象這樣的一些步驟。另提供一段可行的通過使用Mat2Bitmap方法,創建Bitmap對象並由FileOutStream流保存文件的代碼:
Bitmap bmp = null;
try {
bmp = Bitmap.createBitmap(mRgba.cols(), mRgba.rows(), Bitmap.Config.ARGB_8888);
Utils.matToBitmap(mRgba, bmp); //將MAT對象轉換爲bmp對象
Log.d(TAG, "mat to bmp.");
} catch (CvException e) {
Log.d(TAG, e.getMessage());
}
FileOutputStream out = null;
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
String fileName = sdf.format(new Date()) + ".png";
File sd = new File(Environment.getExternalStorageDirectory() + "/Pictures/OpenCV");
boolean success = true;
if(!sd.exists()) {
success = sd.mkdir();
}
if(success) {
File dest = new File(sd, fileName);
try {
out = new FileOutputStream(dest);
bmp.compress(Bitmap.CompressFormat.PNG, 100, out);
//Imgcodecs.imwrite(dest.toString(), mRgba);
Toast.makeText(MainActivity.this, "Picture has saved in " + dest.toString(), Toast.LENGTH_SHORT).show();
} catch (Exception e) {
e.printStackTrace();
Log.d(TAG, e.getMessage());
} finally {
try {
if(out != null) {
out.close();
Log.d(TAG, "success save");
}
} catch (IOException e) {
Log.d(TAG, e.getMessage() + "Error");
e.printStackTrace();
}
}
}
將這段代碼替換到按鍵監聽事件中第二個判斷語句if(!mRgba.empty()) 之後,也是可以運行的。
以上,通過OpenCV實現的一個簡易相機已經實現了,效果如圖:
如果需要其他的一些處理,則將相關代碼寫在onCameraFrame方法中即可。我也是剛開始接觸OpenCV以及圖像處理方面的內容,因此各方面可能會有不足之處,敬請原諒。
參考資料: