Java使用opencv进行二维码定位、矫正和裁剪

Wesley13
• 阅读 537

例子使用的版本为3.4.0,安装配置网上资料比较多。

代码为本地测试时候的版本,所以会有点乱。

import org.opencv.core.*;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.opencv.utils.Converters;

import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * @author liuxf  
 * @date 2018/10/22 14:28  
 * @param   
 * @return   
 */
public class OpenCVJavaTest2 {

    static{ System.loadLibrary(Core.NATIVE_LIBRARY_NAME); }
    public static void main(String[] args) {
        String imgUrl = "F:\\1.jpg";
        Mat src = Imgcodecs.imread(imgUrl ,1);
        Mat src_gray = new Mat();
        test1(src,src_gray);
    }

    public static void test1(Mat src ,Mat src_gray){
        List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
        List<MatOfPoint> markContours = new ArrayList<MatOfPoint>();
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
        /**图片太小就放大**/
        if (src.width()*src.height()<90000){
            Imgproc.resize(src,src,new Size(800,600));
        }
        Mat src_all=src.clone();
        //彩色图转灰度图
        Imgproc.cvtColor(src ,src_gray ,Imgproc.COLOR_RGB2GRAY);
        //对图像进行平滑处理
        Imgproc.GaussianBlur(src_gray, src_gray, new Size(3,3), 0);
        /**Imgcodecs.imwrite("F:\\output\\EH.jpg", src_gray);**/
        Imgproc.Canny(src_gray,src_gray,112,255);

        /**Imgcodecs.imwrite("F:\\output\\1-2.jpg", src_gray);**/
        Mat hierarchy = new Mat();
        Imgproc.findContours(src_gray ,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 < 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));
                    }
                }
            }
        }
       /**
        * 这部分代码画框,调试用**/
       for(int i=0; i<markContours.size(); i++){
          Imgproc.drawContours(src_all,markContours,i,new Scalar(0,255,0) ,-1);
        }
        Imgcodecs.imwrite("F:\\output\\2-1.jpg", src_all);

        /***
         * 二维码有三个角轮廓,少于三个的无法定位放弃,多余三个的循环裁剪出来
         */
        if (markContours.size() < 3){
            return;
        }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));
                        capture(threePointList ,src ,i+"-"+j+"-"+k);
                        threePointList.clear();
                    }
                }
            }
        }
    }

    /***
     * 另一种实现,识别能力比第一种弱
     * @param src
     * @param src_gray
     */
    public static void test2(Mat src,Mat src_gray){
        List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
        List<MatOfPoint> markContours = new ArrayList<MatOfPoint>();
        if (src.width()*src.height()<90000){
            Imgproc.resize(src,src,new Size(800,600));
        }
        Mat src_all=src.clone();
        Mat threshold_output = new Mat();

        //彩色图转灰度图
        Imgproc.cvtColor(src ,src_gray ,Imgproc.COLOR_RGB2GRAY);
        //对图像进行平滑处理
        Imgproc.blur(src_gray ,src_gray ,new Size(3,3));
        Imgproc.equalizeHist(src_gray,src_gray);
        //指定112阀值进行二值化
        Imgproc.threshold(src_gray ,threshold_output,112,255 ,Imgproc.THRESH_BINARY );

        /**Imgcodecs.imwrite("F:\\output\\1-2.jpg", threshold_output);**/
        Mat hierarchy = new Mat();
        Imgproc.findContours(threshold_output ,contours ,hierarchy ,Imgproc.RETR_TREE ,Imgproc.CHAIN_APPROX_SIMPLE);

        int c=0,ic=0,area=0;
        int parentIdx=-1;
        for ( int i = 0; i< contours.size(); i++ ) {
            double[] ds = hierarchy.get(0, i);
            int k=i;
            if (ds == null) {
                continue;
            }
            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 >= 2){
                    markContours.add(contours.get(i));
                }
            }
        }
        Point[] point = new Point[markContours.size()];
        for(int i=0; i<markContours.size(); i++)
        {
            point[i] = centerCal(markContours.get(i));
        }

    }


    /**
     * 对图片进行矫正,裁剪
     * @param contours
     * @param src
     * @param idx
     */
    public static void capture(List<MatOfPoint> contours ,Mat src ,String idx){
        Point[] pointthree = new Point[3];
        for(int i=0; i<contours.size(); i++)
        {
            pointthree[i] = centerCal(contours.get(i));
        }

     /**画线
      * **/
        Mat sline = src.clone();
        Imgproc.line(sline ,pointthree[0],pointthree[1] ,new Scalar(0,0,255),2);
        Imgproc.line(sline ,pointthree[1],pointthree[2] ,new Scalar(0,0,255),2);
        Imgproc.line(sline ,pointthree[0],pointthree[2] ,new Scalar(0,0,255),2);
        Imgcodecs.imwrite("F:\\output\\cvRio-"+idx+".jpg", sline);

        double[] ca = new double[2];
        double[] cb = new double[2];

        ca[0] =  pointthree[1].x - pointthree[0].x;
        ca[1] =  pointthree[1].y - pointthree[0].y;
        cb[0] =  pointthree[2].x - pointthree[0].x;
        cb[1] =  pointthree[2].y - pointthree[0].y;
      /*  if (Math.max(ca[0],cb[0])/Math.min(ca[0],cb[0]) > 1.5 || Math.max(ca[1],cb[1])/Math.min(ca[1],cb[1])>1.3){
            return;
        }*/
        double angle1 = 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])));
        double ccw1;
        if(ca[0]*cb[1] - ca[1]*cb[0] > 0) {
            ccw1 = 0;
        } else {
            ccw1 = 1;
        }
        ca[0] =  pointthree[0].x - pointthree[1].x;
        ca[1] =  pointthree[0].y - pointthree[1].y;
        cb[0] =  pointthree[2].x - pointthree[1].x;
        cb[1] =  pointthree[2].y - pointthree[1].y;
        double angle2 = 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])));
        double ccw2;
        if(ca[0]*cb[1] - ca[1]*cb[0] > 0) {
            ccw2 = 0;
        }else {
            ccw2 = 1;
        }

        ca[0] =  pointthree[1].x - pointthree[2].x;
        ca[1] =  pointthree[1].y - pointthree[2].y;
        cb[0] =  pointthree[0].x - pointthree[2].x;
        cb[1] =  pointthree[0].y - pointthree[2].y;
        double angle3 = 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])));
        int ccw3;
        if(ca[0]*cb[1] - ca[1]*cb[0] > 0) {
            ccw3 = 0;
        }else {
            ccw3 = 1;
        }

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

        Point[] poly= new Point[4];
        if(angle3>angle2 && angle3>angle1)
        {
            if(ccw3==1)
            {
                poly[1] = pointthree[1];
                poly[3] = pointthree[0];
            }
            else
            {
                poly[1] = pointthree[0];
                poly[3] = pointthree[1];
            }
            poly[0] = pointthree[2];
            Point temp = new Point(pointthree[0].x + pointthree[1].x - pointthree[2].x , pointthree[0].y + pointthree[1].y - pointthree[2].y );
            poly[2] = temp;
        } else if(angle2>angle1 && angle2>angle3)
        {
            if(ccw2==1)
            {
                poly[1] = pointthree[0];
                poly[3] = pointthree[2];
            }
            else
            {
                poly[1] = pointthree[2];
                poly[3] = pointthree[0];
            }
            poly[0] = pointthree[1];
            Point temp = new Point(pointthree[0].x + pointthree[2].x - pointthree[1].x , pointthree[0].y + pointthree[2].y - pointthree[1].y );
            poly[2] = temp;
        } else if(angle1>angle2 && angle1 > angle3)
        {
            if(ccw1==1)
            {
                poly[1] = pointthree[1];
                poly[3] = pointthree[2];
            }
            else
            {
                poly[1] = pointthree[2];
                poly[3] = pointthree[1];
            }
            poly[0] = pointthree[0];
            Point temp = new Point(pointthree[1].x + pointthree[2].x - pointthree[0].x , pointthree[1].y + pointthree[2].y - pointthree[0].y );
            poly[2] = temp;
        }

        Point[] trans=new Point[4];

        int temp =50;
        trans[0] = new Point(0+temp,0+temp);
        trans[1] = new Point(0+temp,100+temp);
        trans[2] = new Point(100+temp,100+temp);
        trans[3] = new Point(100+temp,0+temp);

        double maxAngle = Math.max(angle3,Math.max(angle1,angle2));
        System.out.println(maxAngle);
        if (maxAngle<75 || maxAngle>115){ /**二维码为直角,最大角过大或者过小都判断为不是二维码*/
            return;
        }

        Mat perspectiveMmat=Imgproc.getPerspectiveTransform(Converters.vector_Point_to_Mat(Arrays.asList(poly),CvType.CV_32F),Converters.vector_Point_to_Mat(Arrays.asList(trans),CvType.CV_32F)); //warp_mat
        Mat dst = new Mat();
        //计算变换结果
        Imgproc.warpPerspective(src,dst ,perspectiveMmat,src.size(),Imgproc.INTER_LINEAR);

        Rect roiArea = new Rect(0, 0, 200, 200);
        Mat dstRoi = new Mat(dst, roiArea);
        Imgcodecs.imwrite("F:\\output\\dstRoi-"+idx+".jpg", dstRoi);
    }

    public static BufferedImage toBufferedImage(Mat m) {
        int type = BufferedImage.TYPE_BYTE_GRAY;

        if (m.channels() > 1) {
            type = BufferedImage.TYPE_3BYTE_BGR;
        }

        int bufferSize = m.channels() * m.cols() * m.rows();
        byte[] b = new byte[bufferSize];
        m.get(0, 0, b); // get all the pixels
        BufferedImage image = new BufferedImage(m.cols(), m.rows(), type);

        final byte[] targetPixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
        System.arraycopy(b, 0, targetPixels, 0, b.length);

        return image;
    }

    public static Point centerCal(MatOfPoint matOfPoint){
        double centerx=0,centery=0;
        int size = matOfPoint.cols();
        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;
    }
}

