Android-拍照后使用OpenCV进行图像模糊度检测

通过计算方差判断图像是否模糊

所需依赖

  1. 图像加载库:Glide

在gradle.properties 文件中添加

implementation 'com.github.bumptech.glide:glide:4.12.0'     
annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'  
  1. OpenCV

    下载opencv的安卓sdk并导入。

    注意,Android Studio升级Artic Fox 2020 3.1 之后从Import Module导入会有些问题,需要手动将sdk拖入项目中。

    参考:https://stackoverflow.com/questions/68649524/opencv-android-studio-module-importing-issue

思路描述

  1. 打开相机拍照;

  2. 拍照结束后计算图像方差;

  3. 根据预先设定的阈值判断图像是清晰还是模糊;

  4. 使用对话框显示判断结果。

实现前的准备

​ 异步调用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;
    }
}

检查手机是否有摄像头,当 isCameratrue时同样跳转到拍照界面。

/**
 * 相机帮助类
 */
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);
                                // ......
        

blurDetectdetecting()方法中调用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 -> {}中使用。

  • 4
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值