很多时候我们拍摄的照片都会产生一点畸变的,就像下面的这张图

虽然不是很明显,但还是有一点畸变的,而我们要做的就是把它变成下面的这张图

效果看起来并不是很好,主要是四个顶点找的不准确,会有一些偏差,而且矫正后产生的目标图是倒着的,哪位好心人给说说为啥

因为我也没有测试畸变很大的图像,也不能保证方法适用于每个图像,这里仅提供我的思路供大家参考。

思路:

我们最重要的就是找到图像的四个顶点,有利用hough直线,求直线交点确定四个顶点,有采用寻找轮廓确定四个顶点等等;今天我提供的思路,也是采用寻找轮廓的方法,用approxPolyDP函数,对图像轮廓点进行多边形拟合,可以得到大概的一个这样的图

可以看到图像的四个顶点处,都有小白点。接下来我们要做的就是把这些点归类,即划分出四个区域[左上,右上,右下,左下];我采用的是利用opencv的寻找轮廓,得到最大轮廓,然后生成最小外接矩形,确定四个顶点的大致位置;然后设置一个阀值,与上图中的点集合求距离,大于阀值的舍弃,小于的保留,可以得到如下的图像

这样所有的点集都落到了四个区域,利用矩形中,对角线距离最大,确定四个顶点的位置,发现效果并不是很好,如下图

到此四个顶点的位置大概的确定了,就只需要根据输入和输出点获得图像透视变换的矩阵,然后透视变换;

我们把思路再理一下:

1、寻找图像的四个顶点的坐标(重要)

思路: 1、canny描边 2、寻找最大轮廓 3、对最大轮廓点集合逼近,得到轮廓的大致点集合 4、把点击划分到四个区域中,即左上,右上,左下,右下 5、根据矩形中,对角线最长,找到矩形的四个顶点坐标

2、根据输入和输出点获得图像透视变换的矩阵

3、透视变换

我们来跟着思路实现一下代码

1、canny描边

  1. /**
  2. * canny算法,边缘检测
  3. *
  4. * @param src
  5. * @return
  6. */
  7. public static Mat canny(Mat src) {
  8. Mat mat = src.clone();
  9. Imgproc.Canny(src, mat, 60, 200);
  10. HandleImgUtils.saveImg(mat, "C:/Users/admin/Desktop/opencv/open/x/canny.jpg");
  11. return mat;
  12. }

2、寻找最大轮廓;3、对最大轮廓点集合逼近,得到轮廓的大致点集合(代码中有很多冗余,后期会进行优化)

  1. /**
  2. * 利用函数approxPolyDP来对指定的点集进行逼近 精确度设置好,效果还是比较好的
  3. *
  4. * @param cannyMat
  5. */
  6. public static Point[] useApproxPolyDPFindPoints(Mat cannyMat) {
  7. List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
  8. Mat hierarchy = new Mat();
  9. // 寻找轮廓
  10. Imgproc.findContours(cannyMat, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_NONE,
  11. new Point(0, 0));
  12. // 找出匹配到的最大轮廓
  13. double area = Imgproc.boundingRect(contours.get(0)).area();
  14. int index = 0;
  15. // 找出匹配到的最大轮廓
  16. for (int i = 0; i < contours.size(); i++) {
  17. double tempArea = Imgproc.boundingRect(contours.get(i)).area();
  18. if (tempArea > area) {
  19. area = tempArea;
  20. index = i;
  21. }
  22. }
  23. MatOfPoint2f approxCurve = new MatOfPoint2f();
  24. MatOfPoint2f matOfPoint2f = new MatOfPoint2f(contours.get(index).toArray());
  25. // 原始曲线与近似曲线之间的最大距离设置为0.01,true表示是闭合的曲线
  26. Imgproc.approxPolyDP(matOfPoint2f, approxCurve, 0.01, true);
  27. Point[] points = approxCurve.toArray();
  28. return points;
  29. }

