文章目录
通过计算方差判断图像是否模糊
所需依赖
- 图像加载库:Glide
在gradle.properties 文件中添加
implementation 'com.github.bumptech.glide:glide:4.12.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
-
OpenCV
下载opencv的安卓sdk并导入。
注意,Android Studio升级Artic Fox 2020 3.1 之后从Import Module导入会有些问题,需要手动将sdk拖入项目中。
参考:https://stackoverflow.com/questions/68649524/opencv-android-studio-module-importing-issue
思路描述
-
打开相机拍照;
-
拍照结束后计算图像方差;
-
根据预先设定的阈值判断图像是清晰还是模糊;
-
使用对话框显示判断结果。
实现前的准备
异步调用opencv的方法很多,由于前不久尝试过使用模板实现图像质量检测的demo,所以这里也沿用之前写的模板方法。
模板
模板的重点在于,使用顶层的抽象类固定化方法的执行顺序,使得继承相同模板的对象具有相似的内部函数执行逻辑。
即,首先检查传入的图像是否有效,在执行模板子类、通过接口自定义的方法。
public abstract class DetectionTemplate {
protected Bitmap srcBitmap = null;
/**
* 模板方法
*/
public final void detecting(IConstract.ICommon doAfter){
if (doCheckValid()){
System.out.println("开始检测");
doDetect();
doAfter.onDoSomething();
}else{
System.out.println("未开始检测");
}
// 钩子方法
hook();
}
/**
* 钩子方法:
* 一个钩子方法由抽象类声明并实现,而子类会加以扩展。
* 通常抽象类给出的实现是一个空实现,作为方法的默认实现。
*/
protected void hook(){
}
/**
* 基本方法留给子类实现:
* 在检测前执行, 验证参数是否正确
*/
protected abstract boolean doCheckValid();
/**
* 基本方法留给子类实现:
* 在检测后执行
*/
// protected abstract void doAfterDetecting();
protected abstract void doDetect();
}
模板子类
/**
* 图像模糊度检测
*/
public class BlurDetect extends DetectionTemplate{
private static final String TAG = "BlurDetect";
public double sqDev = 0.0;
public String strBlurDescribe = "";
public BlurDetect(final Bitmap bitmap){
this.srcBitmap = bitmap;
}
@Override
protected boolean doCheckValid() {
return srcBitmap != null;
}
@Override
protected void doDetect() {
// 获取标准差
sqDev = OpenCvUtil.getSquareDeviation(srcBitmap);
// 保留两位小数
sqDev = NumberUtil.reserveDecimal2(sqDev);
sqDev = (int)(sqDev*100)/100.0;
strBlurDescribe = "模糊度: " + sqDev;
Log.d(TAG, "计算结束: "+strBlurDescribe);
}
}
接口
接口不是必要的,但是将自定义的接口注入模板中可以使逻辑更清晰。
/**
* 接口汇总
*/
public interface IConstract {
/**
* 通用接口
*/
interface ICommon{
void onDetected();
}
}
工具类
OpenCvUtil
/**
* 自定义Opencv工具类
* 包含:
* Mat与Bitmap相互转换
* 获取方差和标准差
* 根据阈值判断模糊度
*/
public class OpenCvUtil {
private static final String TAG = "OpenCvUtil";
// 模糊度阈值
private static final int BLUR_THRESHOLD500 = 500;
private static final int BLUR_THRESHOLD300 = 300;
private static final int BLUR_THRESHOLD50 = 50;
private static final int BLUR_THRESHOLD15 = 15;
/**
* Mat 转Bitmap
* @param mat
* @return
*/
public static Bitmap matToBitmap(Mat mat){
Bitmap result = null;
if (mat != null){
result = Bitmap.createBitmap(mat.cols(), mat.rows(), Bitmap.Config.RGB_565);
if (result != null){
Utils.matToBitmap(mat, result);
}
}
return result;
}
/**
* Bitmap 转 Mat
* @param bitmap
* @return Mat
*/
public static Mat bitmapToMat(Bitmap bitmap){
Mat result = null;
Bitmap bmp32 = bitmap.copy(Bitmap.Config.RGB_565, true);
result = new Mat(bitmap.getHeight(), bitmap.getWidth(), CvType.CV_8UC2, new Scalar(0));
Utils.bitmapToMat(bmp32, result);
return result;
}
/**
* 计算图像标准差
* @param bitmap
* @return
*/
public static double getStandardDeviation(Bitmap bitmap){
double result = 0.0;
if (bitmap != null){
result = getStdDev(bitmap);
}
return result;
}
/**
* 计算图像方差
* @param bitmap
* @return
*/
public static double getSquareDeviation(Bitmap bitmap){
double result = 0.0;
if (bitmap != null){
result = getStdDev(bitmap);
}
return result * result;
}
/**
* 输入标准差,判断图像是否清晰
* @param st
* @param tv
* @param context
*/
public static void judgeBlurByStdDev(final double st, final TextView tv,final Context context){
judgeBlurBySquDev(st*st, tv, context);
}
/**
* 通过方差判断
* @param sq
* @param tv
* @param context
*/
public static void judgeBlurBySquDev(final double sq, final TextView tv,final Context context){
tv.post(new Runnable() {
@Override
public void run() {
StringBuilder sb = new StringBuilder("模糊度: ");
double tempSt = sq;
// 标准差 -> 方差
// double tempSt = st * st;
sb.append(tempSt).append("\t");
// 颜色可以自行设置
if (tempSt > BLUR_THRESHOLD500){
sb.append("清晰");
tv.setTextColor(context.getColor(R.color.colorGreen));
}else if(tempSt > BLUR_THRESHOLD300){
sb.append("不清晰");
tv.setTextColor(context.getColor(R.color.indianred));
}else if(tempSt > BLUR_THRESHOLD50){
sb.append("很不清晰 ");
tv.setTextColor(context.getColor(R.color.indianred));
}else if (tempSt > BLUR_THRESHOLD15){
sb.append("非常不清晰 ");
tv.setTextColor(context.getColor(R.color.colorYellow));
}else{
sb.append("完全看不清了 ");
tv.setTextColor(context.getColor(R.color.red));
}
Log.d(TAG, "run: "+sb);
tv.setText(sb);
}
});
}
/**
* 获取模糊位图
* @param srcBitmap
* @return
*/
public static Bitmap getBlurBitmap(final Bitmap srcBitmap){
Mat srcImage = bitmapToMat(srcBitmap);
Mat blurImage = new Mat();
blur(srcImage, blurImage, new Size(3, 3));
return matToBitmap(blurImage);
}
// region tool
/**
* 获取标准差
* @param bitmap
* @return double 标准差
*/
private static double getStdDev(Bitmap bitmap) {
Mat matSrc = bitmapToMat(bitmap);
Mat mat = new Mat();
int channel = matSrc.channels();
System.out.println("getStdDev: channel = " + channel);
// 1表示图像是灰度图
if (channel != 1){
cvtColor(matSrc, mat, COLOR_BGR2GRAY);
}else{
mat = matSrc;
}
Mat lap = new Mat();
Laplacian(mat, lap, CV_64F);
MatOfDouble s = new MatOfDouble();
meanStdDev(lap, new MatOfDouble(), s);
double st = s.get(0, 0)[0];
System.out.println( "getStdDev: st = " + st);
// Log.d(TAG, "getStdDev: s.get(0,0)[0] = "+s.get(0,0)[0]);
return st;
}
// endregion
}
BmpUtil
/**
* 自定义位图工具
* 包含:
* 从ImageView获取位图
* 加载位图
*/
public class BmpUtil {
/**
*
* @param imageView 图像控件
* @return Bitmap
*/
public static Bitmap getBitmapFromImageView(ImageView imageView){
Bitmap result = null;
imageView.setDrawingCacheEnabled(true);
result = Bitmap.createBitmap(imageView.getDrawingCache());
imageView.setDrawingCacheEnabled(false);
return result;
}
/**
* 加载位图
* @param uri
* @param imageView
*/
public static void loadBitmap(final Uri uri, final ImageView imageView) {
if (null == uri) {
return;
}
Glide.with(imageView)
.load(uri)
.fitCenter()
.placeholder(R.drawable.ic_launcher_foreground)
.into(imageView);
}
/**
* 加载位图
* @param bitmap
* @param imageView
*/
public static void loadBitmap(final Bitmap bitmap, final ImageView imageView) {
if (null == bitmap) {
return;
}
Glide.with(imageView)
.load(bitmap)
.fitCenter()
.placeholder(R.drawable.ic_launcher_foreground)
.into(imageView);
}
}
CameraUtil
相机权限申请参考官方文档。
拍照 | Android 开发者 | Android Developers (google.cn)中提到:
请注意,
startActivityForResult()
方法受调用resolveActivity()
(返回可处理 Intent 的第一个 Activity 组件)的条件保护。执行此检查非常重要,因为如果您使用任何应用都无法处理的 Intent 调用startActivityForResult()
,您的应用就会崩溃。所以只要结果不是 Null,就可以放心使用 Intent。
真机上可以正常运行,但是在模拟器上,这个返回值却总是为null导致相机无法打开。
为了解决这个问题,可以再加一个条件。
CameraManager cameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
String[] cameraIds = cameraManager.getCameraIdList();
if (cameraIds.length > 0) {
//摄像头存在
if (cameraIds[0] != null || cameraIds[1] != null) {
isCamera = true;
}
}
检查手机是否有摄像头,当 isCamera
为 true
时同样跳转到拍照界面。
/**
* 相机帮助类
*/
public class CameraUtil {
private static final String TAG = "CameraUtil";
public static final int INT_OPEN_CAMERA = 1001;
// 检查是否有摄像头
private static boolean isCamera = false;
/**
* 打开相机
* @param from
*/
@SuppressLint("QueryPermissionsNeeded")
public static void openCamera(Activity from){
if (!isCamera){
try {
CameraManager cameraManager = (CameraManager) from.getSystemService(Context.CAMERA_SERVICE);
String[] cameraIds = cameraManager.getCameraIdList();
if (cameraIds.length > 0) {
//摄像头存在
if (cameraIds[0] != null || cameraIds[1] != null) {
isCamera = true;
}
}
} catch (IllegalStateException | CameraAccessException e) {
e.printStackTrace();
}
}
Intent intentCamera = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (intentCamera.resolveActivity(from.getPackageManager()) != null || isCamera) {
from.startActivityForResult(intentCamera, INT_OPEN_CAMERA);
} else {
Log.d(TAG, "onClick: 打开失败");
}
}
}
布局文件
字体、颜色、背景图片之类的资源文件可以自行修改。
activity_blur_detect_page.xml 界面
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/cd_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@drawable/app_background11">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!-- <View-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="2dp"-->
<!-- android:background="@drawable/divider"/>-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
>
<LinearLayout
android:id="@+id/ll_nowImage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/round_lieaner_layout"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:text="选择图像:"
android:textSize="@dimen/popup_item"
android:lines="1"
android:textColor="@color/black"/>
<Spinner
android:id="@+id/sp_imageSelector"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/ll_image"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
>
<ImageButton
android:id="@+id/ib_imageShow"
android:layout_width="250dp"
android:layout_height="250dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="7dp"
android:scaleType="fitCenter"
android:background="@color/translate"
android:src="@drawable/drill_image_01"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="90dp"
android:orientation="horizontal"
android:gravity="center"
android:layout_marginTop="5dp"
android:layout_gravity="center">
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:text="@string/bottomDeepthUnit"
android:textSize="15sp"
android:textColor="@color/white"
android:gravity="center|bottom"/>
<TextView
android:id="@+id/tv_bottomDeepth"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:textSize="15sp"
android:textColor="@color/white"
android:gravity="center"/>
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:text="@string/layerNumber"
android:textSize="15sp"
android:textColor="@color/white"
android:gravity="center|bottom"/>
<TextView
android:id="@+id/tv_layer"
android:layout_width="match_parent"
android:layout_weight="1"
android:layout_height="0dp"
android:textSize="15sp"
android:textColor="@color/white"
android:gravity="center"/>
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:text="@string/horizontalSizeUnit"
android:textSize="15sp"
android:textColor="@color/white"
android:gravity="center|bottom"/>
<TextView
android:id="@+id/tv_sizeHorizontal"
android:layout_width="match_parent"
android:layout_weight="1"
android:layout_height="0dp"
android:textSize="15sp"
android:textColor="@color/white"
android:gravity="center"/>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/tv_reminder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="打开相机,拍摄第X箱"
android:textSize="20sp"
android:textColor="@color/white"
android:visibility="invisible"
android:gravity="center_horizontal"/>
<Button
android:id="@+id/btn_openCamera"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginTop="20dp"
android:layout_marginRight="15dp"
android:text="拍照"
android:textSize="25sp"
android:background="@drawable/round_btn_normal"/>
<Button
android:id="@+id/btn_phone_camera"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginTop="20dp"
android:layout_marginRight="15dp"
android:text="打开手机相机"
android:textSize="25sp"
android:onClick="openPhoneCamera"
android:background="@drawable/round_btn_normal"
android:visibility="gone"/>
<Button
android:id="@+id/btn_back"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginTop="20dp"
android:layout_marginRight="15dp"
android:text="返回"
android:textSize="25sp"
android:background="@drawable/round_btn_normal"/>
</LinearLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>
dialog_blur.xml 对话框
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/tv_head"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="@dimen/text_common_content"
android:text="检测结果仅作参考"
android:textColor="@color/black"
android:gravity="center"
android:layout_gravity="center"/>
<TextView
android:id="@+id/tv_blur"
android:layout_width="match_parent"
android:layout_height="45dp"
android:textSize="@dimen/text_common_btn"
android:background="@color/translate"
/>
</LinearLayout>
实现
/*
* author: hgx
* time: 2021/10/19 10:23
* describe: 拍照后检测照片是否模糊的demo
*/
public class BlurDetectPageActivity extends AppCompatActivity {
private static final String TAG = "BlurDetectPageActivity";
ActivityBlurDetectPageBinding binding;
private Mat imageMat;
private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) {
@Override
public void onManagerConnected(int status) {
switch (status) {
case LoaderCallbackInterface.SUCCESS: {
Log.i("OpenCV", "OpenCV loaded successfully");
imageMat = new Mat();
}
break;
default: {
super.onManagerConnected(status);
}
break;
}
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.activity_blur_detect_page);
setBtnListener();
}
private void setBtnListener() {
// 拍照
binding.btnOpenCamera.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
CameraUtil.openCamera(BlurDetectPageActivity.this);
}
});
}
@Override
protected void onResume() {
super.onResume();
if (!OpenCVLoader.initDebug()) {
Log.d("OpenCV", "Internal OpenCV library not found. Using OpenCV Manager for initialization");
OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_3_0_0, this, mLoaderCallback);
} else {
Log.d("OpenCV", "OpenCV library found inside package. Using it!");
mLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
Log.d(TAG, "onActivityResult: requestCode = " + requestCode);
Log.d(TAG, "onActivityResult: resultCode = " + resultCode);
if (resultCode == RESULT_OK) {
if (requestCode == CameraUtil.INT_OPEN_CAMERA && data != null) {
Bundle extras = data.getExtras();
Bitmap bmpCaptured = (Bitmap) extras.get("data");
// 加载位图
BmpUtil.loadBitmap(bmpCaptured, binding.ibImageShow);
// 检测模糊度
detectBlur(bmpCaptured);
}
}
}
// 检测模糊度-方差
private void detectBlur(Bitmap srcBmp) {
// 模糊度检测子对象
BlurDetect blurDetect = new BlurDetect(srcBmp);
// 耗时操作在子线程中执行,避免ANR(Application Not Responding)
new Thread(new Runnable() {
@Override
public void run() {
// 开始执行检测逻辑
blurDetect.detecting(new IConstract.ICommon() {
// 重载onDetected 方法,检测结束后执行
@Override
public void onDetected() {
// 在UI线程中更新控件
runOnUiThread(new Runnable() {
@Override
public void run() {
double sqaDev = blurDetect.sqDev;
String strDes = blurDetect.strBlurDescribe;
Log.d(TAG, "run: sq = " + sqaDev);
Log.d(TAG, "run: des = " + strDes);
// 声明弹窗控件
LayoutInflater inflater = getLayoutInflater();
View view = inflater.inflate(R.layout.activity_dialog, null);
// 根据方差判断图像是否清晰
OpenCvUtil.judgeBlurBySquDev(sqaDev, (TextView)view.findViewById(R.id.tv_describe), getApplicationContext());
// 定义弹窗
AlertDialog alertDialog2 = new AlertDialog.Builder(BlurDetectPageActivity.this)
.setView(view)
.setPositiveButton("保存", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Log.d(TAG, "onClick: 开始保存");
}
})
.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Log.d(TAG, "onClick: 取消保存");
}
}).create();
alertDialog2.show();
}
});
}
});
}
}).start();
}
}
效果
后续修改
2021.10.20 - 使用匿名内部对象
模糊度检测的实现代码中
// 检测模糊度-方差
private void detectBlur(Bitmap srcBmp) {
// 模糊度检测子对象
BlurDetect blurDetect = new BlurDetect(srcBmp);
// 耗时操作在子线程中执行,避免ANR(Application Not Responding)
new Thread(new Runnable() {
@Override
public void run() {
// 开始执行检测逻辑
blurDetect.detecting(new IConstract.ICommon() {
// 重载onDetected 方法,检测结束后执行
@Override
public void onDetected() {
// 在UI线程中更新控件
runOnUiThread(new Runnable() {
@Override
public void run() {
double sqaDev = blurDetect.sqDev;
String strDes = blurDetect.strBlurDescribe;
Log.d(TAG, "run: sq = " + sqaDev);
Log.d(TAG, "run: des = " + strDes);
// ......
在blurDetect
的detecting()
方法中调用blurDetect
的成员变量的显得比较傻气,为了试代码更加优雅,可以自定义一个匿名内部对象用来保存检测的结果。
- 首先定义一个简单的类
/*
* author: hgx
* time: 2021/10/20 11:29
* describe: 图像检测结束后返回的对象
*/
public class DetectResult {
public double resultNumber;
public String describe;
public DetectResult(double squareDeviation, String describe){
this.resultNumber = squareDeviation;
this.describe = describe;
}
}
- 再修改接口,在接口中传入这个这个类
/**
* 通用接口
*/
interface ICommon{
void onDetected(DetectResult detectResult);
}
- 修改模板类
主要修改了抽象方法deDetect()
的返回值
public abstract class DetectionTemplate {
protected Bitmap srcBitmap = null;
/**
* 模板方法
*/
public final void detecting(IConstract.ICommon doAfter){
if (doCheckValid()){
System.out.println("开始检测");
// 传入检测结果(内部参数)
doAfter.onDetected(doDetect());
}else{
System.out.println("未开始检测");
}
// 钩子方法
hook();
}
/**
* 钩子方法:
* 一个钩子方法由抽象类声明并实现,而子类会加以扩展。
* 通常抽象类给出的实现是一个空实现,作为方法的默认实现。
*/
protected void hook(){
}
/**
* 基本方法留给子类实现:
* 在检测前执行, 验证参数是否正确
*/
protected abstract boolean doCheckValid();
// 返回检测结果
protected abstract DetectResult doDetect();
}
- 修改实现子类
主要移除了两个公有成员变量
/*
* author: hgx
* time: 2021/10/20 11:51
* describe: 图像模糊度检测
*/
public class BlurDetect extends DetectionTemplate{
private static final String TAG = "BlurDetect";
public BlurDetect(final Bitmap bitmap){
this.srcBitmap = bitmap;
}
@Override
protected boolean doCheckValid() {
return srcBitmap != null;
}
@Override
protected DetectResult doDetect() {
// 获取方差
double sqDev = OpenCvUtil.getSquareDeviation(srcBitmap);
// 保留两位小数
sqDev = NumberUtil.reserveDecimal2(sqDev);
// 描述信息
String strBlurDescribe = "检测结果: " + sqDev;
Log.d(TAG, "计算结束: "+ strBlurDescribe);
return new DetectResult(sqDev, strBlurDescribe);
}
}
- 最后是实现部分
// 模糊度检测子对象
BlurDetect blurDetect = new BlurDetect(srcBmp);
// 耗时操作在子线程中执行,避免ANR(Application Not Responding)
new Thread(new Runnable() {
@Override
public void run() {
// 开始执行检测逻辑
blurDetect.detecting(detectResult -> {
// 在UI线程中更新控件
runOnUiThread(new Runnable() {
@Override
public void run() {
double sqaDev = detectResult.resultNumber;
String strDes = detectResult.describe;
Log.d(TAG, "run: sq = " + sqaDev);
Log.d(TAG, "run: des = " + strDes);
这样子检测得到的方差和描述信息就对外隐藏了,detectResult
只能在匿名函数detectResult -> {}
中使用。