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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM