目录
在识别某些验证码(如点选验证码)的时候,我们会看到验证码图片上要求点选的内容是东倒西歪的,如下图所示。
为了更好的识别点选内容,我们最好先将该内容回正,也就是要调整图片的角度。现在我们一起来看下有什么好的方案。
方案一:通过最小外接矩形解决
我们可以使用opencv-python的minAreaRect()方法找到点选内容的最小外接矩形,关键是该方法会返回矩形当前的旋转角度。我们现在试着将下面的文字内容回正。
编写代码如下:
import cv2 as cv
import numpy as np
def get_angle():
img = cv.imread("pic.jpg")
# 灰度化
img_gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
# 高斯模糊
img_gaussian = cv.GaussianBlur(img_gray, (9, 9), 0)
# 二值化
ret, img_thresh = cv.threshold(img_gaussian, 0, 255, cv.THRESH_BINARY_INV + cv.THRESH_OTSU)
# 腐蚀处理
kernel = np.ones((3, 3), np.float32)
img_erode = cv.erode(img_thresh, kernel)
# 通过计算图像非零值找到文本轮廓
# 文本所在轮廓非零值应该是最大的
def get_non_zero(_cnt, _img):
_x, _y, _w, _h = cv.boundingRect(_cnt)
return cv.countNonZero(_img[_y:_y + _h, _x:_x + _w])
cnts, hiers = cv.findContours(img_erode, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
max_cnt = max(cnts, key=lambda cnt: get_non_zero(cnt, img_erode))
# 获取文本轮廓的最小外接矩形,从而得到角度
(x, y), (w, h), angle = cv.minAreaRect(max_cnt)
# # 画出最小外接矩形
# box = np.int0(cv.boxPoints(cv.minAreaRect(max_cnt)))
# cv.drawContours(img, [box], 0, (0, 255, 0), 2)
# cv.imshow("img", img)
# cv.waitKey(0)
# 如果角度为90,则不需要旋转
if angle == 90:
cv.imwrite("result.jpg", img)
return
# 两种方向回正
angleToRotate1 = -90 + angle
angleToRotate2 = angle
# 顺时针回正
rows, cols = img.shape[:2]
M1 = cv.getRotationMatrix2D((cols / 2, rows / 2), angleToRotate1, 1)
result_img1 = cv.warpAffine(img, M1, (cols, rows))
cv.imwrite("result1.jpg", result_img1)
# 逆时针回正
M2 = cv.getRotationMatrix2D((cols / 2, rows / 2), angleToRotate2, 1)
result_img2 = cv.warpAffine(img, M2, (cols, rows))
cv.imwrite("result2.jpg", result_img2)
# 显示
cv.imshow("result1", result_img1)
cv.imshow("result2", result_img2)
cv.waitKey(0)
if __name__ == "__main__":
get_angle()
我们首先对图像进行处理并获得一个二值化图像,然后用findContours()方法找到图像中的各个轮廓。轮廓中拥有最大非零值的就断定为文本轮廓,然后我们调用minAreaRect()方法获得该轮廓的最小外接矩形。
minAreaRect()方法会最小外接矩形的中心点坐标、高宽以及矩形当前旋转的角度值,该角度是x轴顺时针旋转时与碰到的矩形第一条边的夹角,范围在(0, 90]。请见下方示意图。
注:minAreaRect()方法返回的角度是x轴顺时针旋转时与碰到的矩形第一条边的夹角,而碰到的第一条边也会被当做矩形的宽。
如果角度为90°,则说明最小外接矩形是正的,不需要旋转。如果不是90°,则通过判断最小外接矩形的宽和高来决定回正时要旋转的角度。如果宽大于高,则顺时针旋转-90+angle角度;如果宽小于等于高,则逆时针旋转angle角度。
注:角度值为正时,getRotationMatrix2D()方法会逆时针旋转图片。
但是由于文字种类很多,而且文字还被旋转,所以我们不能确定被点选文字在最小外接矩形中所呈现的宽高大小、笔者这里就回正了两次,得到两张图片。运行结果如下:
方案二:通过霍夫变换解决
霍夫变换是一种特征提取方法,我们可以通过该方法获取到图像中的各个直线,然后通过直线的倾斜角度来调整图像。具体步骤如下:
1. 用Canny()方法检测出图像中的边缘轮廓线。
2. 用HoughLines()方法检测出图像中的所有直线。
3. 求出多条特定倾斜范围内直线的平均倾斜角度,然后进行回正。
编写代码如下:
import math
import cv2 as cv
import numpy as np
def rotate():
img = cv.imread("test1.jpg")
# 灰度化
img_gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
# 高斯模糊
img_gaussian = cv.GaussianBlur(img_gray, (9, 9), 0)
# 二值化
ret, img_thresh = cv.threshold(img_gaussian, 0, 255, cv.THRESH_BINARY_INV + cv.THRESH_OTSU)
# 腐蚀处理
kernel = np.ones((3, 3), np.float32)
img_erode = cv.erode(img_thresh, kernel)
# 边缘检测
img_edge = cv.Canny(img_erode, 350, 400, apertureSize=3)
# 提取直线
# 如果lines为None,则需要调整该函数的最后一个值
lines = cv.HoughLines(img_edge, 1, np.pi / 180, 25)
# 循环直线获取角度
line_angle_list = []
for line in lines:
for rho, theta in line:
a = np.cos(theta)
b = np.sin(theta)
x0 = a * rho
y0 = b * rho
x1 = int(x0 + 1000 * (-b))
y1 = int(y0 + 1000 * (a))
x2 = int(x0 - 1000 * (-b))
y2 = int(y0 - 1000 * (a))
# 如果是完全水平或者垂直的线就直接跳过
if x1 == x2 or y1 == y2:
cv.imwrite("result.jpg", img)
return
# 寻找特定范围的倾斜直线,跳过倾斜角度过大的
t = float(y2 - y1) / (x2 - x1)
if t < - math.pi / 3 or t > math.pi / 3:
continue
# 将符合条件的线条的倾斜角度保存到列表中
angle = abs(math.degrees(math.atan(t)))
line_angle_list.append(angle)
# 计算平均角度
average_angle = sum(line_angle_list) / len(line_angle_list)
# 两种回正方案
angleToRotate1 = -90 + average_angle
angleToRotate2 = average_angle
# 顺时针回正
rows, cols = img.shape[:2]
M1 = cv.getRotationMatrix2D((cols / 2, rows / 2), angleToRotate1, 1)
result_img1 = cv.warpAffine(img, M1, (cols, rows))
cv.imwrite("result1.jpg", result_img1)
# 逆时针回正
M2 = cv.getRotationMatrix2D((cols / 2, rows / 2), angleToRotate2, 1)
result_img2 = cv.warpAffine(img, M2, (cols, rows))
cv.imwrite("result2.jpg", result_img2)
# 显示
cv.imshow("result1", result_img1)
cv.imshow("result2", result_img2)
cv.waitKey(0)
if __name__ == "__main__":
rotate()
我们首先对图像进行处理并获得一个二值化图像,然后用Canny()方法检测图像中的边缘线条,接着再通过HoughLines()方法检测出图像中的所有直线。通过t <= - math.pi / 3 or t >= math.pi / 3条件判断可以得到在特定倾斜范围内的一些直线(倾斜弧度小于math.pi/3的直线),我们将这些直线的倾斜角度的绝对值保存到列表中。
注:之所以要保存角度的绝对值,是因为直线可能朝着不同的方向,而得出的角度就会有正有负,如下图所示。当然我们也可以选择只保存角度为正的或者角度为负的值。
由于文字种类很多,而且文字还被旋转,所以我们不能确定文字要朝着哪一个方向旋转。笔者这里就回正了两次,得到两张图片。运行结果如下:
源码下载:
笔者还放入了其他两张测试图片。
链接:https://pan.baidu.com/s/1eWMw4s6RsmYsuIrZnOLwCA
密码:fkdd