项目配置
在opencv官网下载sdkhttps://opencv.org/releases/
项目解压后,需要用到的目录大致如下
│OPENCV-4.5.2-ANDROID-SDK\OPENCV-ANDROID-SDK
├─samples
└─sdk
├─etc
├─java
├─libcxx_helper
└─native
├─3rdparty
├─jni
├─libs
│ ├─arm64-v8a
│ ├─armeabi-v7a
│ ├─x86
│ └─x86_64
└─staticlibs
需要向项目导入module的文件夹为/sdk/java
该目录下文件情况如下
├─javadoc
├─res
├─src
└─AndroidManifest.xml
在AndroidStudio中创建一个NDK项目,项目编译完成后使用File->New->Import Module...
将OpenCV的SDK/sdk/java
导入
之后要修改SDK的build.gradle
配置
//apply plugin: 'com.android.application'
//注意 这里需要以库的方式导入项目
apply plugin: 'com.android.library'
android {
//这个编译SDK版本和构建工具版本要与主项目的相同
compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig {
//因为不是app所以不需要application id
// applicationId "org.opencv"
//这个最小sdk版本和目标sdk版本要与主项目的相同
minSdkVersion 23
targetSdkVersion 30
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
}
}
}
修改主项目add的build.gradle
配置,主要是添加一个过滤器,添加一个配置指定so库的目录
plugins {
id 'com.android.application'
}
android {
compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig {
applicationId "com.phc.opencv_demo_fuck"
minSdkVersion 23
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
cppFlags "-std=c++14"
//添加如下两行
abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86', 'x86_64'
arguments '-DANDROID_STL=c++_shared'
}
}
//这个路径要配置为so库存放的位置
sourceSets {
main {
jniLibs.srcDirs = ['src/main/jniLibs']
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt"
version "3.10.2"
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.2.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation project(path: ':java_opencv_sdk')
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}
将SDK中的so库放到指定的目录下(src/main/jniLibs
)没有目录就创建
so库位于opencv-4.5.2-android-sdk\OpenCV-android-sdk\sdk\native\libs路径下
之后将SDK模块依赖于主项目
到这一步,OpenCV的SDK导入完成了
官方色块检测Sample
-
清单文件
-
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.phc.opencv_demo_fuck"> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.CAMERA" /> <uses-feature android:name="android.hardware.camera" android:required="false" /> <uses-feature android:name="android.hardware.camera.autofocus" android:required="false" /> <uses-feature android:name="android.hardware.camera.front" android:required="false" /> <uses-feature android:name="android.hardware.camera.front.autofocus" android:required="false" /> <supports-screens android:anyDensity="true" android:largeScreens="true" android:normalScreens="true" android:resizeable="true" android:smallScreens="true" /> <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/Theme.Opencv_demo_fuck"> <activity android:name=".colorBlob.ColorBlobDetectionActivity" android:configChanges="keyboardHidden|orientation" android:screenOrientation="landscape" /> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
-
xml ui文件
-
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:orientation="vertical"> <org.opencv.android.JavaCameraView android:id="@+id/jcv_view" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout>
-
java逻辑文件
-
package com.phc.opencv_demo_fuck.colorBlob; import android.os.Bundle; import android.util.Log; import android.view.MotionEvent; import android.view.SurfaceView; import android.view.View; import android.view.View.OnTouchListener; import android.view.Window; import android.view.WindowManager; import com.phc.opencv_demo_fuck.R; import org.opencv.android.BaseLoaderCallback; import org.opencv.android.CameraActivity; import org.opencv.android.CameraBridgeViewBase; import org.opencv.android.CameraBridgeViewBase.CvCameraViewFrame; import org.opencv.android.CameraBridgeViewBase.CvCameraViewListener2; import org.opencv.android.LoaderCallbackInterface; import org.opencv.android.OpenCVLoader; import org.opencv.core.Core; import org.opencv.core.CvType; import org.opencv.core.Mat; import org.opencv.core.MatOfPoint; import org.opencv.core.Rect; import org.opencv.core.Scalar; import org.opencv.core.Size; import org.opencv.imgproc.Imgproc; import java.util.Collections; import java.util.List; public class ColorBlobDetectionActivity extends CameraActivity implements OnTouchListener, CvCameraViewListener2 { private static final String TAG = "OCVSample::Activity"; private boolean mIsColorSelected = false; private Mat mRgba; private Scalar mBlobColorRgba; private Scalar mBlobColorHsv; private ColorBlobDetector mDetector; private Mat mSpectrum; private Size SPECTRUM_SIZE; private Scalar CONTOUR_COLOR; private CameraBridgeViewBase mOpenCvCameraView; private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) { @Override public void onManagerConnected(int status) { switch (status) { case LoaderCallbackInterface.SUCCESS: { Log.i(TAG, "OpenCV loaded successfully"); mOpenCvCameraView.enableView(); mOpenCvCameraView.setOnTouchListener(ColorBlobDetectionActivity.this); } break; default: { super.onManagerConnected(status); } break; } } }; public ColorBlobDetectionActivity() { Log.i(TAG, "Instantiated new " + this.getClass()); } /** * Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { Log.i(TAG, "called onCreate"); super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); setContentView(R.layout.activity_color_blod); mOpenCvCameraView = (CameraBridgeViewBase) findViewById(R.id.jcv_view); mOpenCvCameraView.setVisibility(SurfaceView.VISIBLE); mOpenCvCameraView.setCvCameraViewListener(this); } @Override public void onPause() { super.onPause(); if (mOpenCvCameraView != null) { mOpenCvCameraView.disableView(); } } @Override public void onResume() { super.onResume(); if (!OpenCVLoader.initDebug()) { Log.d(TAG, "Internal OpenCV library not found. Using OpenCV Manager for initialization"); OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_3_0_0, this, mLoaderCallback); } else { Log.d(TAG, "OpenCV library found inside package. Using it!"); mLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS); } } @Override protected List<? extends CameraBridgeViewBase> getCameraViewList() { return Collections.singletonList(mOpenCvCameraView); } @Override public void onDestroy() { super.onDestroy(); if (mOpenCvCameraView != null) { mOpenCvCameraView.disableView(); } } @Override public void onCameraViewStarted(int width, int height) { mRgba = new Mat(height, width, CvType.CV_8UC4); mDetector = new ColorBlobDetector(); mSpectrum = new Mat(); mBlobColorRgba = new Scalar(255); mBlobColorHsv = new Scalar(255); SPECTRUM_SIZE = new Size(200, 64); CONTOUR_COLOR = new Scalar(255, 0, 0, 255); } @Override public void onCameraViewStopped() { mRgba.release(); } @Override public boolean onTouch(View v, MotionEvent event) { int cols = mRgba.cols(); int rows = mRgba.rows(); int xOffset = (mOpenCvCameraView.getWidth() - cols) / 2; int yOffset = (mOpenCvCameraView.getHeight() - rows) / 2; int x = (int) event.getX() - xOffset; int y = (int) event.getY() - yOffset; Log.i(TAG, "Touch image coordinates: (" + x + ", " + y + ")"); if ((x < 0) || (y < 0) || (x > cols) || (y > rows)) { return false; } Rect touchedRect = new Rect(); touchedRect.x = (x > 4) ? x - 4 : 0; touchedRect.y = (y > 4) ? y - 4 : 0; touchedRect.width = (x + 4 < cols) ? x + 4 - touchedRect.x : cols - touchedRect.x; touchedRect.height = (y + 4 < rows) ? y + 4 - touchedRect.y : rows - touchedRect.y; Mat touchedRegionRgba = mRgba.submat(touchedRect); Mat touchedRegionHsv = new Mat(); Imgproc.cvtColor(touchedRegionRgba, touchedRegionHsv, Imgproc.COLOR_RGB2HSV_FULL); // Calculate average color of touched region mBlobColorHsv = Core.sumElems(touchedRegionHsv); int pointCount = touchedRect.width * touchedRect.height; for (int i = 0; i < mBlobColorHsv.val.length; i++) { mBlobColorHsv.val[i] /= pointCount; } mBlobColorRgba = convertScalarHsv2Rgba(mBlobColorHsv); Log.i(TAG, "Touched rgba color: (" + mBlobColorRgba.val[0] + ", " + mBlobColorRgba.val[1] + ", " + mBlobColorRgba.val[2] + ", " + mBlobColorRgba.val[3] + ")"); mDetector.setHsvColor(mBlobColorHsv); Imgproc.resize(mDetector.getSpectrum(), mSpectrum, SPECTRUM_SIZE, 0, 0, Imgproc.INTER_LINEAR_EXACT); mIsColorSelected = true; touchedRegionRgba.release(); touchedRegionHsv.release(); return false; // don't need subsequent touch events } @Override public Mat onCameraFrame(CvCameraViewFrame inputFrame) { mRgba = inputFrame.rgba(); if (mIsColorSelected) { mDetector.process(mRgba); List<MatOfPoint> contours = mDetector.getContours(); Log.i(TAG, "Contours count: " + contours.size()); Imgproc.drawContours(mRgba, contours, -1, CONTOUR_COLOR); Mat colorLabel = mRgba.submat(4, 68, 4, 68); colorLabel.setTo(mBlobColorRgba); Mat spectrumLabel = mRgba.submat(4, 4 + mSpectrum.rows(), 70, 70 + mSpectrum.cols()); mSpectrum.copyTo(spectrumLabel); } // Core.rotate(mRgba, mRgba, Core.ROTATE_90_CLOCKWISE); return mRgba; } private Scalar convertScalarHsv2Rgba(Scalar hsvColor) { Mat pointMatRgba = new Mat(); Mat pointMatHsv = new Mat(1, 1, CvType.CV_8UC3, hsvColor); Imgproc.cvtColor(pointMatHsv, pointMatRgba, Imgproc.COLOR_HSV2RGB_FULL, 4); return new Scalar(pointMatRgba.get(0, 0)); } }
-
色块检测器
-
package com.phc.opencv_demo_fuck.colorBlob; import org.opencv.core.Core; import org.opencv.core.CvType; import org.opencv.core.Mat; import org.opencv.core.MatOfPoint; import org.opencv.core.Scalar; import org.opencv.imgproc.Imgproc; import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class ColorBlobDetector { // Lower and Upper bounds for range checking in HSV color space private Scalar mLowerBound = new Scalar(0); private Scalar mUpperBound = new Scalar(0); // Minimum contour area in percent for contours filtering private static double mMinContourArea = 0.1; // Color radius for range checking in HSV color space private Scalar mColorRadius = new Scalar(25, 50, 50, 0); private Mat mSpectrum = new Mat(); private List<MatOfPoint> mContours = new ArrayList<MatOfPoint>(); // Cache Mat mPyrDownMat = new Mat(); Mat mHsvMat = new Mat(); Mat mMask = new Mat(); Mat mDilatedMask = new Mat(); Mat mHierarchy = new Mat(); public void setColorRadius(Scalar radius) { mColorRadius = radius; } public void setHsvColor(Scalar hsvColor) { double minH = (hsvColor.val[0] >= mColorRadius.val[0]) ? hsvColor.val[0] - mColorRadius.val[0] : 0; double maxH = (hsvColor.val[0] + mColorRadius.val[0] <= 255) ? hsvColor.val[0] + mColorRadius.val[0] : 255; mLowerBound.val[0] = minH; mUpperBound.val[0] = maxH; mLowerBound.val[1] = hsvColor.val[1] - mColorRadius.val[1]; mUpperBound.val[1] = hsvColor.val[1] + mColorRadius.val[1]; mLowerBound.val[2] = hsvColor.val[2] - mColorRadius.val[2]; mUpperBound.val[2] = hsvColor.val[2] + mColorRadius.val[2]; mLowerBound.val[3] = 0; mUpperBound.val[3] = 255; Mat spectrumHsv = new Mat(1, (int) (maxH - minH), CvType.CV_8UC3); for (int j = 0; j < maxH - minH; j++) { byte[] tmp = {(byte) (minH + j), (byte) 255, (byte) 255}; spectrumHsv.put(0, j, tmp); } Imgproc.cvtColor(spectrumHsv, mSpectrum, Imgproc.COLOR_HSV2RGB_FULL, 4); } public Mat getSpectrum() { return mSpectrum; } public void setMinContourArea(double area) { mMinContourArea = area; } public void process(Mat rgbaImage) { Imgproc.pyrDown(rgbaImage, mPyrDownMat); Imgproc.pyrDown(mPyrDownMat, mPyrDownMat); Imgproc.cvtColor(mPyrDownMat, mHsvMat, Imgproc.COLOR_RGB2HSV_FULL); Core.inRange(mHsvMat, mLowerBound, mUpperBound, mMask); Imgproc.dilate(mMask, mDilatedMask, new Mat()); List<MatOfPoint> contours = new ArrayList<MatOfPoint>(); Imgproc.findContours(mDilatedMask, contours, mHierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE); // Find max contour area double maxArea = 0; Iterator<MatOfPoint> each = contours.iterator(); while (each.hasNext()) { MatOfPoint wrapper = each.next(); double area = Imgproc.contourArea(wrapper); if (area > maxArea) { maxArea = area; } } // Filter contours by area and resize to fit the original image size mContours.clear(); each = contours.iterator(); while (each.hasNext()) { MatOfPoint contour = each.next(); if (Imgproc.contourArea(contour) > mMinContourArea * maxArea) { Core.multiply(contour, new Scalar(4, 4), contour); mContours.add(contour); } } } public List<MatOfPoint> getContours() { return mContours; } }