图像倾斜矫正

文章目录
  1. 1. 理论
  2. 2. 实践
    1. 2.1. 边缘检测
    2. 2.2. 计算倾斜角
    3. 2.3. 仿射变换
    4. 2.4. Z轴倾斜
  3. 3. 后记
  4. 4. 书签

理论

没有找到关于图像倾斜矫正的综述性文献,那就自己整理一下吧。

图像倾斜可以分为两种情况,一种是平面倾斜,这种情况下拍照设备与试卷平行,拍出来的图像只需要进行旋转即可完成矫正;另一种是Z轴倾斜,这种情况下拍照设备与试卷存在一定的角度,拍出来的图像要先进行透视变换,然后再进行旋转等操作才可以完成矫正。

图像倾斜矫正关键在于根据图像特征自动检测出图像倾斜方向和倾斜角度。

对于平面倾斜,先利用边缘(轮廓)检测算法算法找到图像的边界,然后利用Radon变换法(基于投影的方法)Hough变换法、线性回归法等找到倾斜角度,然后再利用仿射变换进行旋转。

对于Z轴倾斜,先利用边缘(轮廓)检测算法找到图像的边界,然后利用透视变换把视平面上的点投影到现实平面,然后再利用仿射变换进行旋转。

实践

边缘检测

1
2
3
4
5
6
7
8
9
10
11
12
13
# -*- coding: utf-8 -*-

import numpy as np
import cv2

img = cv2.imread('../image/tilt.jpg')
GrayImage = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

CannyImage = cv2.Canny(GrayImage,50, 150, apertureSize=3)

cv2.imshow('gray',GrayImage)
cv2.imshow('canny',CannyImage)
cv2.waitKey(0)


edges=cv.Canny(image, threshold1, threshold2[, edges[, apertureSize[, L2gradient]]])参数说明:

  • image:输入图像。
  • threshold1:最小阈值。
  • threshold2:最大阈值。
  • apertureSize:Sobel算子的孔径大小。

更多内容参考:

以上代码,调用了OpenCV中的canny方法找到了边缘,但是,文字的边缘也被显示出来了。那么,怎么去掉文字边缘,只要长方形框的边缘呢?OpenCV也提供了方法,使用findContours来查找轮廓,使用drawContours来绘制轮廓。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# -*- coding: utf-8 -*-

import numpy as np
import cv2

img = cv2.imread('../image/tilt.jpg')
GrayImage = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
CannyImage = cv2.Canny(GrayImage,50, 150, apertureSize=3)
ret,BinImage=cv2.threshold(CannyImage,127,255,cv2.THRESH_BINARY)