获取四个顶点的参照点

  1. /**
  2. * 获取四个顶点的参照点,返回Point数组[左上,右上,右下,左下] 思路: 我们可以把四个点分成两部分,左部分,右部分
  3. * 左部分:高的为左上,低的为左下(高低是以人的视觉) 右部分同理 首先我们找到最左和最右的位置,以它们的两个中间为分界点,
  4. * 靠左的划分到左部分,靠右的划分到右部分 如果一个区域有三个或更多,哪个比较靠近分界线,划分到少的那个区域
  5. *
  6. * @param cannyMat
  7. * @return
  8. */
  9. public static Point[] findReferencePoint(Mat cannyMat) {
  10. RotatedRect rect = findMaxRect(cannyMat);
  11. Point[] referencePoints = new Point[4];
  12. rect.points(referencePoints);
  13. double minX = Double.MAX_VALUE;
  14. double maxX = Double.MIN_VALUE;
  15. for (int i = 0; i < referencePoints.length; i++) {
  16. referencePoints[i].x = Math.abs(referencePoints[i].x);
  17. referencePoints[i].y = Math.abs(referencePoints[i].y);
  18. minX = referencePoints[i].x < minX ? referencePoints[i].x : minX;
  19. maxX = referencePoints[i].x > maxX ? referencePoints[i].x : maxX;
  20. }
  21. double center = (minX + maxX) / 2;
  22. List<Point> leftPart = new ArrayList<Point>();
  23. List<Point> rightPart = new ArrayList<Point>();
  24. // 划分左右两个部分
  25. for (int i = 0; i < referencePoints.length; i++) {
  26. if (referencePoints[i].x < center) {
  27. leftPart.add(referencePoints[i]);
  28. } else if (referencePoints[i].x > center) {
  29. rightPart.add(referencePoints[i]);
  30. } else {
  31. if (leftPart.size() < rightPart.size()) {
  32. leftPart.add(referencePoints[i]);
  33. } else {
  34. rightPart.add(referencePoints[i]);
  35. }
  36. }
  37. }
  38. double minDistance = 0;
  39. int minIndex = 0;
  40. if (leftPart.size() < rightPart.size()) {
  41. // 左部分少
  42. minDistance = rightPart.get(0).x - center;
  43. minIndex = 0;
  44. for (int i = 1; i < rightPart.size(); i++) {
  45. if (rightPart.get(i).x - center < minDistance) {
  46. minDistance = rightPart.get(i).x - center;
  47. minIndex = i;
  48. }
  49. }
  50. leftPart.add(rightPart.remove(minIndex));
  51. } else if (leftPart.size() > rightPart.size()) {
  52. // 右部分少
  53. minDistance = center - leftPart.get(0).x;
  54. minIndex = 0;
  55. for (int i = 1; i < leftPart.size(); i++) {
  56. if (center - leftPart.get(0).x < minDistance) {
  57. minDistance = center - leftPart.get(0).x;
  58. minIndex = i;
  59. }
  60. }
  61. rightPart.add(leftPart.remove(minIndex));
  62. }
  63. if (leftPart.get(0).y < leftPart.get(1).y) {
  64. referencePoints[0] = leftPart.get(0);
  65. referencePoints[3] = leftPart.get(1);
  66. }
  67. if (rightPart.get(0).y < rightPart.get(1).y) {
  68. referencePoints[1] = rightPart.get(0);
  69. referencePoints[2] = rightPart.get(1);
  70. }
  71. return referencePoints;
  72. }

4、把点击划分到四个区域中,即左上,右上,右下,左下(效果还可以)

  1. /**
  2. * 把点击划分到四个区域中,即左上,右上,右下,左下
  3. *
  4. * @param points
  5. * 逼近的点集
  6. * @param referencePoints
  7. * 四个参照点集(通过寻找最大轮廓,进行minAreaRect得到四个点[左上,右上,右下,左下])
  8. */
  9. public static Map<String, List> pointsDivideArea(Point[] points, Point[] referencePoints) {
  10. // px1 左上,px2左下,py1右上,py2右下
  11. List<Point> px1 = new ArrayList<Point>(), px2 = new ArrayList<Point>(), py1 = new ArrayList<Point>(),
  12. py2 = new ArrayList<Point>();
  13. int thresold = 50;// 设置距离阀值
  14. double distance = 0;
  15. for (int i = 0; i < referencePoints.length; i++) {
  16. for (int j = 0; j < points.length; j++) {
  17. distance = Math.pow(referencePoints[i].x - points[j].x, 2)
  18. + Math.pow(referencePoints[i].y - points[j].y, 2);
  19. if (distance < Math.pow(thresold, 2)) {
  20. if (i == 0) {
  21. px1.add(points[j]);
  22. } else if (i == 1) {
  23. py1.add(points[j]);
  24. } else if (i == 2) {
  25. py2.add(points[j]);
  26. } else if (i == 3) {
  27. px2.add(points[j]);
  28. }
  29. } else {
  30. continue;
  31. }
  32. }
  33. }
  34. Map<String, List> map = new HashMap<String, List>();
  35. map.put("px1", px1);
  36. map.put("px2", px2);
  37. map.put("py1", py1);
  38. map.put("py2", py2);
  39. return map;
  40. }