需要说明一下:

double[] ds = hierarchy.get(0, i);

网上的资料ds[0]是后一个轮廓,ds[1]是前一个轮廓,ds[2]是父轮廓,ds[3]是内嵌轮廓, 但是通过实际测试ds[2]是内嵌轮廓 ds[3]是父轮廓 ,不知道跟版本有没有关系,还请自测。

2.网上大部分例子都是只定位3点的,但是实际图片如果模糊或者有干扰的话定位出来会是多个点,所以这里进行了循环的裁剪。

3.通过长短轴、面积和角度丢弃的点是没经过数值测试的

参考资料:

https://blog.csdn.net/iamqianrenzhan/article/details/79117119

https://www.jianshu.com/p/957f83f646cf?nomobile=yes

https://blog.csdn.net/marooon/article/details/81332487

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
3年前
Java爬虫之JSoup使用教程
title:Java爬虫之JSoup使用教程date:201812248:00:000800update:201812248:00:000800author:mecover:https://imgblog.csdnimg.cn/20181224144920712(https://www.oschin
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Java服务总在半夜挂,背后的真相竟然是... | 京东云技术团队
最近有用户反馈测试环境Java服务总在凌晨00:00左右挂掉,用户反馈Java服务没有定时任务,也没有流量突增的情况,Jvm配置也合理,莫名其妙就挂了
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这