_, contours, _= cv2.findContours(BinImage, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

cv2.imshow('bin',BinImage)
cv2.drawContours(img, contours, -1, (0,255, 0), 1)
cv2.imshow('edge',img)
cv2.waitKey(0)

contours, hierarchy = cv.findContours(image, mode, method[, contours[, hierarchy[, offset]]])参数说明:

如果按照官方文档调用,会报错,参考ValueError: too many values to unpack解决。实际上,这是因为opencv3之后该函数的返回值有三个,而官方文档有多个版本,比如这一版中就说明了有三个参数。

计算倾斜角

以上,已经找到了图片的边缘,接下来计算倾斜角度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# -*- coding: utf-8 -*-

import numpy as np
import cv2

img = cv2.imread('../image/tilt.jpg')
GrayImage = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
CannyImage = cv2.Canny(GrayImage,50, 150, apertureSize=3)
ret,BinImage=cv2.threshold(CannyImage,127,255,cv2.THRESH_BINARY)

lines = cv2.HoughLinesP(BinImage, 1, np.pi / 180, 160, minLineLength=200, maxLineGap=180)

# 寻找长度最长的线
distance = []
for line in lines:
x1, y1, x2, y2 = line[0]
dis = np.sqrt(pow((x2 - x1), 2) + pow((y2 - y1), 2))
distance.append(dis)
max_dis_index = distance.index(max(distance))
max_line = lines[max_dis_index]
x1, y1, x2, y2 = max_line[0]

# 获取旋转角度
angle = cv2.fastAtan2((y2 - y1), (x2 - x1))
print(angle)

以上代码,很尴尬,并不是利用findContours的结果进行计算的,而是一个新的思路。为什么没有使用contours?因为不会写代码。。。如果要继续findContours思路,那么可以参考图像矫正技术深入探讨进行改写。

仿射变换

求出倾斜角度之后,利用仿射变换进行旋转。

1
2
3
4
5
6
7
8
# 计算图片中心
centerpoint = (img.shape[1]/2,img.shape[0]/2)
# 获取旋转矩阵
rotate_mat = cv2.getRotationMatrix2D(centerpoint,angle,1.0)
correct_image = cv2.warpAffine(img,rotate_mat,(img.shape[1],img.shape[0]),borderValue =(255,255,255) )

cv2.imshow('right',correct_image)
cv2.waitKey(0)

Z轴倾斜

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
import cv2
import numpy as np

def gray_and_bin(init_img):
gray_img = cv2.cvtColor(init_img, cv2.COLOR_BGR2GRAY)
blur_img = cv2.GaussianBlur(gray_img, (3, 3), 0) # 高斯模糊去噪(设定卷积核大小影响效果)
canny_img = cv2.Canny(blur_img, 35, 189, apertureSize=3)
_, bin_img = cv2.threshold(canny_img, 127, 255, cv2.THRESH_BINARY) # 设定阈值165(阈值影响开闭运算效果)
return gray_img, bin_img

def points_and_box(init_img, bin_img):
image, contours, hierarchy = cv2.findContours(bin_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
c = sorted(contours, key=cv2.contourArea, reverse=True)[0] # 计算最大轮廓的旋转包围盒
rect = cv2.minAreaRect(c) # 获取包围盒(中心点,宽高,旋转角度)
box = np.int0(cv2.boxPoints(rect)) # box
# box_img = cv2.drawContours(init_img.copy(), [box], -1, (0, 0, 255), 2)
# cv2.imshow('box',box_img)

empty_img = np.zeros(init_img.shape, np.uint8) # 创建空白图像
empty_img[...] = 255 # 设置白底
# cv2.imshow('test',empty_img)
cv2.drawContours(empty_img, contours, -1, (0, 0, 0), 1) # 在空白图像上绘制试卷轮廓
# cv2.imshow('edge', empty_img)
_, bin_img2 = gray_and_bin(empty_img)
# cv2.imshow('bin',bin_img2)
lines = cv2.HoughLinesP(bin_img2, 1, np.pi / 180, 100, minLineLength=200, maxLineGap=10)

for i in range(int(np.size(lines) / 4)):
for x1, y1, x2, y2 in lines[i]:
cv2.line(empty_img, (x1, y1), (x2, y2), (255, 255, 0), 1)
# cv2.imshow('line',empty_img)

points = None
if len(contours) > 0:
contours = sorted(contours, key=cv2.contourArea, reverse=True)
for c in contours:
peri = cv2.arcLength(c, True) # 轮廓按大小降序排序
approx = cv2.approxPolyDP(c, 0.02 * peri, True) # 获取近似的轮廓
if len(approx) == 4: # 近似轮廓有四个顶点
points = approx
break

print('piont[0]', points[0]) # 左下
print('piont[1]', points[1]) # 左上
print('piont[2]', points[2]) # 右上
print('piont[3]', points[3]) # 右下

print('box[0]:', box[0]) # 右下
print('box[1]:', box[1]) # 右上
print('box[2]:', box[2]) # 左上
print('box[3]:', box[3]) # 左下

return points,box

def perspective_transform(box,points,init_img):

# 原图中试卷的四个顶点
pts1 = np.float32([points[0], points[1], points[2], points[3]])
# box中的四个顶点
pts2 = np.float32([box[3], box[2], box[1], box[0]])

# 生成透视变换矩阵;进行透视变换
M = cv2.getPerspectiveTransform(pts1,pts2)
result_img = cv2.warpPerspective(init_img, M, (1200, 1300))

return result_img

if __name__=='__main__':
init_img = cv2.imread('../image/init.jpg')
gray_img, bin_img = gray_and_bin(init_img)
points, box = points_and_box(init_img,bin_img)
result_img = perspective_transform(box,points,init_img)
# cv2.imshow('init',init_img)
cv2.namedWindow('result', 0)
cv2.resizeWindow('result', 640, 1200)
cv2.imshow('result', result_img)
# cv2.imwrite('result.jpg',result_img)
cv2.waitKey(0)


以上结果,并不是很理想。透视矫正完成了,但是存在黑边白边,试卷长宽比例也有问题,也没有进行平面倾斜矫正。就先这样吧,作为一个小白,做成这样已经很不容易了。

更多内容参考利用opencv库,实现校正图片中的A4纸用numpy+OpenCV快速实现矫正图像的功能OpenCV—python图像矫正对倾斜的图像进行修正——基于opencv透视变换

后记

图像倾斜矫正这部分,是图像预处理中的一个难点。以上,虽然实现了基本的图像倾斜矫正算法,但是算法原理还需要进一步学习。而且,上面的代码中使用的图片都是理想的,干扰很小,而实际拍摄的图片,除了目标物体(试卷),还会出现其他物体,比如签字笔文具盒等等。所以,后续还需要考虑其他物体的干扰。

另外,发现OpenCV成为了开发过程中的瓶颈,想要实现一些功能,每次都要求助于百度谷歌。因此,必须找时间系统学习一下OpenCV。

书签

图像矫正原理说明

图像矫正-基于opencv实现

利用包围轮廓和仿射变换进行图像倾斜校正

对Z轴倾斜的图像进行校正–基于OpenCV透视变换

OpenCV实现基于傅里叶变换的旋转文本校正

Python+OpenCV实现旋转文本校正