├── 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
--------------------------------------------------------------------------------