java 图片中的二维码定位

java 图片中的二维码定位

使用技术:opencv 3.4.14 执行下载安装

import com.google.zxing.*;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.common.HybridBinarizer;
import org.opencv.core.*;
import org.opencv.core.Point;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.opencv.objdetect.QRCodeDetector;
import org.opencv.utils.Converters;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.util.*;
import java.util.List;

/**
 *  参考:https://www.cnblogs.com/huacanfly/p/9908408.html
 *  参考:https://blog.csdn.net/StrangeSir/article/details/93143177
 *  参考:https://opencv.org/releases/
 */
public class OpenCVQrCodePosition {
    
    
    private boolean isDebug = true;
    static {
    
    
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
    }

    // 获取二维码图片位置信息
    public Point[] getPosition(String imgUrl){
    
    
        Mat src = Imgcodecs.imread(imgUrl ,1);
        Mat src_gray = new Mat();

        List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
        //彩色图转灰度图
        Imgproc.cvtColor(src ,src_gray ,Imgproc.COLOR_RGB2GRAY);
        //对图像进行平滑处理
        Imgproc.GaussianBlur(src_gray, src_gray, new Size(3,3), 0);
        this.printImg("1-1.jpg", src_gray);

        Imgproc.Canny(src_gray,src_gray,112,255);

        this.printImg("1-2.jpg", src_gray);
        Mat hierarchy = new Mat();

        // 寻找轮廓
        Imgproc.findContours(src_gray ,contours ,hierarchy ,Imgproc.RETR_TREE ,Imgproc.CHAIN_APPROX_NONE);
        List<MatOfPoint> markContours =  this.markContour(src_gray, contours, hierarchy);
        markContours = this.filterByAngle(markContours);
        Point[] points =  this.centerCals(markContours);
        if(points != null){
    
    
            this.cutRect(src, points, 12);
        }
        return points;
    }

    //  裁剪指定区域
    public BufferedImage cutRect(Mat src,Point[] points, int offset){
    
    
        if(points == null || points.length < 3){
    
    
            System.out.println("-------没有点---------");
            return null;
        }
        int left_x = (int)Math.min(points[0].x, Math.min(points[1].x, points[2].x)) - offset;
        int right_x = (int)Math.max(points[0].x, Math.max(points[1].x, points[2].x)) + offset;
        int y = (int)Math.min(points[0].y, Math.min(points[1].y, points[2].y)) - offset;
        Rect roiArea = new Rect(left_x, y, right_x - left_x, right_x - left_x);
        Mat dstRoi = new Mat(src, roiArea);
        this.printImg("cutRect.jpg", dstRoi);
        return this.mat2BufImg(dstRoi, ".png");
    }

    // 通过角度筛选点, 只处理正常角度二维码
    private List<MatOfPoint> filterByAngle(List<MatOfPoint> markContours) {
    
    
        // 二维码有三个角轮廓,少于三个的无法定位放弃,多余三个的循环裁剪出来
        if (markContours.size() < 3){
    
    
            return null;
        }else{
    
    
            for (int i=0; i<markContours.size()-2; i++){
    
    
                List<MatOfPoint> threePointList = new ArrayList<>();
                for (int j=i+1;j<markContours.size()-1; j++){
    
    
                    for (int k=j+1;k<markContours.size();k++){
    
    
                        threePointList.add(markContours.get(i));
                        threePointList.add(markContours.get(j));
                        threePointList.add(markContours.get(k));
                        if(capture(threePointList)){
    
    
                            return threePointList;
                        }else{
    
    
                            threePointList.clear();
                        }
                    }
                }
            }
        }
        return null;
    }

    // 计算三个点是否符合 90,45,45
    private boolean capture(List<MatOfPoint> contours){
    
    
        Point[] pointthree = this.centerCals(contours);
        double angle1 = this.angle(pointthree[1],  pointthree[0],  pointthree[2]);
        double angle2 = this.angle(pointthree[0],  pointthree[1],  pointthree[2]);
        double angle3 = this.angle(pointthree[1],  pointthree[2],  pointthree[0]);

        System.out.println("angle1:"+angle1+",angle2:"+angle2+",angle3:"+angle3);
        if (Double.isNaN(angle1) || Double.isNaN(angle2) || Double.isNaN(angle3)){
    
    
            return false;
        }

        // 最大角度和最小角度
        double maxAngle = Math.max(angle3,Math.max(angle1,angle2));
        double minAngle = Math.min(angle3,Math.min(angle1,angle2));
        if (maxAngle<85 || maxAngle>95 || minAngle < 40 || minAngle > 50){
    
     /**二维码为直角,最大角过大或者过小都判断为不是二维码*/
            return false;
        }
        return true;
    }

    //  计算夹角
    private double angle(Point p1, Point p2, Point p3){
    
    
        double[] ca = new double[2];
        double[] cb = new double[2];

        ca[0] =  p1.x - p2.x;
        ca[1] =  p1.y - p2.y;
        cb[0] =  p3.x - p2.x;
        cb[1] =  p3.y - p2.y;
        return 180/3.1415*Math.acos((ca[0]*cb[0]+ca[1]*cb[1])/(Math.sqrt(ca[0]*ca[0]+ca[1]*ca[1])*Math.sqrt(cb[0]*cb[0]+cb[1]*cb[1])));
    }
    // 转换为点
    private Point[] centerCals(List<MatOfPoint> matOfPoint){
    
    
        if(matOfPoint == null || matOfPoint.size() == 0){
    
    
            return null;
        }
        Point[] pointthree = new Point[matOfPoint.size()];
        for(int i=0; i<matOfPoint.size(); i++){
    
    
            pointthree[i] = centerCal(matOfPoint.get(i));
        }
        return pointthree;
    }

