├── Cannyimg.jpg ├── Imgcorr.py ├── README.md ├── input.jpg ├── linedimg.jpg └── output.jpg /Cannyimg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joece/Image-Correction/b43f712ad04b3de05cf79fdb3ae2a3c772194fd9/Cannyimg.jpg -------------------------------------------------------------------------------- /Imgcorr.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import cv2 3 | 4 | def CrossPoint(line1, line2): 5 | x0, y0, x1, y1 = line1[0] 6 | x2, y2, x3, y3 = line2[0] 7 | 8 | dx1 = x1 - x0 9 | dy1 = y1 - y0 10 | 11 | dx2 = x3 - x2 12 | dy2 = y3 - y2 13 | 14 | D1 = x1 * y0 - x0 * y1 15 | D2 = x3 * y2 - x2 * y3 16 | 17 | y = float(dy1 * D2 - D1 * dy2) / (dy1 * dx2 - dx1 * dy2) 18 | x = float(y * dx1 - D1) / dy1 19 | 20 | return (int(x), int(y)) 21 | 22 | def SortPoint(points): 23 | sp = sorted(points, key = lambda x:(int(x[1]), int(x[0]))) 24 | if sp[0][0] > sp[1][0]: 25 | sp[0], sp[1] = sp[1], sp[0] 26 | 27 | if sp[2][0] > sp[3][0]: 28 | sp[2], sp[3] = sp[3], sp[2] 29 | 30 | return sp 31 | 32 | def imgcorr(src): 33 | rgbsrc = src.copy() 34 | graysrc = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY) 35 | blurimg = cv2.GaussianBlur(src, (3, 3), 0) 36 | Cannyimg = cv2.Canny(blurimg, 35, 189) 37 | 38 | lines = cv2.HoughLinesP(Cannyimg, 1, np.pi / 180, threshold = 30, minLineLength = 320, maxLineGap = 40) 39 | 40 | for i in range(int(np.size(lines)/4)): 41 | for x1, y1, x2, y2 in lines[i]: 42 | cv2.line(rgbsrc, (x1, y1), (x2, y2), (255, 255, 0), 3) 43 | 44 | points = np.zeros((4, 2), dtype = "float32") 45 | points[0] = CrossPoint(lines[0], lines[2]) 46 | points[1] = CrossPoint(lines[0], lines[3]) 47 | points[2] = CrossPoint(lines[1], lines[2]) 48 | points[3] = CrossPoint(lines[1], lines[3]) 49 | 50 | sp = SortPoint(points) 51 | 52 | width = int(np.sqrt(((sp[0][0] - sp[1][0]) ** 2) + (sp[0][1] - sp[1][1]) ** 2)) 53 | height = int(np.sqrt(((sp[0][0] - sp[2][0]) ** 2) + (sp[0][1] - sp[2][1]) ** 2)) 54 | 55 | dstrect = np.array([ 56 | [0, 0], 57 | [width - 1, 0], 58 | [0, height - 1], 59 | [width - 1, height - 1]], dtype = "float32") 60 | 61 | transform = cv2.getPerspectiveTransform(np.array(sp), dstrect) 62 | warpedimg = cv2.warpPerspective(src, transform, (width, height)) 63 | 64 | return warpedimg 65 | 66 | if __name__ == '__main__': 67 | src = cv2.imread("input.jpg") 68 | dst = imgcorr(src) 69 | cv2.imshow("Image", dst) 70 | cv2.waitKey(0) 71 | cv2.imwrite("output.jpg", dst) 72 | 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Image-Correction 2 | ## 实验目的 3 | 输入一张图像,图像中有一张 A4 纸,通过图像处理的方法将其校正,如下: 4 | 5 |  校正之后->  6 | 7 | ## 实验环境 8 | Win 10 系统,使用 python 语言以及 opencv 库 9 | 10 | ## 实验原理 11 | 通过对图像做边缘检测,然后运用霍夫线变换获取图像中 A4 纸的边缘线段,利用线段交点得出图像中 A4 纸的四个角点,然后通过几何图像变换,对 A4 纸通过透视变换进行校正 12 | 13 | ## 实验步骤 14 | 1. 读入图像,传入 imgcorr() 函数,并将图像转为灰度图后做一次高斯滤波,以除去一些干扰噪点,便于边缘检测提取校正内容 15 | ```python 16 | src = cv2.imread("input.jpg") #读入图像 17 | 18 | graysrc = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY) #转为灰度图 19 | blurimg = cv2.GaussianBlur(src, (3, 3), 0) #对灰度图做高斯滤波 20 | ``` 21 | 2. 对滤波后的灰度图做边缘检测提取,应用 opencv 提供的 Canny 函数提取边缘 22 | ```python 23 | Cannyimg = cv2.Canny(blurimg, 35, 189) 24 | ``` 25 |
26 |    提取边缘得到的灰度图如下: 27 |   
28 | 29 | >这里所用到的 Canny 函数运用了 1986 年 JOHN CANNY 提出的一个很好的边缘检测算法,它根据对信噪比与定位乘积进行测度,得到最优化逼近算子,也就是 Canny 算子。使用 Canny 边缘检测,必须满足以下两个条件: 30 | - 能有效地抑制噪声 31 | - 必须尽量精确确定边缘的位置 32 | 33 | >在这里不多加赘述 Canny 算法的具体原理,直接说明 Opencv 中的 Canny 函数的使用: 34 | void cv2::Canny(Mat gray, Mat canny, double threshold1, double threshold2, int aperture_size = 3) 35 | **gray** 36 | 单通道输入图像(灰度图) 37 | **canny** 38 | 用于储存边缘的单通道输出图像 39 | **threshold1** 40 | 第一个阈值 41 | **threshold2** 42 | 第二个阈值 43 | **aperture_size** 44 | Sobel 算子内核大小,可省略 45 | 46 | >Canny 算法使用了滞后阈值,有两个阈值参数(高阈值和低阈值): 47 | - **如果某个像素的幅值超过高阈值,该像素被保留为边缘像素** 48 | - **如果某个像素的幅值低于低阈值,则该像素被排除** 49 | - **如果某个像素的幅值在两个阈值之间,该像素仅仅在连接到一个高于高阈值的像素时被保留** 50 | 51 | 3. 再对边缘提取后的灰度图执行霍夫变换得到 A4 纸的边缘直线,并描绘在图像上以便观察,实现代码及结果如下: 52 | ```python 53 | lines = cv2.HoughLinesP(Cannyimg, 1, np.pi / 180, threshold = 30, minLineLength = 320, maxLineGap = 40) 54 | 55 | for i in range(int(np.size(lines)/4)): 56 | for x1, y1, x2, y2 in lines[i]: 57 | cv2.line(rgbsrc, (x1, y1), (x2, y2), (255, 255, 0), 3) 58 | ``` 59 |
60 |    通过霍夫变换得到相应的直线描绘在原图上效果如下: 61 |   
62 | 63 | >霍夫变换是一种用来寻找直线的方法,这里所用到的 HoughLinesP 函数就是运用了霍夫变换的原理,事实上,opencv中有两个运用霍夫变换获取线段的函数,分别是标准霍夫线变换 HoughLines 和 统计概率霍夫线变换 HoughLinesP,具体的原理在此也不多加赘述,有兴趣了解的大可谷歌必应一波,我就直接说一下这里用的统计概率霍夫线变换函数 HoughLinesP 的使用: 64 | void cv2::HoughLinesP(Mat src, Mat lines, doublerho, double theta, int threshold, double minLineLength, double maxLineGap) 65 | **src** 66 | 边缘检测的输出图像(二值灰度图) 67 | **lines** 68 | 用于储存检测到的线段的端点($x_{start}, y_{start}, x_{end}, y_{end}$)的容器 69 | **rho** 70 | 参数极径 r 以像素值为单位的分辨率。我们使用1像素 71 | **theta** 72 | 参数极角 θ 以弧度为单位的分辨率。我们使用1度(即CV_PI/180) 73 | **threshold** 74 | 要检测出一条直线所需最少的曲线交点 75 | **minLineLength** 76 | 能组成一条直线的最少点的数量。点数量不足的直接被抛弃 77 | **maxLineGap** 78 | 能被认为在一条直线上的两个点之间的最大距离 79 | 80 | 4. 根据霍夫变换所获得的直线端点,用求直线交点的方式,求 A4 纸的四个角点 81 | ```python 82 | //根据上述霍夫变换获得的线段求直线交点,实验证明,霍夫变换获取并存储直线时是横纵方向依次完成的,即只需如下形式计算 83 | points = np.zeros((4, 2), dtype = "float32") 84 | points[0] = CrossPoint(lines[0], lines[2]) 85 | points[1] = CrossPoint(lines[0], lines[3]) 86 | points[2] = CrossPoint(lines[1], lines[2]) 87 | points[3] = CrossPoint(lines[1], lines[3]) 88 | 89 | //根据线段端点计算对应直线交点,原理参考直线点斜式方程联立所解 90 | def CrossPoint(line1, line2): 91 | x0, y0, x1, y1 = line1[0] 92 | x2, y2, x3, y3 = line2[0] 93 | 94 | dx1 = x1 - x0 95 | dy1 = y1 - y0 96 | 97 | dx2 = x3 - x2 98 | dy2 = y3 - y2 99 | 100 | D1 = x1 * y0 - x0 * y1 101 | D2 = x3 * y2 - x2 * y3 102 | 103 | y = float(dy1 * D2 - D1 * dy2) / (dy1 * dx2 - dx1 * dy2) 104 | x = float(y * dx1 - D1) / dy1 105 | 106 | return (int(x), int(y)) 107 | ``` 108 | 然而,通过以上方式获取的四个角点的顺序并非规则的矩形角点顺序,还需要通过一定的排序方法对其进行排序,如下 109 | ```python 110 | def SortPoint(points): 111 | sp = sorted(points, key = lambda x:(int(x[1]), int(x[0]))) 112 | if sp[0][0] > sp[1][0]: 113 | sp[0], sp[1] = sp[1], sp[0] 114 | 115 | if sp[2][0] > sp[3][0]: 116 | sp[2], sp[3] = sp[3], sp[2] 117 | 118 | return sp 119 | ``` 120 | 5. 利用 opencv 提供的函数计算透视变换的矩阵,具体的透视变换原理和应用在这里我就不说了,留给大家寄几去学习吧 121 | ```python 122 | dstrect = np.array([ 123 | [0, 0], 124 | [width - 1, 0], 125 | [0, height - 1], 126 | [width - 1, height - 1]], dtype = "float32") 127 | 128 | transform = cv2.getPerspectiveTransform(np.array(sp), dstrect) #利用原图角点和目标角点计算透视变换矩阵 129 | ``` 130 | 6. 利用 opencv 提供的函数对图像做透视变换得到校正后的图像并输出 131 | ```java 132 | warpedimg = cv2.warpPerspective(src, transform, (width, height)) 133 | 134 | return warpedimg 135 | ``` 136 |    最终图像校正的效果如下: 137 |   
138 | 139 | -------------------------------------------------------------------------------- /input.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joece/Image-Correction/b43f712ad04b3de05cf79fdb3ae2a3c772194fd9/input.jpg -------------------------------------------------------------------------------- /linedimg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joece/Image-Correction/b43f712ad04b3de05cf79fdb3ae2a3c772194fd9/linedimg.jpg -------------------------------------------------------------------------------- /output.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joece/Image-Correction/b43f712ad04b3de05cf79fdb3ae2a3c772194fd9/output.jpg --------------------------------------------------------------------------------