5、根据矩形中,对角线最长,找到矩形的四个顶点坐标(效果不好)

  1. /**
  2. * 具体的寻找四个顶点的坐标
  3. *
  4. * @param map
  5. * 四个点集域 即左上,右上,右下,左下
  6. * @return
  7. */
  8. public static Point[] specificFindFourPoint(Map<String, List> map) {
  9. Point[] result = new Point[4];// [左上,右上,右下,左下]
  10. List<Point> px1 = map.get("px1");// 左上
  11. List<Point> px2 = map.get("px2");// 左下
  12. List<Point> py1 = map.get("py1");// 右上
  13. List<Point> py2 = map.get("py2");// 右下
  14. System.out.println("px1.size() " + px1.size());
  15. System.out.println("px2.size() " + px2.size());
  16. System.out.println("py1.size() " + py1.size());
  17. System.out.println("py2.size() " + py2.size());
  18. double maxDistance = 0;
  19. double tempDistance;
  20. int i, j;
  21. int p1 = 0, p2 = 0;// 记录点的下标
  22. // 寻找左上,右下
  23. for (i = 0; i < px1.size(); i++) {
  24. for (j = 0; j < py2.size(); j++) {
  25. tempDistance = Math.pow(px1.get(i).x - py2.get(j).x, 2) + Math.pow(px1.get(i).y - py2.get(j).y, 2);
  26. if (tempDistance > maxDistance) {
  27. maxDistance = tempDistance;
  28. p1 = i;
  29. p2 = j;
  30. }
  31. }
  32. }
  33. result[0] = px1.get(p1);
  34. result[2] = py2.get(p2);
  35. // 寻找左下,右上
  36. maxDistance = 0;
  37. for (i = 0; i < px2.size(); i++) {
  38. for (j = 0; j < py1.size(); j++) {
  39. tempDistance = Math.pow(px2.get(i).x - py1.get(j).x, 2) + Math.pow(px2.get(i).y - py1.get(j).y, 2);
  40. if (tempDistance > maxDistance) {
  41. maxDistance = tempDistance;
  42. p1 = i;
  43. p2 = j;
  44. }
  45. }
  46. }
  47. result[1] = py1.get(p2);
  48. result[3] = px2.get(p1);
  49. return result;
  50. }

整合寻找四个顶点坐标函数

  1. /**
  2. * 寻找四个顶点的坐标 思路: 1、canny描边 2、寻找最大轮廓 3、对最大轮廓点集合逼近,得到轮廓的大致点集合
  3. * 4、把点击划分到四个区域中,即左上,右上,左下,右下 5、根据矩形中,对角线最长,找到矩形的四个顶点坐标
  4. *
  5. * @param src
  6. */
  7. public static Point[] findFourPoint(Mat src) {
  8. // 1、canny描边
  9. Mat cannyMat = canny(src);
  10. // 2、寻找最大轮廓;3、对最大轮廓点集合逼近,得到轮廓的大致点集合
  11. Point[] points = useApproxPolyDPFindPoints(cannyMat);
  12. //在图像上画出逼近的点
  13. Mat approxPolyMat = src.clone();
  14. for( int i = 0; i < points.length ; i++) {
  15. setPixel(approxPolyMat, (int)points[i].y, (int) points[i].x, 255);
  16. }
  17. saveImg(approxPolyMat, "C:/Users/admin/Desktop/opencv/open/q/x11-approxPolyMat.jpg");
  18. // 获取参照点集
  19. Point[] referencePoints = findReferencePoint(cannyMat);
  20. // 4、把点击划分到四个区域中,即左上,右上,左下,右下(效果还可以)
  21. Map<String, List> map = pointsDivideArea(points, referencePoints);
  22. // 画出标记四个区域中的点集
  23. Mat areaMat = src.clone();
  24. List<Point> px1 = map.get("px1");// 左上
  25. List<Point> px2 = map.get("px2");// 左下
  26. List<Point> py1 = map.get("py1");// 右上
  27. List<Point> py2 = map.get("py2");// 右下
  28. for (int i = 0; i < px1.size(); i++) {
  29. setPixel(areaMat, (int) px1.get(i).y, (int) px1.get(i).x, 255);
  30. }
  31. for (int i = 0; i < px2.size(); i++) {
  32. setPixel(areaMat, (int) px2.get(i).y, (int) px2.get(i).x, 255);
  33. }
  34. for (int i = 0; i < py1.size(); i++) {
  35. setPixel(areaMat, (int) py1.get(i).y, (int) py1.get(i).x, 255);
  36. }
  37. for (int i = 0; i < py2.size(); i++) {
  38. setPixel(areaMat, (int) py2.get(i).y, (int) py2.get(i).x, 255);
  39. }
  40. saveImg(areaMat, "C:/Users/admin/Desktop/opencv/open/q/x11-pointsDivideArea.jpg");
  41. // 5、根据矩形中,对角线最长,找到矩形的四个顶点坐标(效果不好)
  42. Point[] result = specificFindFourPoint(map);
  43. return result;
  44. }

透视变换,矫正图像

  1. /**
  2. * 透视变换,矫正图像 思路: 1、寻找图像的四个顶点的坐标(重要) 思路: 1、canny描边 2、寻找最大轮廓
  3. * 3、对最大轮廓点集合逼近,得到轮廓的大致点集合 4、把点击划分到四个区域中,即左上,右上,左下,右下 5、根据矩形中,对角线最长,找到矩形的四个顶点坐标
  4. * 2、根据输入和输出点获得图像透视变换的矩阵 3、透视变换
  5. *
  6. * @param src
  7. */
  8. public static Mat warpPerspective(Mat src) {
  9. // 灰度话
  10. src = HandleImgUtils.gray(src);
  11. // 找到四个点
  12. Point[] points = HandleImgUtils.findFourPoint(src);
  13. // Canny
  14. Mat cannyMat = HandleImgUtils.canny(src);
  15. // 寻找最大矩形
  16. RotatedRect rect = HandleImgUtils.findMaxRect(cannyMat);
  17. // 点的顺序[左上 ,右上 ,右下 ,左下]
  18. List<Point> listSrcs = java.util.Arrays.asList(points[0], points[1], points[2], points[3]);
  19. Mat srcPoints = Converters.vector_Point_to_Mat(listSrcs, CvType.CV_32F);
  20. Rect r = rect.boundingRect();
  21. r.x = Math.abs(r.x);
  22. r.y = Math.abs(r.y);
  23. List<Point> listDsts = java.util.Arrays.asList(new Point(r.x, r.y), new Point(r.x + r.width, r.y),
  24. new Point(r.x + r.width, r.y + r.height), new Point(r.x, r.y + r.height));
  25. System.out.println(r.x + "," + r.y);
  26. Mat dstPoints = Converters.vector_Point_to_Mat(listDsts, CvType.CV_32F);
  27. Mat perspectiveMmat = Imgproc.getPerspectiveTransform(srcPoints, dstPoints);
  28. Mat dst = new Mat();
  29. Imgproc.warpPerspective(src, dst, perspectiveMmat, src.size(), Imgproc.INTER_LINEAR + Imgproc.WARP_INVERSE_MAP,
  30. 1, new Scalar(0));
  31. return dst;
  32. }

测试函数

  1. /**
  2. * 测试透视变换
  3. */
  4. public void testWarpPerspective() {
  5. System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
  6. Mat src = HandleImgUtils.matFactory("C:/Users/admin/Desktop/opencv/open/q/x10.jpg");
  7. src = HandleImgUtils.warpPerspective(src);
  8. HandleImgUtils.saveImg(src, "C:/Users/admin/Desktop/opencv/open/q/x10-testWarpPerspective.jpg");
  9. }

本项目所有代码地址:https://github.com/YLDarren/opencvHandleImg

觉得写的不错话,还是希望能给个Star的

