前言
既然環境搭好了,開始玩相機(作死的開始。。。)
在前面環境搭建的基礎上繼續
實現方案:
採用OpenCV Camera框架 + JNI實現;
工具:
Android Studio 3.3.2 、NDK R16、OpenCV for Android 3.4.1
第一步:修改UI文件
Step 1 :修改 activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/bt_toCanny"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:layout_marginStart="11dp"
android:layout_marginTop="29dp"
android:text="Canny"/>
<Button
android:id="@+id/bt_camera"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentEnd="true"
android:layout_marginTop="30dp"
android:layout_marginEnd="11dp"
android:text="相機"/>
<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:contentDescription="圖片預覽"
android:scaleType="fitCenter"
android:src="@drawable/test"/>
</RelativeLayout>
Step 1 :添加相機預覽UI activity_cameraxml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".CameraView">
<RadioGroup
android:id="@+id/radioGroup"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:orientation="horizontal"
tools:ignore="RtlHardcoded">
<RadioButton
android:id="@+id/backCameraOption"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/sw_camera"/>
</RadioGroup>
<org.opencv.android.JavaCameraView
android:id="@+id/cv_camera"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/radioGroup"
android:visibility="gone"/>
<Switch
android:id="@+id/sw_color"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentEnd="true"
android:layout_marginTop="0dp"
android:layout_marginEnd="3dp"
android:text="@string/switch_color"
tools:ignore="RelativeOverlap"/>
</RelativeLayout>
第二步:修改app/AndroidManifest.java
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="cn.angry.opencvcamera">
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-feature android:name="android.hardware.Camera"/>
<uses-feature android:name="android.hardware.Camera.flash"/>
<uses-feature android:name="android.hardware.Camera.autofocus"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:ignore="GoogleAppIndexingWarning">
<activity
android:name=".CameraView"
android:label="@string/title_activity_camera_view"
android:theme="@style/AppTheme.NoActionBar">
</activity>
<activity
android:name=".MainActivity"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="ca-app-pub-4910454923329631~7109328246"/>
</application>
</manifest>
第三步:修改MAinActivity.java
public class MainActivity extends AppCompatActivity {
private static int cannyID = 0;
private static final String TAG = "OpenCV";
private InterstitialAd mInterstitialAd;
// OpenCV庫靜態加載並初始化
static {
if (OpenCVLoader.initDebug()) {
Log.i(TAG, "Load successfully...");
} else {
Log.i(TAG, "Fail to load...");
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button btCanny = findViewById(R.id.bt_toCanny);
btCanny.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
cannyID = (cannyID + 1) % 2;
imageToCanny();
}
});
Button btCamera = findViewById(R.id.bt_camera);
btCamera.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this.getApplicationContext(), CameraView.class);
startActivity(intent);
}
});
}
private void imageToCanny() {
ImageView imageView = findViewById(R.id.imageView);
Bitmap image = BitmapFactory.decodeResource(this.getResources(), R.drawable.test);
if (cannyID == 1) {
new JniInterface().getEdge(image);
}
imageView.setImageBitmap(image);
}
}
第三步:添加CameraView.java類
package cn.angry.opencvcamera;
import android.Manifest;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.view.SurfaceView;
import android.view.View;
import android.view.WindowManager;
import android.widget.RadioButton;
import android.widget.Switch;
import org.opencv.android.CameraBridgeViewBase;
import org.opencv.android.JavaCameraView;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
/**
* @author hirah
*/
public class CameraView extends AppCompatActivity implements CameraBridgeViewBase.CvCameraViewListener2 {
private JavaCameraView mCameraView;
private static int requestPermissionId = 1;
private static int buildVersion = 23;
private static int cameraId = 0;
private static int process = 0;
private static int colorToGray = 0;
Mat mFrame;
Mat mGray;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_camera);
// 動態權限
if (Build.VERSION.SDK_INT >= buildVersion) {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE},
requestPermissionId);
}
// 設置窗口
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
// 初始化相機
mCameraView = findViewById(R.id.cv_camera);
mCameraView.setVisibility(SurfaceView.VISIBLE);
mCameraView.setCvCameraViewListener(this);
// 開始預覽
mCameraView.setCameraIndex(0);
mCameraView.enableView();
mCameraView.enableFpsMeter();
// 切換攝像頭方法
RadioButton backOption = findViewById(R.id.backCameraOption);
backOption.setChecked(true);
backOption.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
cameraId = (cameraId + 1) % 2;
cameraSwitch(cameraId);
}
});
Switch btHistogram = findViewById(R.id.sw_histogram);
btHistogram.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
process = (process + 1) % 2;
}
});
Switch btGray = findViewById(R.id.sw_color);
btGray.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
colorToGray = (colorToGray + 1) % 2;
}
});
}
@Override
public void onCameraViewStarted(int width, int height) {
mFrame = new Mat(height, width, CvType.CV_8UC4);
mGray = new Mat(height, width, CvType.CV_8UC1);
}
@Override
public void onCameraViewStopped() {
mFrame.release();
mGray.release();
}
@Override
public Mat onCameraFrame(CameraBridgeViewBase.CvCameraViewFrame inputFrame) {
mFrame = inputFrame.rgba();
mFrame = process(mFrame);
return mFrame;
}
/*
** Frame process method
*/
private Mat process(Mat frame) {
if (colorToGray == 1) {
new JniInterface().colorToGray(frame.getNativeObjAddr(), mGray.getNativeObjAddr());
return mGray;
}
if (process == 1) {
new JniInterface().histogram(frame.getNativeObjAddr(), mGray.getNativeObjAddr());
return mGray;
}
return frame;
}
private void cameraSwitch(int id) {
cameraId = id;
mCameraView.setCameraIndex(cameraId);
if (mCameraView != null) {
mCameraView.disableView();
}
mCameraView.enableView();
mCameraView.enableFpsMeter();
}
@Override
protected void onPause() {
super.onPause();
if (mCameraView != null) {
mCameraView.disableView();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mCameraView != null) {
mCameraView.disableView();
}
}
@Override
protected void onResume() {
super.onResume();
if (mCameraView != null) {
mCameraView.setCameraIndex(cameraId);
mCameraView.enableView();
mCameraView.enableFpsMeter();
}
}
}
第四步:修改OpenCV for Android源碼
重點啊,超級重要啊,否者會有意想不到的驚喜
- 當修改旋轉後,應修改
// 在 CameraBridgeViewBase.java 中的 deliverAndDrawFrame方法中添加, 否者切換攝像頭可能出差錯
if (mCacheBitmap != null) {
mCacheBitmap.recycle();
mCacheBitmap = Bitmap.createBitmap(modified.width(), modified.height(), Bitmap.Config.ARGB_8888);
}
- 修改全屏
// 修改 JavaCameraView.java, 否者可能無法全屏預覽
if ((getLayoutParams().width == LayoutParams.MATCH_PARENT) && (getLayoutParams().height == LayoutParams.MATCH_PARENT))
mScale = Math.min(((float)height)/mFrameHeight, ((float)width)/mFrameWidth);
// update -------------------->
if ((getLayoutParams().width == LayoutParams.MATCH_PARENT) && (getLayoutParams().height == LayoutParams.MATCH_PARENT))
mScale = Math.max(((float)height)/mFrameHeight, ((float)width)/mFrameWidth);
- 修改攝像頭預覽方向,否者可能會導致預覽圖相倒置。。。一系列迷
// CameraBridgeViewBase.java 的 deliverAndDrawFrame 中添加
if (bmpValid && mCacheBitmap != null) {
Canvas canvas = getHolder().lockCanvas();
if (canvas != null) {
// added for rotation by hirah
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT && mCameraIndex == 0) {
canvas.save();
canvas.rotate(180, (canvas.getWidth()/ 2),(canvas.getHeight()/ 2));
} else if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT && mCameraIndex == 1) {
canvas.save();
canvas.rotate(0, (canvas.getWidth()/ 2),(canvas.getHeight()/ 2));
} else {
canvas.save();
}
ennnn,到此結,不對,還有JNI(呸。。。)
第五步:修改native-lib.cpp
#include <jni.h>
#include <android/bitmap.h>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
extern "C" {
int toGray(Mat img, Mat &gray) {
cvtColor(img, gray, CV_RGBA2GRAY);
if (gray.rows == img.rows && gray.cols == img.cols) {
return 1;
} else {
return 0;
}
}
/* 直方圖均衡化算法 */
// 預留算法接口,正在測試新的直方圖均衡化算法。。。。。
int Hist_equalization(Mat src, Mat &dst){
}
// Color to gray
JNIEXPORT jint JNICALL
Java_cn_angry_opencvcamera_JniInterface_colorToGray(JNIEnv *env, jobject, jlong addrSrc,
jlong addrDst) {
Mat &src = *(Mat *) addrSrc;
Mat &dst = *(Mat *) addrDst;
int conv;
jint retVal;
conv = toGray(src, dst);
retVal = (jint) conv;
return retVal;
}
// Histogram
JNIEXPORT jint JNICALL
Java_cn_angry_opencvcamera_JniInterface_histogram(JNIEnv *env, jobject, jlong addrSrc,
jlong addrDst) {
Mat &src = *(Mat *) addrSrc;
Mat &dst = *(Mat *) addrDst;
int conv;
jint retVal;
conv = Hist_equalization(src, dst);
retVal = (jint) conv;
return retVal;
}
// Canny edge detect
JNIEXPORT void JNICALL
Java_cn_angry_opencvcamera_JniInterface_getEdge(JNIEnv *env, jobject, jobject bitmap) {
AndroidBitmapInfo info;
void *pixels;
CV_Assert(AndroidBitmap_getInfo(env, bitmap, &info) >= 0);
CV_Assert(info.format == ANDROID_BITMAP_FORMAT_RGBA_8888 ||
info.format == ANDROID_BITMAP_FORMAT_RGB_565);
CV_Assert(AndroidBitmap_lockPixels(env, bitmap, &pixels) >= 0);
CV_Assert(pixels);
if (info.format == ANDROID_BITMAP_FORMAT_RGBA_8888) {
Mat temp(info.height, info.width, CV_8UC4, pixels);
Mat gray;
cvtColor(temp, gray, COLOR_RGBA2GRAY);
Canny(gray, gray, 100, 185);
cvtColor(gray, temp, COLOR_GRAY2RGBA);
} else {
Mat temp(info.height, info.width, CV_8UC2, pixels);
Mat gray;
cvtColor(temp, gray, COLOR_RGB2GRAY);
Canny(gray, gray, 100, 185);
cvtColor(gray, temp, COLOR_GRAY2RGB);
}
AndroidBitmap_unlockPixels(env, bitmap);
}
}
第六步:修改JNIInterface.java
/**
* @author: hirah
* @date: 19-3-27 上午11:26
* @note: This is a jni interface for static loading native function.
*/
public class JniInterface {
static {
System.loadLibrary("native-lib");
}
// 聲明本地Jni函數
/**
* This is a method for image processing.
* You can see detail in native-lib.cpp
* @param:
* bitmap for processing
* @return:
* void
*/
public native void getEdge(Object bitmap);
/**
* This is a method for translating color to gray
* You can see detail in native-lib.cpp
*
* @param:
* native address of pending frame
* native address of result frame
* @return:
* 0, not processed
* 1, processed successfully
*/
public native int colorToGray(long srcAdd, long dstAdd);
/**
* This is a method for histogram equalization
* You can see detail in native-lib.cpp
*
* @param:
* native address of pending frame
* native address of result frame
* @return: int
* 0, not processed
* 1, processed successfully
*/
public native int histogram(long srcAdd, long dstAdd);
}
ennnn,到此應該正式結束,測試效果應該有五個方面
- 圖片處理,Canny邊緣檢測
- 相機預覽
- 實時幀處理
- 相機切換
- 廣告顯示( 開源社會要什麼。。。廣告)