栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

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

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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

文章目录
  • 通过计算方差判断图像是否模糊
    • 所需依赖
    • 思路描述
    • 实现前的准备
      • 模板
        • 模板子类
      • 接口
      • 工具类
        • OpenCvUtil
        • BmpUtil
        • CameraUtil
    • 布局文件
      • activity_blur_detect_page.xml 界面
      • dialog_blur.xml 对话框
    • 实现
    • 效果
    • 后续修改
      • 2021.10.20 - 使用匿名内部对象

通过计算方差判断图像是否模糊 所需依赖
  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
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;
    
    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;
    }

    
    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;
    }

    
    public static double getStandardDeviation(Bitmap bitmap){
        double result = 0.0;
        if (bitmap != null){
            result = getStdDev(bitmap);
        }
        return result;
    }

    
    public static double getSquareDeviation(Bitmap bitmap){
        double result = 0.0;
        if (bitmap != null){
            result = getStdDev(bitmap);
        }
        return result * result;
    }

    
    public static void judgeBlurByStdDev(final double st, final TextView tv,final Context context){
        judgeBlurBySquDev(st*st, tv, 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);
            }
        });
    }

    
    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

    
    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
public class BmpUtil {
    
    public static Bitmap getBitmapFromImageView(ImageView imageView){
        Bitmap result = null;
        imageView.setDrawingCacheEnabled(true);
        result = Bitmap.createBitmap(imageView.getDrawingCache());
        imageView.setDrawingCacheEnabled(false);

        return result;
    }

    
    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);
    }

    
    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;

    
    @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 界面


    



        
            






                


                    

                        

                        
                    

                



                
                    
                        >
                        
                        
                            
                                
                                
                            
                            
                                
                                
                            
                            
                                
                                
                            
                        
                    


                


                
                    

                    
dialog_blur.xml 对话框


    
    
    



实现
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的成员变量的显得比较傻气,为了试代码更加优雅,可以自定义一个匿名内部对象用来保存检测的结果。

  • 首先定义一个简单的类
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();

}

  • 修改实现子类
    主要移除了两个公有成员变量
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 -> {}中使用。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/336650.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号