    // 转换为中心点
    private 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;
    }


    // 寻找二维码轮廓点
    private List<MatOfPoint> markContour(Mat src_gray, List<MatOfPoint> contours, Mat hierarchy) {
    
    
        List<MatOfPoint> markContours = new ArrayList<>();
        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 < src_gray.cols()/4 && h< src_gray.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));
                    }
                }
            }
        }
        return markContours;
    }

    // 测试打印图片
    private  void printImg(String name, Mat img){
    
    
        if(isDebug) {
    
    
            String path = String.format("%s/%s", System.getProperty("user.dir"), name);
            System.out.println("out:" + path);
            Imgcodecs.imwrite(path, img);
        }
    }

    /**
     * Mat转换成BufferedImage
     *
     * @param matrix        要转换的Mat
     * @param fileExtension 格式为 ".jpg", ".png", etc
     * @return
     */
    public static BufferedImage mat2BufImg(Mat matrix, String fileExtension) {
    
    
        // convert the matrix into a matrix of bytes appropriate for
        // this file extension
        MatOfByte mob = new MatOfByte();
        Imgcodecs.imencode(fileExtension, matrix, mob);
        // convert the "matrix of bytes" into a byte array
        byte[] byteArray = mob.toArray();
        BufferedImage bufImage = null;
        try {
    
    
            InputStream in = new ByteArrayInputStream(byteArray);
            bufImage = ImageIO.read(in);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
        return bufImage;
    }

    /**
     * BufferedImage转换成Mat
     *
     * @param original
     *            要转换的BufferedImage
     * @param imgType
     *            bufferedImage的类型 如 BufferedImage.TYPE_3BYTE_BGR
     * @param matType
     *            转换成mat的type 如 CvType.CV_8UC3
     */
    public static Mat bufImg2Mat (BufferedImage original, int imgType, int matType) {
    
    
        if (original == null) {
    
    
            throw new IllegalArgumentException("original == null");
        }
        // Don't convert if it already has correct type
        if (original.getType() != imgType) {
    
    
            // Create a buffered image
            BufferedImage image = new BufferedImage(original.getWidth(), original.getHeight(), imgType);
            // Draw the image onto the new buffer
            Graphics2D g = image.createGraphics();
            try {
    
    
                g.setComposite(AlphaComposite.Src);
                g.drawImage(original, 0, 0, null);
            } finally {
    
    
                g.dispose();
            }
        }
        DataBufferByte dbi = (DataBufferByte) original.getRaster().getDataBuffer();
        byte[] pixels = dbi.getData();
        Mat mat = Mat.eye(original.getHeight(), original.getWidth(), matType);
        mat.put(0, 0, pixels);
        return mat;
    }

    /**
     * 解析读取二维码
     * 先使用ZXING二维码识别,若失败,使用OPENCV自带的二维码识别
     * 个人测试,两者的识别率差不多,都不尽人意,但一起使用还是可以略微提高一点识别率,毕竟实现算法不一样
     * 若还要其它的识别,类似Zbar,都可以集成进来
     *
     * @param qrCodePath 二维码图片路径
     * @return 成功返回二维码识别结果,失败返回null
     * @throws Exception
     */
    public static String decodeQRcode(String qrCodePath){
    
    
        String qrCodeText = null;
        try {
    
    
            BufferedImage image = ImageIO.read(new File(qrCodePath));
            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);
            qrCodeText = result.getText();
        } catch (Exception e) {
    
    
            qrCodeText = new QRCodeDetector().detectAndDecode(Imgcodecs.imread(qrCodePath, 1));
        }
        return qrCodeText;
    }

    public static void main(String[] args) {
    
    
        String img = "C:\\Users\\jonk\\Desktop\\2.png";
        Point[] points =  new OpenCVQrCodePosition().getPosition(img);
        if(points == null) {
    
    
            System.out.println("可能不包含二维码:"+img);
            return;
        }
        for (int i = 0; i < points.length; i++) {
    
    
            Point p = points[i];
            System.out.println("点" + i + ": " + "(" + p.x + "," + p.y + ")");
        }
        QRCodeDetector detector = new QRCodeDetector();
        String path = String.format("%s/%s", System.getProperty("user.dir"), "cutRect.jpg");
        System.out.println("读取二维码:"+path);
        String qrCodeText = decodeQRcode(path);
        System.out.println("二维码内容:"+ qrCodeText);
    }
}


运行结果

out:C:\Users\jonk\workspace\springcloud\opencv/1-1.jpg
out:C:\Users\jonk\workspace\springcloud\opencv/1-2.jpg
angle1:80.21728934490619,angle2:68.08326088303801,angle3:31.704758588278217
angle1:35.215962140850586,angle2:125.8393600086319,angle3:18.949986666739946
angle1:45.001327204055606,angle2:90.00265440811121,angle3:45.001327204055606
out:C:\Users\jonk\workspace\springcloud\opencv/cutRect.jpg
点0: (495.5,743.5)
点1: (495.5,623.5)
点2: (375.5,623.5)

参考文档:https://www.cnblogs.com/huacanfly/p/9908408.html
参考文档:https://blog.csdn.net/StrangeSir/article/details/93143177

猜你喜欢

转载自blog.csdn.net/afgasdg/article/details/112301386
今日推荐