英文原版购书链接:亚马逊《Mastering OpenCV Android Application Programming》
原书所给代码以章为单位,针对的Android版本从API 19到API 21不等,同时使用的OpenCV库版本也有2.4.9和2.4.10两种。本文给出的代码是在原书代码的基础上,针对Android 7.0(API 24)与OpenCV 3.2进行了修改,适当地增加了一些预处理等操作,以使代码整体上更合理。
原书的完整代码可以在这里获取:https://www.packtpub.com/lcode_download/22299。
更新后的代码托管在GitHub:https://github.com/johnhany/MOAAP/tree/master/MOAAP-Chp5-r3。
关于在Android Studio上配置OpenCV开发环境的方法,请参考《在Android Studio上进行OpenCV 3.1开发》。
本章介绍如何在Android Studio 2上通过OpenCV使用常见的目标追踪算法,涉及的算法有光流法,图像金字塔以及KTL追踪器。关于以上各算法的原理可以参考《深入OpenCV Android应用开发》第五章。
开发环境
Windows 10 x64
Android Studio 2.3.3(Gradle 3.3,Android Plugin 2.3.3)
Android 7.0(API 24)
JDK 8u141
OpenCV 3.2.0 Android SDK
代码及简略解释
1. 创建Android Studio项目,包命名为net.johnhany.moaap_chp5。导入OpenCV-android-sdk\sdk\java到项目中,并为app模块加载模块依赖。
2. 将app\src\main\java\net\johnhany\moaap_chp5\MainActivity.java文件修改为如下内容:
package net.johnhany.moaap_chp5; import android.Manifest; import android.content.pm.PackageManager; import android.support.annotation.NonNull; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.WindowManager; import org.opencv.android.BaseLoaderCallback; 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.CvType; import org.opencv.core.Mat; import org.opencv.core.MatOfByte; import org.opencv.core.MatOfFloat; import org.opencv.core.MatOfPoint; import org.opencv.core.MatOfPoint2f; import org.opencv.core.Point; import org.opencv.core.Scalar; import org.opencv.imgproc.Imgproc; import org.opencv.video.Video; import java.util.List; public class MainActivity extends AppCompatActivity implements CvCameraViewListener2 { private static final String TAG = "MOAAP_CHP5"; private static final int VIEW_MODE_KLT_TRACKER = 0; private static final int VIEW_MODE_OPTICAL_FLOW = 1; static int REQUEST_CAMERA = 0; static boolean camera_granted = false; private int mViewMode; private Mat mRgba; private Mat mIntermediateMat; private Mat mGray; private Mat mPrevGray; MatOfPoint2f prevFeatures, nextFeatures; MatOfPoint features; MatOfByte status; MatOfFloat err; private MenuItem mItemPreviewOpticalFlow, mItemPreviewKLT; 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(); } break; default: { super.onManagerConnected(status); } break; } } }; @Override public void onCreate(Bundle savedInstanceState) { Log.i(TAG, "called onCreate"); super.onCreate(savedInstanceState); getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); setContentView(R.layout.activity_main); if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { Log.i("permission", "request READ_EXTERNAL_STORAGE"); ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA); }else { Log.i("permission", "READ_EXTERNAL_STORAGE already granted"); camera_granted = true; } mOpenCvCameraView = (CameraBridgeViewBase) findViewById(R.id.main_activity_surface_view); mOpenCvCameraView.setCvCameraViewListener(this); } @Override public boolean onCreateOptionsMenu(Menu menu) { Log.i(TAG, "called onCreateOptionsMenu"); mItemPreviewKLT = menu.add("KLT Tracker"); mItemPreviewOpticalFlow = menu.add("Optical Flow"); return true; } @Override public void onPause() { super.onPause(); if (mOpenCvCameraView != null) mOpenCvCameraView.disableView(); } @Override public void onResume() { super.onResume(); OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_3_2_0, this, mLoaderCallback); } public void onDestroy() { super.onDestroy(); if (mOpenCvCameraView != null) mOpenCvCameraView.disableView(); } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) { if (requestCode == REQUEST_CAMERA) { // If request is cancelled, the result arrays are empty. if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // permission was granted Log.i("permission", "CAMERA granted"); camera_granted = true; } else { // permission denied Log.i("permission", "CAMERA denied"); } } } public void onCameraViewStarted(int width, int height) { mRgba = new Mat(height, width, CvType.CV_8UC4); mIntermediateMat = new Mat(height, width, CvType.CV_8UC4); mGray = new Mat(height, width, CvType.CV_8UC1); // Log.d(TAG, "\nRows: "+height+"\nCols: "+width+"\n"); resetVars(); } public void onCameraViewStopped() { mRgba.release(); mGray.release(); mIntermediateMat.release(); } public Mat onCameraFrame(CvCameraViewFrame inputFrame) { final int viewMode = mViewMode; switch (viewMode) { case VIEW_MODE_OPTICAL_FLOW: mGray = inputFrame.gray(); if(features.toArray().length==0){ int rowStep = 50, colStep = 100; int nRows = mGray.rows()/rowStep, nCols = mGray.cols()/colStep; // Log.d(TAG, "\nRows: "+nRows+"\nCols: "+nCols+"\n"); Point points[] = new Point[nRows*nCols]; for(int i=0; i<nRows; i++){ for(int j=0; j<nCols; j++){ points[i*nCols+j]=new Point(j*colStep, i*rowStep); // Log.d(TAG, "\nRow: "+i*rowStep+"\nCol: "+j*colStep+"\n: "); } } features.fromArray(points); prevFeatures.fromList(features.toList()); mPrevGray = mGray.clone(); break; } nextFeatures.fromArray(prevFeatures.toArray()); Video.calcOpticalFlowPyrLK(mPrevGray, mGray, prevFeatures, nextFeatures, status, err); List<Point> prevList=features.toList(), nextList=nextFeatures.toList(); Scalar color = new Scalar(255); for(int i = 0; i<prevList.size(); i++){ // Core.circle(mGray, prevList.get(i), 5, color); Imgproc.line(mGray, prevList.get(i), nextList.get(i), color); } mPrevGray = mGray.clone(); break; case VIEW_MODE_KLT_TRACKER: mGray = inputFrame.gray(); if(features.toArray().length==0){ Imgproc.goodFeaturesToTrack(mGray, features, 10, 0.01, 10); Log.d(TAG, features.toList().size()+""); prevFeatures.fromList(features.toList()); mPrevGray = mGray.clone(); // prevFeatures.fromList(nextFeatures.toList()); break; } // OpticalFlow(mPrevGray.getNativeObjAddr(), mGray.getNativeObjAddr(), prevFeatures.getNativeObjAddr(), nextFeatures.getNativeObjAddr()); Video.calcOpticalFlowPyrLK(mPrevGray, mGray, prevFeatures, nextFeatures, status, err); List<Point> drawFeature = nextFeatures.toList(); // Log.d(TAG, drawFeature.size()+""); for(int i = 0; i<drawFeature.size(); i++){ Point p = drawFeature.get(i); Imgproc.circle(mGray, p, 5, new Scalar(255)); } mPrevGray = mGray.clone(); prevFeatures.fromList(nextFeatures.toList()); break; default: mViewMode = VIEW_MODE_KLT_TRACKER; } return mGray; } public boolean onOptionsItemSelected(MenuItem item) { Log.i(TAG, "called onOptionsItemSelected; selected item: " + item); if (item == mItemPreviewOpticalFlow) { mViewMode = VIEW_MODE_OPTICAL_FLOW; resetVars(); } else if (item == mItemPreviewKLT){ mViewMode = VIEW_MODE_KLT_TRACKER; resetVars(); } return true; } private void resetVars(){ mPrevGray = new Mat(mGray.rows(), mGray.cols(), CvType.CV_8UC1); features = new MatOfPoint(); prevFeatures = new MatOfPoint2f(); nextFeatures = new MatOfPoint2f(); status = new MatOfByte(); err = new MatOfFloat(); } }
3. 修改app\src\main\res\layout\activity_main.xml文件:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout 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" android:orientation="horizontal" tools:context="net.johnhany.moaap_chp5.MainActivity"> <org.opencv.android.JavaCameraView android:layout_width="fill_parent" android:layout_height="fill_parent" android:id="@+id/main_activity_surface_view" /> </LinearLayout>
4. 在app\src\main\java\net\johnhany\moaap_chp5下新建一个HomeActivity.java文件,其内容为:
package net.johnhany.moaap_chp5; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.Button; public class HomeActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_home); Button bPyramids, bOptFlowKLT; bPyramids = (Button) findViewById(R.id.bPyramids); bOptFlowKLT = (Button) findViewById(R.id.bOptFlowKLT); bOptFlowKLT.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent i = new Intent(getApplicationContext(), MainActivity.class); startActivity(i); } }); bPyramids.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent i = new Intent(getApplicationContext(), PyramidActivity.class); startActivity(i); } }); } }
5. 在app\src\main\res\layout下新建一个activity_home.xml文件,其内容为:
<?xml version="1.0" encoding="utf-8"?> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <LinearLayout android:layout_height="wrap_content" android:layout_width="match_parent" android:orientation="vertical" > <Button android:id="@+id/bPyramids" android:layout_height="wrap_content" android:layout_width="wrap_content" android:text="@string/home_button_1" /> <Button android:id="@+id/bOptFlowKLT" android:layout_height="wrap_content" android:layout_width="wrap_content" android:text="@string/home_button_2" /> </LinearLayout> </ScrollView>
6. 在app\src\main\java\net\johnhany\moaap_chp5下新建一个PyramidActivity.java文件,文件内容为:
package net.johnhany.moaap_chp5; import android.Manifest; import android.support.v7.app.AppCompatActivity; import android.content.Intent; import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.os.Environment; import android.support.annotation.NonNull; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.Button; import android.widget.ImageView; import org.opencv.android.BaseLoaderCallback; import org.opencv.android.LoaderCallbackInterface; import org.opencv.android.OpenCVLoader; import org.opencv.android.Utils; import org.opencv.core.Core; import org.opencv.core.CvType; import org.opencv.core.Mat; import org.opencv.imgcodecs.Imgcodecs; import org.opencv.imgproc.Imgproc; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; public class PyramidActivity extends AppCompatActivity { private final int SELECT_PHOTO = 1; private ImageView ivImage; Mat src; static int ACTION_MODE = 0; static final int MODE_NONE = 0, MODE_GAUSSIAN_PYR_UP = 1, MODE_GAUSSIAN_PYR_DOWN = 2, MODE_LAPLACIAN_PYR = 3; private boolean srcSelected = false; static int REQUEST_READ_EXTERNAL_STORAGE = 0; static boolean read_external_storage_granted = false; Button bGaussianPyrUp, bGaussianPyrDown, bLaplacianPyr; private BaseLoaderCallback mOpenCVCallBack = new BaseLoaderCallback(this) { @Override public void onManagerConnected(int status) { switch (status) { case LoaderCallbackInterface.SUCCESS: //DO YOUR WORK/STUFF HERE break; default: super.onManagerConnected(status); break; } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_pyramid); if (getActionBar() != null) { getActionBar().setDisplayHomeAsUpEnabled(true); } ivImage = (ImageView)findViewById(R.id.ivImage); bGaussianPyrUp = (Button) findViewById(R.id.bGaussianPyrUp); bGaussianPyrDown = (Button) findViewById(R.id.bGaussianPyrDown); bLaplacianPyr = (Button) findViewById(R.id.bLaplacianPyr); if (ContextCompat.checkSelfPermission(PyramidActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { Log.i("permission", "request READ_EXTERNAL_STORAGE"); ActivityCompat.requestPermissions(PyramidActivity.this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, REQUEST_READ_EXTERNAL_STORAGE); }else { Log.i("permission", "READ_EXTERNAL_STORAGE already granted"); read_external_storage_granted = true; } bGaussianPyrUp.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { ACTION_MODE = MODE_GAUSSIAN_PYR_UP; executeTask(); } }); bGaussianPyrDown.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { ACTION_MODE = MODE_GAUSSIAN_PYR_DOWN; executeTask(); } }); bLaplacianPyr.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { ACTION_MODE = MODE_LAPLACIAN_PYR; executeTask(); } }); if(!srcSelected){ bGaussianPyrDown.setEnabled(false); bGaussianPyrUp.setEnabled(false); bLaplacianPyr.setEnabled(false); } } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_pyramid, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); if (id == R.id.action_load_first_image) { Intent photoPickerIntent = new Intent(Intent.ACTION_PICK); photoPickerIntent.setType("image/*"); startActivityForResult(photoPickerIntent, SELECT_PHOTO); return true; } return super.onOptionsItemSelected(item); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent imageReturnedIntent) { super.onActivityResult(requestCode, resultCode, imageReturnedIntent); switch(requestCode) { case SELECT_PHOTO: if(resultCode == RESULT_OK){ try { final Uri imageUri = imageReturnedIntent.getData(); final InputStream imageStream = getContentResolver().openInputStream(imageUri); final Bitmap selectedImage = BitmapFactory.decodeStream(imageStream); src = new Mat(selectedImage.getHeight(), selectedImage.getWidth(), CvType.CV_8UC4); Utils.bitmapToMat(selectedImage, src); srcSelected = true; bGaussianPyrUp.setEnabled(true); bGaussianPyrDown.setEnabled(true); bLaplacianPyr.setEnabled(true); } catch (FileNotFoundException e) { e.printStackTrace(); } } break; } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) { if (requestCode == REQUEST_READ_EXTERNAL_STORAGE) { // If request is cancelled, the result arrays are empty. if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // permission was granted Log.i("permission", "READ_EXTERNAL_STORAGE granted"); read_external_storage_granted = true; } else { // permission denied Log.i("permission", "READ_EXTERNAL_STORAGE denied"); } } } private void executeTask(){ if(srcSelected){ new AsyncTask<Void, Void, Bitmap>() { @Override protected void onPreExecute() { super.onPreExecute(); } @Override protected Bitmap doInBackground(Void... params) { Mat srcRes = new Mat(); switch (ACTION_MODE){ case MODE_GAUSSIAN_PYR_UP: Imgproc.pyrUp(src, srcRes); break; case MODE_GAUSSIAN_PYR_DOWN: Imgproc.pyrDown(src, srcRes); break; case MODE_LAPLACIAN_PYR: Imgproc.pyrDown(src, srcRes); Imgproc.pyrUp(srcRes, srcRes); Core.absdiff(srcRes, src, srcRes); break; } if(ACTION_MODE != MODE_NONE) { Bitmap image = Bitmap.createBitmap(srcRes.cols(), srcRes.rows(), Bitmap.Config.ARGB_8888); Utils.matToBitmap(srcRes, image); FileOutputStream out = null; try { out = new FileOutputStream(Environment.getExternalStorageDirectory() + "/Download/MOAAP/Chapter5/" + ACTION_MODE + ".png"); image.compress(Bitmap.CompressFormat.PNG, 100, out); // bmp is your Bitmap instance // image = BitmapFactory.decodeFile(Environment.getExternalStorageDirectory() + "/Download/MOAAP/Chapter5/" + ACTION_MODE + ".png"); } catch (Exception e) { e.printStackTrace(); } finally { try { if (out != null) { out.close(); } } catch (IOException e) { e.printStackTrace(); } } Imgcodecs.imwrite(Environment.getExternalStorageDirectory() + "/Download/MOAAP/Chapter5/" + ACTION_MODE + "-imwrite.png", srcRes); Mat src1 = new Mat(); Imgproc.cvtColor(srcRes, src1, Imgproc.COLOR_BGR2BGRA); Imgcodecs.imwrite(Environment.getExternalStorageDirectory() + "/Download/MOAAP/Chapter5/" + ACTION_MODE + "-imwriteBGRA.png", src1); Imgproc.cvtColor(srcRes, src1, Imgproc.COLOR_BGR2RGBA); Imgcodecs.imwrite(Environment.getExternalStorageDirectory() + "/Download/MOAAP/Chapter5/" + ACTION_MODE + "-imwriteRGBA.png", src1); // Utils.matToBitmap(src1, image); return image; } return null; } @Override protected void onPostExecute(Bitmap bitmap) { super.onPostExecute(bitmap); if(bitmap!=null) { ivImage.setImageBitmap(bitmap); } } }.execute(); } } @Override protected void onResume() { super.onResume(); OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_3_2_0, this, mOpenCVCallBack); } }
7. 在app\src\main\res\layout下新建一个activity_pyramid.xml文件,文件内容为:
<?xml version="1.0" encoding="utf-8"?> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="wrap_content"> <ImageView android:layout_width="match_parent" android:layout_height="wrap_content" android:contentDescription="@string/pyramid_img_descrip" android:visibility="visible" android:id="@+id/ivImage" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="0.5" android:id="@+id/bGaussianPyrUp" style="?android:attr/borderlessButtonStyle" android:text="@string/pyramid_button_1"/> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="0.5" android:id="@+id/bGaussianPyrDown" style="?android:attr/borderlessButtonStyle" android:text="@string/pyramid_button_2"/> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/bLaplacianPyr" android:text="@string/pyramid_button_3"/> </LinearLayout> </LinearLayout> </ScrollView>
8. 在app\src\main\res目录下创建一个名为menu的Andorid resource directory,再在res\menu中创建一个menu_main.xml文件:
<?xml version="1.0" encoding="utf-8"?> <menu> </menu>
9. 在app\src\main\res\menu目录下新建一个名为menu_pyramid.xml的文件,其内容为:
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <item android:id="@+id/action_load_first_image" android:title="@string/action_load_first_image" android:orderInCategory="1" app:showAsAction="never" /> </menu>
10. 修改app\src\main\res\AndroidManifest.xml文件为如下内容:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="net.johnhany.moaap_chp5"> <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <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" /> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true"> <activity android:name=".MainActivity" android:screenOrientation="landscape" android:theme="@style/CamTheme" android:label="@string/title_activity_main" android:parentActivityName=".HomeActivity" > <!-- Parent activity meta-data to support 4.0 and lower --> <meta-data android:name="android.support.PARENT_ACTIVITY" android:value=".HomeActivity" /> </activity> <activity android:name=".HomeActivity" android:theme="@style/AppTheme" android:label="@string/title_activity_home"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".PyramidActivity" android:label="@string/title_activity_pyramid" android:theme="@style/AppTheme" android:parentActivityName=".HomeActivity" > <!-- Parent activity meta-data to support 4.0 and lower --> <meta-data android:name="android.support.PARENT_ACTIVITY" android:value=".HomeActivity" /> </activity> </application> </manifest>
11. 修改app\src\main\res\values\strings.xml文件:
<resources> <string name="app_name">第五章 - 深入OpenCV Android应用开发</string> <string name="home_button_1">Image Pyramids</string> <string name="home_button_2">Optical Flow and KLT Tracker</string> <string name="pyramid_img_descrip">Image View</string> <string name="pyramid_button_1">Gaussian Pyramid Up</string> <string name="pyramid_button_2">Gaussian Pyramid Down</string> <string name="pyramid_button_3">Laplacian Pyramid</string> <string name="action_load_first_image">Load Image</string> <string name="title_activity_main">第五章 - 深入OpenCV Android应用开发</string> <string name="title_activity_pyramid">第五章 - 深入OpenCV Android应用开发</string> <string name="title_activity_home">第五章 - 深入OpenCV Android应用开发</string> </resources>
12. 修改app\src\main\res\values\styles.xml文件:
<resources> <!-- Base application theme. --> <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <!-- Customize your theme here. --> <item name="android:windowActionBar">true</item> <item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorAccent">@color/colorAccent</item> </style> <style name="CamTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <!-- Customize your theme here. --> <item name="android:windowNoTitle">true</item> <item name="android:windowActionBar">true</item> <item name="android:windowFullscreen">true</item> <item name="android:windowContentOverlay">@null</item> <item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorAccent">@color/colorAccent</item> </style> </resources>
13. app\build.gradle文件修改为:
apply plugin: 'com.android.application' android { compileSdkVersion 24 buildToolsVersion "26.0.1" defaultConfig { applicationId "net.johnhany.moaap_chp5" minSdkVersion 16 targetSdkVersion 24 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { compile fileTree(include: ['*.jar'], dir: 'libs') androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) compile 'com.android.support:appcompat-v7:24.2.1' compile 'com.android.support.constraint:constraint-layout:1.0.2' testCompile 'junit:junit:4.12' compile project(':openCVLibrary320') }
14. openCVLibrary320\build.gradle文件修改为:
apply plugin: 'com.android.library' android { compileSdkVersion 24 buildToolsVersion "26.0.1" defaultConfig { minSdkVersion 16 targetSdkVersion 24 } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' } } }
15. 检查一下项目根目录的build.gradle文件是否为如下内容:
// Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:2.3.3' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } } allprojects { repositories { jcenter() } } task clean(type: Delete) { delete rootProject.buildDir }
运行效果
右上角的菜单选择Optical Flow,移动手机时,在画面上局部像素的运动会以下图中的白线表示出来。
选择KLT Tracker,则程序会在第一帧图像中选取若干追踪点,并在移动镜头时进行追踪。如果追踪点超出画面的范围,就会造成追踪失败,需要再一次点击KLT Tracker开始新的追踪。
光流的点有点少,怎么取消采样
200行后面建议加上两句:
Core.normalize(srcResult,srcResult,0,255,Core.NORM_MINMAX);
Core.add(srcResult,new Mat(srcResult.rows(),srcResult.cols(),CvType.CV_8UC4, new Scalar(0,0,0,255)),srcResult);
一个是增加对比度,一个是把Alpha值加回去
在我的机器上scrRes格式为RGBA
224行:imwrite 执行后,文件浏览呈反色
227行: Imgproc.cvtColor(srcRes, src1, Imgproc.COLOR_BGR2BGRA) 执行后,scr1 为BGRA,imwrite 执行后,文件浏览正常彩色。(此处不应该啊BGR to BGRA不应该出现调换啊)
230行:Imgproc.cvtColor(srcRes, src1, Imgproc.COLOR_BGR2RGBA)执行后,scr1为RGBA和scrRes格式一样。(此处也不正常该调换的又没调换)
难道OPENCV源码可以自己纠正到底是RGBA还是BGRA?我是这么写的:
Imgcodecs.imwrite(Environment.getExternalStorageDirectory() + "/Download/MOAAP/Chapter5/" + ACTION_MODE + "-imwrite.png", srcRes);
Mat src1 = new Mat();
Imgproc.cvtColor(srcRes, src1, Imgproc.COLOR_RGBA2BGRA);
Imgcodecs.imwrite(Environment.getExternalStorageDirectory() + "/Download/MOAAP/Chapter5/" + ACTION_MODE + "-imwriteBGRA.png", src1);
230 ,231行去掉