之前博文《Android学习笔记之——基于Android的opencv开发(Android studio3.6+opencv4.3.0开发环境搭建)》以及《Android学习笔记之——opencv开发进一步探索》已经介绍了基于Android的opencv开发,本博文继续学习一下
目录
灰度化处理
Mat 与Bitmap 对象
基于opencv的模糊处理、二值化、Canny边缘检测、ROI提取、直方图等代码
参考资料
先给出《Android学习笔记之——opencv开发进一步探索》中代码的解读。详细请见代码注释
package com.example.androidopencvtest;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
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.CvType;
import org.opencv.core.Mat;
import org.opencv.imgproc.Imgproc;
import org.opencv.android.CameraActivity;
import java.io.InputStream;
public class MainActivity extends AppCompatActivity {
private double max_size = 1024;
private int PICK_IMAGE_REQUEST = 1;
private ImageView myImageView;//通过ImageView来显示结果
private Bitmap selectbp;//所选择的bitmap
//相机权限获取
private static String[] PERMISSIONS_STORAGE = {
Manifest.permission.WRITE_EXTERNAL_STORAGE,//写权限
Manifest.permission.CAMERA//照相权限
};
//初始化
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// staticLoadCVLibraries();//静态注册opencv
////////////////////////////华为手机摄像头权限申请************************
//用于判断SDK版本是否大于23
if (Build.VERSION.SDK_INT>= Build.VERSION_CODES.M){
//检查权限
int i = ContextCompat.checkSelfPermission(this,PERMISSIONS_STORAGE[0]);
//如果权限申请失败,则重新申请权限
if(i!= PackageManager.PERMISSION_GRANTED){
//重新申请权限函数
startRequestPermission();
Log.e("这里","权限请求成功");
}
}
myImageView = (ImageView)findViewById(R.id.imageView);
myImageView.setScaleType(ImageView.ScaleType.FIT_CENTER);//设置显示图片的属性。把图片按比例扩大/缩小到View的宽度,居中显示
// 可参考(https://www.cnblogs.com/llm-android/archive/2012/02/19/2357821.html)
Button selectImageBtn = (Button)findViewById(R.id.select_btn);//定义一个按钮来选择图片
selectImageBtn.setOnClickListener(new View.OnClickListener() {//定义选择图片的监听器
@Override
public void onClick(View v) {
// makeText(MainActivity.this.getApplicationContext(), "start to browser image", Toast.LENGTH_SHORT).show();
selectImage();//调用选择图片的函数
}
//从手机的相册中选择图片
private void selectImage() {
Intent intent = new Intent();
intent.setType("image/*");
intent.setAction(Intent.ACTION_GET_CONTENT);//允许用户选择特殊种类的数据,并返回(特殊种类的数据:照一张相片或录一段音)
startActivityForResult(Intent.createChooser(intent,"选择图像..."), PICK_IMAGE_REQUEST);//启动另外一个活动
}
});
//定义处理的按钮
Button processBtn = (Button)findViewById(R.id.process_btn);
processBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {//定义按钮监听器
// makeText(MainActivity.this.getApplicationContext(), "hello, image process", Toast.LENGTH_SHORT).show();
convertGray();//灰度转换
}
//灰度转换函数
private void convertGray() {
Mat src = new Mat();
Mat temp = new Mat();
Mat dst = new Mat();
Utils.bitmapToMat(selectbp, src);//将位图转换为Mat数据。而对于位图,其由A、R、G、B通道组成
Imgproc.cvtColor(src, temp, Imgproc.COLOR_BGRA2BGR);//转换为BGR(opencv中数据存储方式)
Log.i("CV", "image type:" + (temp.type() == CvType.CV_8UC3));
Imgproc.cvtColor(temp, dst, Imgproc.COLOR_BGR2GRAY);//灰度化处理。
Utils.matToBitmap(dst, selectbp);//再将mat转换为位图
myImageView.setImageBitmap(selectbp);//显示位图
}
});
}
//免安装Opencv manager
//onResume()这个方法在活动准备好和用户进行交互的时候调用。此时的活动一定位于返回栈的栈顶,并且处于运行状态。
//所以在活动开启前调用,检查是否有opencv库,若没有,则下载
@Override
protected void onResume() {
super.onResume();
//免安装opencv manager(opencv3.0开始可以采用这种方法)
if (!OpenCVLoader.initDebug()) {
System.out.println("Internal OpenCV library not found. Using OpenCV Manager for initialization");
OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_3_4_0, this, mLoaderCallback);
} else {
System.out.println("OpenCV library found inside package. Using it!");
mLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS);
}
}
// OpenCV库加载并初始化成功后的回调函数
private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) {
@Override
public void onManagerConnected(int status) {
switch (status) {
case LoaderCallbackInterface.SUCCESS: {
System.out.println("OpenCV loaded successfully");
}
break;
default: {
super.onManagerConnected(status);
}
break;
}
}
};
//静态注册opencv发表方法(应该是旧版的opencv采用的方法)
// private void staticLoadCVLibraries() {
// boolean load = OpenCVLoader.initDebug();
// if(load) {//注册成功后,输出
// Log.i("CV", "Open CV Libraries loaded...");
// }
//
// }
//在一个主界面(主Activity)通过意图(startActivityForResult)跳转至多个不同子Activity上去,
// 当子模块的代码执行完毕后再次返回主页面,将子activity中得到的数据显示在主界面/完成的数据交给主Activity处理。
// 这种带数据的意图跳转需要使用activity的onActivityResult()方法
//note:点击完选择图片按钮后,应该进入的是这里的选项
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
//requestCode 最初提供给startActivityForResult()的整数请求代码,允许您识别此结果的来源。
//整数requestCode用于与startActivityForResult中的requestCode中值进行比较判断,是以便确认返回的数据是从哪个Activity返回的。
//resultCode 子活动通过其setResult()返回的整数结果代码。适用于多个activity都返回数据时,来标识到底是哪一个activity返回的值。
//data。一个Intent对象,带有返回的数据。可以通过data.getXxxExtra( );方法来获取指定数据类型的数据,
super.onActivityResult(requestCode, resultCode, data);
if(requestCode == PICK_IMAGE_REQUEST && resultCode == RESULT_OK && data != null && data.getData() != null) {//当为选择image这个意图时,进入下面代码。选择图片以位图的形式显示出来
Uri uri = data.getData();
try {
Log.d("image-tag", "start to decode selected image now...");
InputStream input = getContentResolver().openInputStream(uri);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(input, null, options);
int raw_width = options.outWidth;
int raw_height = options.outHeight;
int max = Math.max(raw_width, raw_height);
int newWidth = raw_width;
int newHeight = raw_height;
int inSampleSize = 1;
if(max > max_size) {
newWidth = raw_width / 2;
newHeight = raw_height / 2;
while((newWidth/inSampleSize) > max_size || (newHeight/inSampleSize) > max_size) {
inSampleSize *=2;
}
}
options.inSampleSize = inSampleSize;
options.inJustDecodeBounds = false;
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
selectbp = BitmapFactory.decodeStream(getContentResolver().openInputStream(uri), null, options);
myImageView.setImageBitmap(selectbp);//将所选择的位图显示出来
} catch (Exception e) {
e.printStackTrace();
}
}
}
private void startRequestPermission(){
//321为请求码
ActivityCompat.requestPermissions(this,PERMISSIONS_STORAGE,321);
}
}
效果如下图所示。
Mat 是OpenCV 中用来存储图像信息的内存对象,当通过Imgcodecs. imread ()方法从文件读入一个图像文件时·, imread 方法就会返回Mat 对象实例,或者通过Utils.bitmatToMat() 方法由Bitmap 对象转换而来
由于opencv中大多数是直接调用函数。为此,此处demo调用各种函数,这几个方法的原理百度就很多了~这里就不深入介绍了。直接给出代码
package com.example.androidopencvtest;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
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.core.MatOfFloat;
import org.opencv.core.MatOfInt;
import org.opencv.core.Point;
import org.opencv.core.Rect;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.imgproc.Imgproc;
import org.opencv.android.CameraActivity;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private double max_size = 1024;
private int PICK_IMAGE_REQUEST = 1;
private ImageView myImageView;//通过ImageView来显示结果
private Bitmap selectbp;//所选择的bitmap
//相机权限获取
private static String[] PERMISSIONS_STORAGE = {
Manifest.permission.WRITE_EXTERNAL_STORAGE,//写权限
Manifest.permission.CAMERA//照相权限
};
//初始化
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// staticLoadCVLibraries();//静态注册opencv
////////////////////////////华为手机摄像头权限申请************************
//用于判断SDK版本是否大于23
if (Build.VERSION.SDK_INT>= Build.VERSION_CODES.M){
//检查权限
int i = ContextCompat.checkSelfPermission(this,PERMISSIONS_STORAGE[0]);
//如果权限申请失败,则重新申请权限
if(i!= PackageManager.PERMISSION_GRANTED){
//重新申请权限函数
startRequestPermission();
Log.e("这里","权限请求成功");
}
}
myImageView = (ImageView)findViewById(R.id.imageView);
myImageView.setScaleType(ImageView.ScaleType.FIT_CENTER);//设置显示图片的属性。把图片按比例扩大/缩小到View的宽度,居中显示
// 可参考(https://www.cnblogs.com/llm-android/archive/2012/02/19/2357821.html)
Button selectImageBtn = (Button)findViewById(R.id.select_btn);//定义一个按钮来选择图片
selectImageBtn.setOnClickListener(new View.OnClickListener() {//定义选择图片的监听器
@Override
public void onClick(View v) {
// makeText(MainActivity.this.getApplicationContext(), "start to browser image", Toast.LENGTH_SHORT).show();
selectImage();//调用选择图片的函数
}
//从手机的相册中选择图片
private void selectImage() {
Intent intent = new Intent();
intent.setType("image/*");
intent.setAction(Intent.ACTION_GET_CONTENT);//允许用户选择特殊种类的数据,并返回(特殊种类的数据:照一张相片或录一段音)
startActivityForResult(Intent.createChooser(intent,"选择图像..."), PICK_IMAGE_REQUEST);//启动另外一个活动
}
});
//定义处理的按钮
Button processBtn = (Button)findViewById(R.id.process_btn);
processBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {//定义按钮监听器
// makeText(MainActivity.this.getApplicationContext(), "hello, image process", Toast.LENGTH_SHORT).show();
convertGray();//灰度转换
}
//灰度转换函数
private void convertGray() {
Mat src = new Mat();
Mat temp = new Mat();
Mat dst = new Mat();
Utils.bitmapToMat(selectbp, src);//将位图转换为Mat数据。而对于位图,其由A、R、G、B通道组成
Imgproc.cvtColor(src, temp, Imgproc.COLOR_BGRA2BGR);//转换为BGR(opencv中数据存储方式)
Log.i("CV", "image type:" + (temp.type() == CvType.CV_8UC3));
Imgproc.cvtColor(temp, dst, Imgproc.COLOR_BGR2GRAY);//灰度化处理。
Bitmap selectbp1 = Bitmap.createBitmap(src.width(), src.height(), Bitmap.Config.ARGB_8888) ;
Utils.matToBitmap(dst, selectbp1);//再将mat转换为位图
myImageView.setImageBitmap(selectbp1);//显示位图
}
});
//模糊
//定义处理的按钮
Button processBtn_blur = (Button)findViewById(R.id.process_btn_blur);
processBtn_blur.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {//定义按钮监听器
// makeText(MainActivity.this.getApplicationContext(), "hello, image process", Toast.LENGTH_SHORT).show();
BlurProcess();//灰度转换
}
//灰度转换函数
private void BlurProcess() {
Mat src = new Mat();
Mat temp = new Mat();
Mat dst = new Mat();
Utils.bitmapToMat(selectbp, src);//将位图转换为Mat数据。而对于位图,其由A、R、G、B通道组成
Imgproc.cvtColor(src, temp, Imgproc.COLOR_BGRA2BGR);//转换为BGR(opencv中数据存储方式)
Imgproc.blur(temp,dst,new Size(11,11));
Bitmap selectbp2 = Bitmap.createBitmap(src.width(), src.height(), Bitmap.Config.ARGB_8888) ;
Utils.matToBitmap(dst, selectbp2);//再将mat转换为位图
myImageView.setImageBitmap(selectbp2);//显示位图
}
});
//二值化
//定义处理的按钮
Button processBtn_Binarization = (Button)findViewById(R.id.Binarization);
processBtn_Binarization.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {//定义按钮监听器
// makeText(MainActivity.this.getApplicationContext(), "hello, image process", Toast.LENGTH_SHORT).show();
BinarizationProcess();//二值化
}
//灰度转换函数
private void BinarizationProcess() {
Mat src = new Mat();
Mat temp = new Mat();
Mat dst = new Mat();
Utils.bitmapToMat(selectbp, src);//将位图转换为Mat数据。而对于位图,其由A、R、G、B通道组成
Imgproc.cvtColor(src, temp, Imgproc.COLOR_BGRA2BGR);//转换为BGR(opencv中数据存储方式)
Imgproc.cvtColor(temp, temp, Imgproc.COLOR_BGR2GRAY);//灰度化处理。
// Imgproc.adaptiveThreshold(temp,dst,255,Imgproc.ADAPTIVE_THRESH_MEAN_C,Imgproc.THRESH_BINARY,15,10);
Imgproc.threshold(temp,dst,50,255,Imgproc.THRESH_BINARY);
Bitmap selectbp2 = Bitmap.createBitmap(src.width(), src.height(), Bitmap.Config.ARGB_8888) ;
Utils.matToBitmap(dst, selectbp2);//再将mat转换为位图
myImageView.setImageBitmap(selectbp2);//显示位图
}
});
//canny
//定义处理的按钮
Button processBtn_canny = (Button)findViewById(R.id.canny);
processBtn_canny.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {//定义按钮监听器
// makeText(MainActivity.this.getApplicationContext(), "hello, image process", Toast.LENGTH_SHORT).show();
cannyProcess();
}
//灰度转换函数
private void cannyProcess() {
Mat src = new Mat();
Mat temp = new Mat();
Mat dst = new Mat();
Utils.bitmapToMat(selectbp, src);//将位图转换为Mat数据。而对于位图,其由A、R、G、B通道组成
Imgproc.cvtColor(src, src, Imgproc.COLOR_BGRA2BGR);//转换为BGR(opencv中数据存储方式)
//先进行高斯模糊
Imgproc.GaussianBlur(src,src,new Size(3,3),0);
Mat gray=new Mat();
Mat edges=new Mat();
//转换为灰度图
Imgproc.cvtColor(src,gray,Imgproc.COLOR_BGR2GRAY);
Imgproc.Canny(src,edges,50,150,3,true);
Core.bitwise_and(src,src,dst,edges);
Bitmap selectbp2 = Bitmap.createBitmap(src.width(), src.height(), Bitmap.Config.ARGB_8888) ;
Utils.matToBitmap(dst, selectbp2);//再将mat转换为位图
myImageView.setImageBitmap(selectbp2);//显示位图
}
});
//直方图
//定义处理的按钮
Button processBtn_histogram = (Button)findViewById(R.id.histogram);
processBtn_histogram.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {//定义按钮监听器
// makeText(MainActivity.this.getApplicationContext(), "hello, image process", Toast.LENGTH_SHORT).show();
histogramProcess();
}
//处理
private void histogramProcess() {
Mat src = new Mat();
Mat temp = new Mat();
Mat dst = new Mat();
Utils.bitmapToMat(selectbp, src);//将位图转换为Mat数据。而对于位图,其由A、R、G、B通道组成
Imgproc.cvtColor(src, src, Imgproc.COLOR_BGRA2BGR);//转换为BGR(opencv中数据存储方式)
//进行直方图绘制
displayHistogram(src,dst);
Bitmap selectbp2 = Bitmap.createBitmap(dst.width(), dst.height(), Bitmap.Config.ARGB_8888) ;
Utils.matToBitmap(dst, selectbp2);//再将mat转换为位图
myImageView.setImageBitmap(selectbp2);//显示位图
}
private void displayHistogram(Mat src, Mat dst){
Mat gray=new Mat();
Imgproc.cvtColor(src,gray,Imgproc.COLOR_BGR2GRAY);//转换为灰度图
//计算直方图数据并归一化
List images=new ArrayList<>();
images.add(gray);
Mat mask=Mat.ones(src.size(),CvType.CV_8UC1);
Mat hist=new Mat();
Imgproc.calcHist(images,new MatOfInt(0),mask,hist,new MatOfInt(256),new MatOfFloat(0,255));
Core.normalize(hist,hist,0,255,Core.NORM_MINMAX);
int height=hist.rows();
dst.create(400,400,src.type());
dst.setTo(new Scalar(200,200,200));
float[] histdata=new float[256];
hist.get(0,0,histdata);
int offsetx=50;
int offsety=350;
//绘制直方图
Imgproc.line(dst,new Point(offsetx,0),new Point(offsetx,offsety),new Scalar(0,0,0));
Imgproc.line(dst,new Point(offsetx,offsety),new Point(400,offsety),new Scalar(0,0,0));
for(int i=0;i images=new ArrayList<>();
images.add(gray);
Mat mask=Mat.ones(gray.size(),CvType.CV_8UC1);
Mat hist=new Mat();
Imgproc.calcHist(images,new MatOfInt(0),mask,hist,new MatOfInt(256),new MatOfFloat(0,255));
Core.normalize(hist,hist,0,255,Core.NORM_MINMAX);
int height=hist.rows();
dst.create(400,400,gray.type());
dst.setTo(new Scalar(200,200,200));
float[] histdata=new float[256];
hist.get(0,0,histdata);
int offsetx=50;
int offsety=350;
//绘制直方图
Imgproc.line(dst,new Point(offsetx,0),new Point(offsetx,offsety),new Scalar(0,0,0));
Imgproc.line(dst,new Point(offsetx,offsety),new Point(400,offsety),new Scalar(0,0,0));
for(int i=0;i max_size) {
newWidth = raw_width / 2;
newHeight = raw_height / 2;
while((newWidth/inSampleSize) > max_size || (newHeight/inSampleSize) > max_size) {
inSampleSize *=2;
}
}
options.inSampleSize = inSampleSize;
options.inJustDecodeBounds = false;
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
selectbp = BitmapFactory.decodeStream(getContentResolver().openInputStream(uri), null, options);
myImageView.setImageBitmap(selectbp);//将所选择的位图显示出来
} catch (Exception e) {
e.printStackTrace();
}
}
}
private void startRequestPermission(){
//321为请求码
ActivityCompat.requestPermissions(this,PERMISSIONS_STORAGE,321);
}
}
https://www.cnblogs.com/tail/p/4618790.html(Android Studio使用OpenCV后,使APP不安装OpenCV Manager即可运行)
https://www.cnblogs.com/llm-android/archive/2012/02/19/2357821.html(ImageView.setScaleType)
https://www.cnblogs.com/fuck1/p/5456337.html(关于Android的startActivityForResult()与onActivityResult()与setResult()参数分析,activity带参数的返回)
https://blog.csdn.net/poorkick/article/details/103979399