ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

OpenCV Java 实现票据、纸张的四边形边缘检测与提取、摆正

2019-09-18 17:07:34  阅读:320  来源: 互联网

标签:Imgproc Java Mat get Point lastHullPointList OpenCV 摆正 new


    最近公司让研究用opencv来做发票的提取摆正,简单的说就是如下图所示

原图:

结果:

说说过程吧,网上找了很多范例,也试了很多。很多贴的代码都不全,要么就不是用java来实现的,下面是实现如上功能的具体java代码:

public static void main(String[] args) {
    logger.info("测试票据、纸张的四边形边缘检测与提取、摆正程序开始....");
    //这个必须配置,否则会报错
    System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
    Mat img = Imgcodecs.imread("D:\\orc\\7.jpg");
    if(img.empty()){
        logger.info("读取的图片信息为空");
        return;
    }
    Mat greyImg = img.clone();
    //1.彩色转灰色
    Imgproc.cvtColor(img, greyImg, Imgproc.COLOR_BGR2GRAY);
    createImage(greyImg, "D:\\orc\\huise.jpg");

    Mat gaussianBlurImg = greyImg.clone();
    // 2.高斯滤波,降噪
    Imgproc.GaussianBlur(greyImg, gaussianBlurImg, new Size(3,3),2,2);
    createImage(gaussianBlurImg, "D:\\orc\\jiangzao.jpg");

    Mat cannyImg = gaussianBlurImg.clone();
    // 3.Canny边缘检测
    Imgproc.Canny(gaussianBlurImg, cannyImg, 20, 60, 3, false);
    createImage(cannyImg, "D:\\orc\\bianyuanjiance.jpg");
    // 4.膨胀,连接边缘
    Mat dilateImg = cannyImg.clone();
    Imgproc.dilate(cannyImg, dilateImg, new Mat(), new Point(-1, -1), 2, 1, new Scalar(1));
    createImage(dilateImg, "D:\\orc\\bianyuanjiance2.jpg");
    //5.对边缘检测的结果图再进行轮廓提取
    List<MatOfPoint> contours = new ArrayList<>();
    List<MatOfPoint> drawContours = new ArrayList<>();
    Mat hierarchy = new Mat();
    Imgproc.findContours(dilateImg, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
    Mat linePic = Mat.zeros(dilateImg.rows(), dilateImg.cols(), CvType.CV_8UC3);

    //6.找出轮廓对应凸包的四边形拟合
    List<MatOfPoint> squares = new ArrayList<>();
    List<MatOfPoint> hulls = new ArrayList<>();
    MatOfInt hull = new MatOfInt();
    MatOfPoint2f approx = new MatOfPoint2f();
    approx.convertTo(approx, CvType.CV_32F);

    for (int i = 0; i < contours.size(); i++) {
        MatOfPoint contour = contours.get(i);
        // 边框的凸包
        Imgproc.convexHull(contour, hull);
        // 用凸包计算出新的轮廓点
        Point[] contourPoints = contour.toArray();
        int[] indices = hull.toArray();
        List<Point> newPoints = new ArrayList<>();
        for (int index : indices) {
            newPoints.add(contourPoints[index]);
        }
        MatOfPoint2f contourHull = new MatOfPoint2f();
        contourHull.fromList(newPoints);
        // 多边形拟合凸包边框(此时的拟合的精度较低)
        Imgproc.approxPolyDP(contourHull, approx, Imgproc.arcLength(contourHull, true)*0.02, true);
        MatOfPoint mat = new MatOfPoint();
        mat.fromArray(approx.toArray());
        drawContours.add(mat);
        // 筛选出面积大于某一阈值的,且四边形的各个角度都接近直角的凸四边形
        MatOfPoint approxf1 = new MatOfPoint();
        approx.convertTo(approxf1, CvType.CV_32S);
        if (approx.rows() == 4 && Math.abs(Imgproc.contourArea(approx)) > 40000 &&
                Imgproc.isContourConvex(approxf1)) {
            double maxCosine = 0;
            for (int j = 2; j < 5; j++) {
                double cosine = Math.abs(getAngle(approxf1.toArray()[j%4], approxf1.toArray()[j-2], approxf1.toArray()[j-1]));
                maxCosine = Math.max(maxCosine, cosine);
            }
            // 角度大概72度
            if (maxCosine < 0.3) {
                MatOfPoint tmp = new MatOfPoint();
                contourHull.convertTo(tmp, CvType.CV_32S);
                squares.add(approxf1);
                hulls.add(tmp);
            }
        }
    }
    //这里是把提取出来的轮廓通过不同颜色的线描述出来,具体效果可以自己去看
    Random r = new Random();
    for (int i = 0; i < drawContours.size(); i++) {
        Imgproc.drawContours(linePic, drawContours, i, new Scalar(r.nextInt(255),r.nextInt(255), r.nextInt(255)));
    }
    createImage(linePic, "D:\\orc\\drawContours1.jpg");
    //7.找出最大的矩形
    int index = findLargestSquare(squares);
    MatOfPoint largest_square = squares.get(index);
    Mat polyPic = Mat.zeros(img.size(), CvType.CV_8UC3);
    Imgproc.drawContours(polyPic, squares, index, new Scalar(0, 0,255), 2);
    createImage(polyPic, "D:\\orc\\drawContours2.jpg");
    //存储矩形的四个凸点
    hull = new MatOfInt();
    Imgproc.convexHull(largest_square, hull, false);
    List<Integer> hullList =  hull.toList();
    List<Point> polyContoursList = largest_square.toList();
    List<Point> hullPointList = new ArrayList<>();
    List<Point> lastHullPointList = new ArrayList<>();
    for(int i = 0; i < hullList.size();i++){
        Imgproc.circle(polyPic, polyContoursList.get(hullList.get(i)), 10, new Scalar(r.nextInt(255),r.nextInt(255), r.nextInt(255), 3));
        hullPointList.add(polyContoursList.get(hullList.get(i)));
        System.out.println(hullList.get(i));
    }
    Core.addWeighted(polyPic, 0.5, img, 0.5, 0, img);
    createImage(img, "D:\\orc\\addWeighted.jpg");
    for(int i = 0; i < hullPointList.size(); i++){
        lastHullPointList.add(hullPointList.get(i));
    }
    //dstPoints储存的是变换后各点的坐标,依次为左上,右上,右下, 左下
    //srcPoints储存的是上面得到的四个角的坐标
    Point[] dstPoints = {new Point(0,0), new Point(img.cols(),0), new Point(img.cols(),img.rows()), new Point(0,img.rows())};
    Point[] srcPoints = new Point[4];
    boolean sorted = false;
    int n = 4;
    //对四个点进行排序 分出左上 右上 右下 左下
    while (!sorted){
        for (int i = 1; i < n; i++){
            sorted = true;
            if (lastHullPointList.get(i-1).x > lastHullPointList.get(i).x){
                Point tempp1 = lastHullPointList.get(i);
                Point tempp2 = lastHullPointList.get(i-1);
                lastHullPointList.set(i, tempp2);
                lastHullPointList.set(i-1, tempp1);
                sorted = false;
            }
        }
        n--;
    }

    //即先对四个点的x坐标进行冒泡排序分出左右,再根据两对坐标的y值比较分出上下
    if (lastHullPointList.get(0).y < lastHullPointList.get(1).y){
        srcPoints[0] = lastHullPointList.get(0);
        srcPoints[3] = lastHullPointList.get(1);
    }else{
        srcPoints[0] = lastHullPointList.get(1);
        srcPoints[3] = lastHullPointList.get(0);
    }
    if (lastHullPointList.get(2).y < lastHullPointList.get(3).y){
        srcPoints[1] = lastHullPointList.get(2);
        srcPoints[2] = lastHullPointList.get(3);
    }else{
        srcPoints[1] = lastHullPointList.get(3);
        srcPoints[2] = lastHullPointList.get(2);
    }
    List<Point> listSrcs = java.util.Arrays.asList(srcPoints[0], srcPoints[1], srcPoints[2], srcPoints[3]);
    Mat srcPointsMat = Converters.vector_Point_to_Mat(listSrcs, CvType.CV_32F);

    List<Point> dstSrcs = java.util.Arrays.asList(dstPoints[0], dstPoints[1], dstPoints[2], dstPoints[3]);
    Mat dstPointsMat = Converters.vector_Point_to_Mat(dstSrcs, CvType.CV_32F);
    //参数分别为输入输出图像、变换矩阵、大小。
    //坐标变换后就得到了我们要的最终图像。
    Mat transMat = Imgproc.getPerspectiveTransform(srcPointsMat, dstPointsMat);    //得到变换矩阵
    Mat outPic = new Mat();
    Imgproc.warpPerspective(img, outPic, transMat, img.size());
    createImage(outPic, "D:\\orc\\outPic.jpg");

    logger.info("测试票据、纸张的四边形边缘检测与提取、摆正程序结束....");
}

// 根据三个点计算中间那个点的夹角   pt1 pt0 pt2
private static double getAngle(Point pt1, Point pt2, Point pt0)
{
    double dx1 = pt1.x - pt0.x;
    double dy1 = pt1.y - pt0.y;
    double dx2 = pt2.x - pt0.x;
    double dy2 = pt2.y - pt0.y;
    return (dx1*dx2 + dy1*dy2)/Math.sqrt((dx1*dx1 + dy1*dy1)*(dx2*dx2 + dy2*dy2) + 1e-10);
}

// 找到最大的正方形轮廓
private static int findLargestSquare(List<MatOfPoint> squares) {
    if (squares.size() == 0)
        return -1;
    int max_width = 0;
    int max_height = 0;
    int max_square_idx = 0;
    int currentIndex = 0;
    for (MatOfPoint square : squares) {
        Rect rectangle = Imgproc.boundingRect(square);
        if (rectangle.width >= max_width && rectangle.height >= max_height) {
            max_width = rectangle.width;
            max_height = rectangle.height;
            max_square_idx = currentIndex;
        }
        currentIndex++;
    }
    return max_square_idx;
}


/**
 * 创建一张图片到指定位置
 * @param mat
 * @param fileName
 */
public static void createImage(Mat mat, String fileName) {
    Mat dst = mat.clone();
    Imgcodecs.imwrite(fileName, dst);
}

 

代码是经过本机测试的,使用如上图片是没有问题的,这里只是提供了一种大体思路,本文参考了:https://www.cnblogs.com/josephkim/p/8319069.html 

标签:Imgproc,Java,Mat,get,Point,lastHullPointList,OpenCV,摆正,new
来源: https://blog.csdn.net/kardelpeng/article/details/100986912

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有