java+opencv 识别图片中的二维码,截取二维码放大增强清晰度等识别

识别率 92%+

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import com.google.zxing.*;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.common.HybridBinarizer;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.opencv.core.*;
import org.opencv.core.Point;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.CLAHE;
import org.opencv.imgproc.Imgproc;
import org.springframework.util.ResourceUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.URL;
import java.util.*;
import java.util.List;

/**
 * @Author: GHL
 * @Date: 2022/2/18
 * @Description:
 */
public class QRCodeUtil {
    /**
     * 默认放大倍数
     */
    private final static int TIMES = 4;
    static {
        String dir = System.getProperty("user.dir").replace("/", "\\");
        String filePath = "\\dll\\" + (System.getProperty("java.vm.name").contains("64") ? "x64" : "x86") + "\\opencv_java451.dll";
        String path = dir.concat(filePath);
        File file = new File(path);
        if (!file.exists()) {
            throw new RuntimeException("找不到动态库".concat(path));
        }
        System.load(path);
        System.out.println(path.concat("  load success"));
    }
    /**
     * 复杂图片二维码解析
     *
     * @param file
     * @return
     */
    public static String complexDecode(File file) {
        String tempFilePath = null;
        try {
            tempFilePath = getFilePath(file.getName());
            //第一次解析:直接解析
            String codeDataByFirst = simpleDecode(file);
            if (codeDataByFirst != null) {
                return codeDataByFirst;
            }
            //第二次解析:定位图中二维码,截图放大
            piz(file.getAbsolutePath(),tempFilePath);
            String codeDataBySecond = simpleDecode(tempFilePath);
            if (codeDataBySecond != null) {
                return codeDataBySecond;
            }
            //第三次解析:将截图后二维码二值化
            Mat mat = binarization(tempFilePath);
            String codeDataByThird = simpleDecode(tempFilePath);
            if (codeDataByThird != null) {
                return codeDataByThird;
            }
            //第四次解析: 进行限制对比度的自适应直方图均衡化处理
            limitContrast(tempFilePath,mat);
            String codeDataByFourth = simpleDecode(tempFilePath);
            if (codeDataByFourth != null) {
//                System.out.println("QRCodeUtil -> complexDecode() fileName:{} state:{} result:{}"+file.getName()+Boolean.TRUE+codeDataByFourth);
                return codeDataByFourth;
            }
            System.out.println(file.getPath()+"========="+tempFilePath);
        } finally {
//            file.deleteOnExit();
//            if (tempFilePath != null){
//                file = new File(tempFilePath);
//                file.deleteOnExit();
//            }
        }
        return null;
    }

    /**
     * 复杂图片二维码解析
     *
     * @param path
     * @return
     */
    public static String complexDecode(String path) {
        return complexDecode(new File(path));
    }

    /**
     * 复杂图片二维码解析
     *
     * @param originalFile
     * @return
     */
    public static String complexDecode(MultipartFile originalFile) {
        String filePath = getFilePath(originalFile.getOriginalFilename());
        File mkFile = new File(filePath);
        if (!mkFile.exists()){
            mkFile.mkdir();
            System.out.println("QRCodeUtil -> complexDecode() create temp file ready by:{}"+originalFile.getOriginalFilename());
        }
        try {
            originalFile.transferTo(mkFile);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return complexDecode(mkFile);
    }

    /**
     * 简单二维码解析
     *
     * @param path
     * @return
     */
    public static String simpleDecode(String path) {
        return simpleDecode(new File(path));
    }

    /**
     * 简单二维码解析
     *
     * @param file
     * @return zxing解析率实测与opencv差不多。所以直接使用zxing解析
     * zxing版本高能提高识别率
     */
    public static String simpleDecode(File file) {
        try {
            BufferedImage image = ImageIO.read(file);
            LuminanceSource source = new BufferedImageLuminanceSource(image);
            Binarizer binarizer = new HybridBinarizer(source);
            BinaryBitmap binaryBitmap = new BinaryBitmap(binarizer);
            Map<DecodeHintType, Object> hints = new HashMap<DecodeHintType, Object>();
            hints.put(DecodeHintType.CHARACTER_SET, "UTF-8");
            Result result = new MultiFormatReader().decode(binaryBitmap, hints);
            return result.getText();
        } catch (Exception e) {
            return null;
        }

    }

    /**
     * 获取临时文件存储地址
     */
    @SneakyThrows
    private static String getFilePath(String fileName) {
        String path = ResourceUtils.getFile("classpath:").getPath() + "/static/decodeWork/";
        File folder = new File(path);
        if (!folder.exists()){
            folder.mkdirs();
        }
        String contentType = fileName.contains(".") ? fileName.substring(fileName.lastIndexOf(".") + 1) : null;
        String newFileName = System.currentTimeMillis() + "." + contentType;
        return path + newFileName;
    }

    /**
     * 定位 - > 截取 -> 放大
     * @param filePath
     * @param tempFilePath
     */
    private static void piz(String filePath, String tempFilePath) {
        Mat srcGray = new Mat();
        Mat src = Imgcodecs.imread(filePath, 1);
        List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
        List<MatOfPoint> markContours = new ArrayList<MatOfPoint>();
        String dir = System.getProperty("user.dir").replace("/", "\\");
        String filePaths = "\\dll\\" + (System.getProperty("java.vm.name").contains("64") ? "x64" : "x86") + "\\opencv_java451.dll";
        String path = dir.concat(filePaths);
        File file = new File(path);
        if (!file.exists()) {
            throw new RuntimeException("找不到动态库".concat(path));
        }
        System.load(path);
        //图片太小就放大
        if (src.width() * src.height() < 90000) {
            Imgproc.resize(src, src, new Size(800, 600));
        }
        // 彩色图转灰度图
        Imgproc.cvtColor(src, srcGray, Imgproc.COLOR_RGB2GRAY);
        // 对图像进行平滑处理
        Imgproc.GaussianBlur(srcGray, srcGray, new Size(3, 3), 0);
        Imgproc.Canny(srcGray, srcGray, 112, 255);
        Mat hierarchy = new Mat();
        Imgproc.findContours(srcGray, contours, hierarchy, Imgproc.RETR_TREE, Imgproc.CHAIN_APPROX_NONE);

        for (int i = 0; i < contours.size(); i++) {
            MatOfPoint2f newMtx = new MatOfPoint2f(contours.get(i).toArray());
            RotatedRect rotRect = Imgproc.minAreaRect(newMtx);
            double w = rotRect.size.width;
            double h = rotRect.size.height;
            double rate = Math.max(w, h) / Math.min(w, h);
            // 长短轴比小于1.3,总面积大于60
            if (rate < 1.3 && w < srcGray.cols() / 4 && h < srcGray.rows() / 4 && Imgproc.contourArea(contours.get(i)) > 60) {
                // 计算层数,二维码角框有五层轮廓(有说六层),这里不计自己这一层,有4个以上子轮廓则标记这一点
                double[] ds = hierarchy.get(0, i);
                if (ds != null && ds.length > 3) {
                    int count = 0;
                    if (ds[3] == -1) {
                        //最外层轮廓排除
                        continue;
                    }
                    // 计算所有子轮廓数量
                    while ((int) ds[2] != -1) {
                        ++count;
                        ds = hierarchy.get(0, (int) ds[2]);
                    }
                    if (count >= 4) {
                        markContours.add(contours.get(i));
                    }
                }
            }
        }
        /*
         * 二维码有三个角轮廓,正常需要定位三个角才能确定坐标,本工具当识别到两个点的时候也将二维码定位出来;
         * 当识别到两个及两个以上点时,取两个点中间点,往四周扩散截取 当小于两个点时,直接返回
         */
        if (markContours.size() == 0) {
            return;
        } else if (markContours.size() == 1) {
            capture(markContours.get(0), src ,tempFilePath);
        } else {
            List<MatOfPoint> threePointList = new ArrayList<>();
            threePointList.add(markContours.get(0));
            threePointList.add(markContours.get(1));
            capture(threePointList, src,tempFilePath);
        }
    }

    /**
     * 当只识别到二维码的两个定位点时,根据两个点的中点进行定位
     * @param threePointList
     * @param src
     */
    private static void capture(List<MatOfPoint> threePointList, Mat src, String tempFilePath) {
        try {
            Point p1 = centerCal(threePointList.get(0));
            Point p2 = centerCal(threePointList.get(1));
            Point centerPoint = new Point((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
            double width = Math.abs(p1.x - p2.x) + Math.abs(p1.y - p2.y) + 50;
            // 设置截取规则
            Rect roiArea = new Rect((int) (centerPoint.x - width) > 0 ? (int) (centerPoint.x - width) : 0,
                    (int) (centerPoint.y - width) > 0 ? (int) (centerPoint.y - width) : 0, (int) (2 * width),
                    (int) (2 * width));
            Mat dstRoi = new Mat(src, roiArea);
            // 放大图片
            Imgproc.resize(dstRoi, dstRoi, new Size(TIMES * width, TIMES * width));
            Imgcodecs.imwrite(tempFilePath, dstRoi);
        }catch (Exception e){
            // 设置截取规则
            Rect roiArea = new Rect(0,0,src.width(),800);
            Mat dstRoi = new Mat(src, roiArea);
            // 放大图片
            Imgcodecs.imwrite(tempFilePath, dstRoi);
        }
    }
    /**
     * 针对对比度不高的图片,只能识别到一个角的,直接以该点为中心截取
     * @param matOfPoint
     * @param src
     * @param tempFilePath
     */
    private static void capture(MatOfPoint matOfPoint, Mat src, String tempFilePath) {
        Point centerPoint = centerCal(matOfPoint);
        int width = 200;
        Rect roiArea = new Rect((int) (centerPoint.x - width) > 0 ? (int) (centerPoint.x - width) : 0,
                (int) (centerPoint.y - width) > 0 ? (int) (centerPoint.y - width) : 0, (int) (2 * width),
                (int) (2 * width));
        // 截取二维码
        Mat dstRoi = new Mat(src, roiArea);
        // 放大图片
        Imgproc.resize(dstRoi, dstRoi, new Size(TIMES * width, TIMES * width));
        Imgcodecs.imwrite(tempFilePath, dstRoi);
    }
    /**
     * 获取轮廓的中心坐标
     * @param matOfPoint
     * @return
     */
    private static Point centerCal(MatOfPoint matOfPoint) {
        double centerx = 0, centery = 0;
        MatOfPoint2f mat2f = new MatOfPoint2f(matOfPoint.toArray());
        RotatedRect rect = Imgproc.minAreaRect(mat2f);
        Point vertices[] = new Point[4];
        rect.points(vertices);
        centerx = ((vertices[0].x + vertices[1].x) / 2 + (vertices[2].x + vertices[3].x) / 2) / 2;
        centery = ((vertices[0].y + vertices[1].y) / 2 + (vertices[2].y + vertices[3].y) / 2) / 2;
        Point point = new Point(centerx, centery);
        return point;
    }

    /**
     * 二值化图像
     * @param filePath 图像地址
     */
    private static Mat binarization(String filePath){
        Mat mat = Imgcodecs.imread(filePath, 1);
        try {
            // 彩色图转灰度图
            Imgproc.cvtColor(mat, mat, Imgproc.COLOR_RGB2GRAY);
            // 对图像进行平滑处理
            Imgproc.blur(mat, mat, new Size(3, 3));
            // 中值去噪
            Imgproc.medianBlur(mat, mat, 5);
            // 这里定义一个新的Mat对象,主要是为了保留原图,未下次处理做准备
            Mat mat2 = new Mat();
            // 根据OTSU算法进行二值化
            Imgproc.threshold(mat, mat2, 205, 255, Imgproc.THRESH_OTSU);
            // 生成二值化后的图像
            Imgcodecs.imwrite(filePath, mat2);
            return mat;
        }catch (Exception e){
            System.out.println("未识别到二维码");
            return new Mat();
        }

    }

    /**
     * 图像进行限制对比度的自适应直方图均衡化处理
     * @param filePath
     */
    public static void limitContrast(String filePath,Mat mat){
        try {
            CLAHE clahe = Imgproc.createCLAHE(2, new Size(8, 8));
            clahe.apply(mat, mat);
            Imgcodecs.imwrite(filePath, mat);
        }catch (Exception e){
            System.out.println("未识别到二维码");
        }
    }

    public static void main(String[] args) throws Exception{

//            InputStream in = new FileInputStream("F:\\300jpg\\1658395621169.jpg");//图片路径
//            BufferedImage image = ImageIO.read(new File("F:\\300jpg\\1658395621169.jpg"));
//            Graphics g = image.getGraphics();
//            g.setColor(Color.RED);//画笔颜色
//            g.drawRect(537, 0, image.getWidth()/2, 800);//矩形框(原点x坐标,原点y坐标,矩形的长,矩形的宽)
//            //g.dispose();
//            FileOutputStream out = new FileOutputStream("F:\\300jpg.jpg");//输出图片的地址
//            ImageIO.write(image, "jpeg", out);

//        String s = complexDecode("F:\\300jpg\\1658395621230.jpg");
//        System.out.println(s+"================="+s);

        File file = new File("F:\\300jpg");
        File[] files = file.listFiles();
        Count count = new Count();
        count.setSum(files.length);
        Arrays.stream(files).forEach(e->{
            System.out.println(e.getPath());
            String s = complexDecode(e.getPath());
            if(null ==s){
                count.setFail(count.getFail()+1);
            }
            System.out.println(e.getPath()+"================="+s);
        });
        System.out.println("识别结束,共:"+count.getSum()+"个,成功:"+(count.getSum()-count.getFail())+"个,失败:"+count.getFail()+",识别率"+((float)(count.getSum()-count.getFail())/count.getSum())*100+"%");
    }
}

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,我了解了你的问题。关于利用 Python + OpenCV 识别图片的圆形(霍夫变换),我可以给你一些详细的解释和代码示例。 首先,我们需要导入 OpenCV 库和 NumPy 库。这里需要注意的是,需要安装正确的 OpenCV 版本,否则可能会出现一些问题。 ```python import cv2 import numpy as np ``` 接下来,我们需要读取图片并将其转换为灰度图像。然后,我们可以对灰度图像进行高斯模糊处理,以减少噪声。这里的高斯核大小可以根据实际情况进行调整。 ```python img = cv2.imread('image.jpg') gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) gray = cv2.GaussianBlur(gray, (5, 5), 0) ``` 接着,我们可以使用霍夫变换检测圆形。这里需要注意的是,需要设置最小半径和最大半径的范围,以及检测圆形的参数阈值。这些参数的设置也需要根据实际情况进行调整。 ```python circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 1, 100, param1=100, param2=30, minRadius=0, maxRadius=0) ``` 最后,我们可以将检测到的圆形标记出来,并显示图片。 ```python if circles is not None: circles = np.round(circles[0, :]).astype("int") for (x, y, r) in circles: cv2.circle(img, (x, y), r, (0, 255, 0), 2) cv2.imshow("Image", img) cv2.waitKey(0) ``` 完整代码如下: ```python import cv2 import numpy as np img = cv2.imread('image.jpg') gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) gray = cv2.GaussianBlur(gray, (5, 5), 0) circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 1, 100, param1=100, param2=30, minRadius=0, maxRadius=0) if circles is not None: circles = np.round(circles[0, :]).astype("int") for (x, y, r) in circles: cv2.circle(img, (x, y), r, (0, 255, 0), 2) cv2.imshow("Image", img) cv2.waitKey(0) ``` 希望这个代码示例能对你有所帮助!

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值