Java基于opencv—透视变换矫正图像的更多相关文章

  1. Java基于opencv实现图像数字识别(五)—投影法分割字符

    Java基于opencv实现图像数字识别(五)-投影法分割字符 水平投影法 1.水平投影法就是先用一个数组统计出图像每行黑色像素点的个数(二值化的图像): 2.选出一个最优的阀值,根据比这个阀值大或小 ...

  2. Java基于opencv实现图像数字识别(四)—图像降噪

    Java基于opencv实现图像数字识别(四)-图像降噪 我们每一步的工作都是基于前一步的,我们先把我们前面的几个函数封装成一个工具类,以后我们所有的函数都基于这个工具类 这个工具类呢,就一个成员变量 ...

  3. Java基于opencv实现图像数字识别(三)—灰度化和二值化

    Java基于opencv实现图像数字识别(三)-灰度化和二值化 一.灰度化 灰度化:在RGB模型中,如果R=G=B时,则彩色表示灰度颜色,其中R=G=B的值叫灰度值:因此,灰度图像每个像素点只需一个字 ...

  4. Java基于opencv实现图像数字识别(二)—基本流程

    Java基于opencv实现图像数字识别(二)-基本流程 做一个项目之前呢,我们应该有一个总体把握,或者是进度条:来一步步的督促着我们来完成这个项目,在我们正式开始前呢,我们先讨论下流程. 我做的主要 ...

  5. Java基于opencv实现图像数字识别(一)

    Java基于opencv实现图像数字识别(一) 最近分到了一个任务,要做数字识别,我分配到的任务是把数字一个个的分开:当时一脸懵逼,直接百度java如何分割图片中的数字,然后就百度到了用Buffere ...

  6. Java基于OpenCV实现走迷宫(图片+路线展示)

    Java基于OpenCV实现走迷宫(图片+路线展示) 由于疫情,待在家中,太过无聊.同学发了我张迷宫图片,让我走迷宫来缓解暴躁,于是乎就码了一个程序出来.特此记录. 原图: 这张图,由于不是非常清晰, ...

  7. Java基于opencv—矫正图像

    更多的时候,我们得到的图像不可能是正的,多少都会有一定的倾斜,就比如下面的 我们要做的就是把它们变成下面这样的 我们采用的是寻找轮廓的思路,来矫正图片:只要有明显的轮廓都可以采用这种思路 具体思路: ...

  8. 对倾斜的图像进行修正——基于opencv 透视变换

    这篇文章主要解决这样一个问题: 有一张倾斜了的图片(当然是在Z轴上也有倾斜,不然直接旋转得了o(╯□╰)o),如何尽量将它纠正到端正的状态. 而要解决这样一个问题,可以用到透视变换. 关于透视变换的原 ...

  9. Java基于opencv实现图像数字识别(五)—腐蚀、膨胀处理

    腐蚀:去除图像表面像素,将图像逐步缩小,以达到消去点状图像的效果:作用就是将图像边缘的毛刺剔除掉 膨胀:将图像表面不断扩散以达到去除小孔的效果:作用就是将目标的边缘或者是内部的坑填掉 使用相同次数的腐 ...

随机推荐

  1. 在配置hibernate.cfg.xml时需指定使用数据库的方言:

    在配置hibernate.cfg.xml时需指定使用数据库的方言: 例: <property name="dialect">org.hibernate.dialect. ...

  2. C++学习笔记:多态篇之虚析构函数

    动态多态中存在的问题:可能会产生内存泄漏! 以下通过一个例子向大家说明问什么会产生内存泄漏: class Shape//形状类 { public: Shape(); virtual double ca ...

  3. HIbernate常见异常(转载)

    SSH阶段常见的异常系列之一hibernate(15条) 异常一 异常一 异常描述: Sax解析异常:cvc-复杂的类型,发现了以元素maping开头的无效内容,应该是以 ‘{“http://www. ...

  4. pyinstaller深入使用,打包指定模块,打包静态文件

    1.标准用法: pyinstall  **.py  直接打包    pyinstall -F **.py  打包成单文件    pyinstall -W **.py  去掉控制台窗口,黑窗口    p ...

  5. react-redux-reducer

    reducer是对dispatch(action)的响应,是一个纯函数,接受旧的state和action,返回新的state. //纯函数要注意的点,下面的例子myname不变 <script& ...

  6. python基础之作业3----三级菜单小练习

    data = { "华为技术":{ "产品与解决方案":{ "云核心网":{"云核心网研发管理部","云核心网 ...

  7. java导出Excel定义导出模板

    在很多系统功能中都会有Excel导入导出功能,小编采用JXLS工具,简单.灵活. JXLS是基于 Jakarta POI API 的Excel报表生成工具,它采用标签的方式,类似于jsp页面的EL表达 ...

  8. 多线程之 Thread类

    一.多线程第一种方式的实现步骤(继承Thread类) 代码演示: 1.定义MyThread类,继承Thread类 2.重写了里面的run方法,在run方法中定义线程要执行的任务 public clas ...

  9. github下载项目代码到本地,不能运行 本地改完代码 再上传

    首先用git bash here,在指定目录下执行, git clone 将项目拉取下来, 试运行: 发现需要配置idea的SDK/jdk, 还要选择language level, 建立输出目录tar ...

  10. SoapUI--the use of Script Library

    SoapUI--the use of Script Library 有两种方法在soapUI中引用自己的groovy脚本库. 方法一:把自己的script folder放到soapUI install ...