很多时候我们拍摄的照⽚都会产⽣⼀点畸变的,就像下⾯的这张图
虽然不是很明显,但还是有⼀点畸变的,⽽我们要做的就是把它变成下⾯的这张图
效果看起来并不是很好,主要是四个顶点找的不准确,会有⼀些偏差,⽽且矫正后产⽣的⽬标图是倒着的,哪位好⼼⼈给说说为啥
因为我也没有测试畸变很⼤的图像,也不能保证⽅法适⽤于每个图像,这⾥仅提供我的思路供⼤家参考。思路:
我们最重要的就是找到图像的四个顶点,有利⽤hough直线,求直线交点确定四个顶点,有采⽤寻找轮廓确定四个顶点等等;今天我提供的思路,也是采⽤寻找轮廓的⽅法,⽤approxPolyDP函数,对图像轮廓点进⾏多边形拟合,可以得到⼤概的⼀个这样的图
可以看到图像的四个顶点处,都有⼩⽩点。接下来我们要做的就是把这些点归类,即划分出四个区域[左上,右上,右下,左下];我采⽤的是利⽤opencv的寻找轮廓,得到最⼤轮廓,然后⽣成最⼩外接矩形,确定四个顶点的⼤致位置;然后设置⼀个阀值,与上图中的点集合求距离,⼤于阀值的舍弃,⼩于的保留,可以得到如下的图像
这样所有的点集都落到了四个区域,利⽤矩形中,对⾓线距离最⼤,确定四个顶点的位置,发现效果并不是很好,如下图
到此四个顶点的位置⼤概的确定了,就只需要根据输⼊和输出点获得图像透视变换的矩阵,然后透视变换;
我们把思路再理⼀下:
1、寻找图像的四个顶点的坐标(重要)
思路: 1、canny描边 2、寻找最⼤轮廓 3、对最⼤轮廓点集合逼近,得到轮廓的⼤致点集合 4、把点击划分到四个区域中,即左上,右上,左下,右下 5、根据矩形中,对⾓线最长,找到矩形的四个顶点坐标
2、根据输⼊和输出点获得图像透视变换的矩阵3、透视变换
我们来跟着思路实现⼀下代码1、canny描边
/**
* canny算法,边缘检测 *
* @param src * @return */
public static Mat canny(Mat src) { Mat mat = src.clone();
Imgproc.Canny(src, mat, 60, 200);
HandleImgUtils.saveImg(mat, \"C:/Users/admin/Desktop/opencv/open/x/canny.jpg\"); return mat;}
2、寻找最⼤轮廓;3、对最⼤轮廓点集合逼近,得到轮廓的⼤致点集合(代码中有很多冗余,后期会进⾏优化)/**
* 利⽤函数approxPolyDP来对指定的点集进⾏逼近 精确度设置好,效果还是⽐较好的 *
* @param cannyMat */
public static Point[] useApproxPolyDPFindPoints(Mat cannyMat) { List // 寻找轮廓 Imgproc.findContours(cannyMat, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_NONE, new Point(0, 0)); // 找出匹配到的最⼤轮廓 double area = Imgproc.boundingRect(contours.get(0)).area(); int index = 0; // 找出匹配到的最⼤轮廓 for (int i = 0; i < contours.size(); i++) { double tempArea = Imgproc.boundingRect(contours.get(i)).area(); if (tempArea > area) { area = tempArea; index = i; } } MatOfPoint2f approxCurve = new MatOfPoint2f(); MatOfPoint2f matOfPoint2f = new MatOfPoint2f(contours.get(index).toArray()); // 原始曲线与近似曲线之间的最⼤距离设置为0.01,true表⽰是闭合的曲线 Imgproc.approxPolyDP(matOfPoint2f, approxCurve, 0.01, true); Point[] points = approxCurve.toArray(); return points; } 获取四个顶点的参照点 /** * 获取四个顶点的参照点,返回Point数组[左上,右上,右下,左下] 思路: 我们可以把四个点分成两部分,左部分,右部分 * 左部分:⾼的为左上,低的为左下(⾼低是以⼈的视觉) 右部分同理 ⾸先我们找到最左和最右的位置,以它们的两个中间为分界点, * 靠左的划分到左部分,靠右的划分到右部分 如果⼀个区域有三个或更多,哪个⽐较靠近分界线,划分到少的那个区域 * * @param cannyMat * @return */ public static Point[] findReferencePoint(Mat cannyMat) { RotatedRect rect = findMaxRect(cannyMat); Point[] referencePoints = new Point[4]; rect.points(referencePoints); double minX = Double.MAX_VALUE; double maxX = Double.MIN_VALUE; for (int i = 0; i < referencePoints.length; i++) { referencePoints[i].x = Math.abs(referencePoints[i].x); referencePoints[i].y = Math.abs(referencePoints[i].y); minX = referencePoints[i].x < minX ? referencePoints[i].x : minX; maxX = referencePoints[i].x > maxX ? referencePoints[i].x : maxX; } double center = (minX + maxX) / 2; List for (int i = 0; i < referencePoints.length; i++) { if (referencePoints[i].x < center) { leftPart.add(referencePoints[i]); } else if (referencePoints[i].x > center) { rightPart.add(referencePoints[i]); } else { if (leftPart.size() < rightPart.size()) { leftPart.add(referencePoints[i]); } else { rightPart.add(referencePoints[i]); } } } double minDistance = 0; int minIndex = 0; if (leftPart.size() < rightPart.size()) { // 左部分少 minDistance = rightPart.get(0).x - center; minIndex = 0; for (int i = 1; i < rightPart.size(); i++) { if (rightPart.get(i).x - center < minDistance) { minDistance = rightPart.get(i).x - center; minIndex = i; } } leftPart.add(rightPart.remove(minIndex)); } else if (leftPart.size() > rightPart.size()) { // 右部分少 minDistance = center - leftPart.get(0).x; minIndex = 0; for (int i = 1; i < leftPart.size(); i++) { if (center - leftPart.get(0).x < minDistance) { minDistance = center - leftPart.get(0).x; minIndex = i; } } rightPart.add(leftPart.remove(minIndex)); } if (leftPart.get(0).y < leftPart.get(1).y) { referencePoints[0] = leftPart.get(0); referencePoints[3] = leftPart.get(1); } if (rightPart.get(0).y < rightPart.get(1).y) { referencePoints[1] = rightPart.get(0); referencePoints[2] = rightPart.get(1); } return referencePoints; } 4、把点击划分到四个区域中,即左上,右上,右下,左下(效果还可以)/** * 把点击划分到四个区域中,即左上,右上,右下,左下 * * @param points * 逼近的点集 * @param referencePoints * 四个参照点集(通过寻找最⼤轮廓,进⾏minAreaRect得到四个点[左上,右上,右下,左下]) */ public static Map List for (int i = 0; i < referencePoints.length; i++) { for (int j = 0; j < points.length; j++) { distance = Math.pow(referencePoints[i].x - points[j].x, 2) + Math.pow(referencePoints[i].y - points[j].y, 2); if (distance < Math.pow(thresold, 2)) { if (i == 0) { px1.add(points[j]); } else if (i == 1) { py1.add(points[j]); } else if (i == 2) { py2.add(points[j]); } else if (i == 3) { px2.add(points[j]); } } else { continue; } } } Map 5、根据矩形中,对⾓线最长,找到矩形的四个顶点坐标(效果不好)/** * 具体的寻找四个顶点的坐标 * * @param map * 四个点集域 即左上,右上,右下,左下 * @return */ public static Point[] specificFindFourPoint(Map double maxDistance = 0; double tempDistance; int i, j; int p1 = 0, p2 = 0;// 记录点的下标 // 寻找左上,右下 for (i = 0; i < px1.size(); i++) { for (j = 0; j < py2.size(); j++) { tempDistance = Math.pow(px1.get(i).x - py2.get(j).x, 2) + Math.pow(px1.get(i).y - py2.get(j).y, 2); if (tempDistance > maxDistance) { maxDistance = tempDistance; p1 = i; p2 = j; } } } result[0] = px1.get(p1); result[2] = py2.get(p2); // 寻找左下,右上 maxDistance = 0; for (i = 0; i < px2.size(); i++) { for (j = 0; j < py1.size(); j++) { tempDistance = Math.pow(px2.get(i).x - py1.get(j).x, 2) + Math.pow(px2.get(i).y - py1.get(j).y, 2); if (tempDistance > maxDistance) { maxDistance = tempDistance; p1 = i; p2 = j; } } } result[1] = py1.get(p2); result[3] = px2.get(p1); return result; } 整合寻找四个顶点坐标函数 /** * 寻找四个顶点的坐标 思路: 1、canny描边 2、寻找最⼤轮廓 3、对最⼤轮廓点集合逼近,得到轮廓的⼤致点集合 * 4、把点击划分到四个区域中,即左上,右上,左下,右下 5、根据矩形中,对⾓线最长,找到矩形的四个顶点坐标 * * @param src */ public static Point[] findFourPoint(Mat src) { // 1、canny描边 Mat cannyMat = canny(src); // 2、寻找最⼤轮廓;3、对最⼤轮廓点集合逼近,得到轮廓的⼤致点集合 Point[] points = useApproxPolyDPFindPoints(cannyMat); //在图像上画出逼近的点 Mat approxPolyMat = src.clone(); for( int i = 0; i < points.length ; i++) { setPixel(approxPolyMat, (int)points[i].y, (int) points[i].x, 255); } saveImg(approxPolyMat, \"C:/Users/admin/Desktop/opencv/open/q/x11-approxPolyMat.jpg\"); // 获取参照点集 Point[] referencePoints = findReferencePoint(cannyMat); // 4、把点击划分到四个区域中,即左上,右上,左下,右下(效果还可以) Map List for (int i = 0; i < px1.size(); i++) { setPixel(areaMat, (int) px1.get(i).y, (int) px1.get(i).x, 255); } for (int i = 0; i < px2.size(); i++) { setPixel(areaMat, (int) px2.get(i).y, (int) px2.get(i).x, 255); } for (int i = 0; i < py1.size(); i++) { setPixel(areaMat, (int) py1.get(i).y, (int) py1.get(i).x, 255); } for (int i = 0; i < py2.size(); i++) { setPixel(areaMat, (int) py2.get(i).y, (int) py2.get(i).x, 255); } saveImg(areaMat, \"C:/Users/admin/Desktop/opencv/open/q/x11-pointsDivideArea.jpg\"); // 5、根据矩形中,对⾓线最长,找到矩形的四个顶点坐标(效果不好) Point[] result = specificFindFourPoint(map); return result; } 透视变换,矫正图像 /** * 透视变换,矫正图像 思路: 1、寻找图像的四个顶点的坐标(重要) 思路: 1、canny描边 2、寻找最⼤轮廓 * 3、对最⼤轮廓点集合逼近,得到轮廓的⼤致点集合 4、把点击划分到四个区域中,即左上,右上,左下,右下 5、根据矩形中,对⾓线最长,找到矩形的四个顶点坐标 * 2、根据输⼊和输出点获得图像透视变换的矩阵 3、透视变换 * * @param src */ public static Mat warpPerspective(Mat src) { // 灰度话 src = HandleImgUtils.gray(src); // 找到四个点 Point[] points = HandleImgUtils.findFourPoint(src); // Canny Mat cannyMat = HandleImgUtils.canny(src); // 寻找最⼤矩形 RotatedRect rect = HandleImgUtils.findMaxRect(cannyMat); // 点的顺序[左上 ,右上 ,右下 ,左下] List Rect r = rect.boundingRect(); r.x = Math.abs(r.x); r.y = Math.abs(r.y); List Mat dstPoints = Converters.vector_Point_to_Mat(listDsts, CvType.CV_32F); Mat perspectiveMmat = Imgproc.getPerspectiveTransform(srcPoints, dstPoints); Mat dst = new Mat(); Imgproc.warpPerspective(src, dst, perspectiveMmat, src.size(), Imgproc.INTER_LINEAR + Imgproc.WARP_INVERSE_MAP, 1, new Scalar(0)); return dst; }测试函数 /** * 测试透视变换 */ public void testWarpPerspective() { System.loadLibrary(Core.NATIVE_LIBRARY_NAME); Mat src = HandleImgUtils.matFactory(\"C:/Users/admin/Desktop/opencv/open/q/x10.jpg\"); src = HandleImgUtils.warpPerspective(src); HandleImgUtils.saveImg(src, \"C:/Users/admin/Desktop/opencv/open/q/x10-testWarpPerspective.jpg\"); } 本项⽬所有代码地址: 觉得写的不错话,还是希望能给个Star的
因篇幅问题不能全部显示,请点此查看更多更全内容
Copyright © 2019- fenyunshixun.cn 版权所有 湘ICP备2023022495号-9
违法及侵权请联系:TEL:199 18 7713 E-MAIL:2724546146@qq.com
本站由北京市万商天勤律师事务所王兴未律师提供法律服务