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

例子使用的版本为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;
    }
}

需要说明一下:

1.

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

posted @ 2018-11-05 12:07  柳随风丶丶  阅读(8516)  评论(3编辑  收藏  举报