esXGray开发笔记:基于直线检测的文本倾斜自动校正算法实现(python+opencv)

发布时间 2023-08-15 10:07:41作者: Moneky

昨日采用最小面积矩形的方式实现文本倾斜自动校正,但后面的角度有点麻烦,于是改用基本直线检测的算法。

算法简介:

  1. 检测直线,自动调节参数,至少获取11条直线(直线条数调节)
  2. 计算每条直线与x轴夹角
  3. 从返回的角度中找到出现次数较多的直线角度平均值并返回作为图片倾斜角度
  4. 检测到角度后,就可以将图片进行校正了

结果:

 在esXGray中的应用演示:

 

主要代码:

  1 import cv2
  2 import numpy as np
  3 import math
  4 
  5 
  6 def group_data(data, num_groups):
  7     """返回一组数据中数据集中的范围上下限(无视少数派数据)"""
  8     # 将列表转换为浮点数数组
  9     data = np.array(data).astype(float)
 10     # 找到列表中的最小值和最大值
 11     min_val = np.min(data)
 12     max_val = np.max(data)
 13     # 计算每个组的范围
 14     if max_val == min_val:
 15         return min_val, max_val  # 如果最小值与最大值相等则直接返回
 16     else:
 17         group_range = (max_val - min_val) / num_groups
 18         # 创建一个数组来存储每个元素所属的组的索引
 19         group_indices = np.floor((data - min_val) / group_range).astype(int)
 20         # 统计每个组中的元素个数
 21         group_counts = np.bincount(group_indices)
 22         # 找到元素个数最多的组的索引
 23         most_common_group_index = np.argmax(group_counts)
 24         # 计算该组的最小值和最大值
 25         most_common_group_min = min_val + most_common_group_index * group_range
 26         most_common_group_max = min_val + (most_common_group_index + 1) * group_range
 27         return most_common_group_min, most_common_group_max
 28 
 29 
 30 def get_angle_auto(src):
 31     """自动获取图片文本行倾斜角度(与x轴夹角)"""
 32     def calc_angle(x1, y1, x2, y2):
 33         # 计算直线的斜率
 34         if x1 == x2:
 35             return 90
 36         slope = (y2 - y1) / (x2 - x1)
 37         # 计算与x轴的夹角(以弧度为单位)
 38         angle_rad = math.atan(slope)
 39         # 将弧度转换为角度(如果需要)
 40         angle_deg = math.degrees(angle_rad)
 41         return angle_deg
 42     # 将图像转换为灰度图像
 43     gray = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY)
 44     # 使用高斯模糊来减少图像中的噪声
 45     blur = cv2.GaussianBlur(gray, (5, 5), 0)
 46     # 使用自适应阈值化将图像转换为二值图像
 47     thresh = cv2.adaptiveThreshold(blur, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 15, 30)
 48     # 定义一个结构元素,用于图像的膨胀操作
 49     kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
 50     # 使用膨胀操作来连接断开的边缘
 51     dilate = cv2.dilate(thresh, kernel, iterations=6)
 52     # 使用Canny边缘检测
 53     edges = cv2.Canny(dilate, 200, 250, apertureSize=3)
 54     # 使用Hough变换检测直线
 55     lines: list = []  # 检测结果list
 56     length = 200  # 直线点数下限,考虑到存在文本行的图片尺寸不会太小,因此设为200
 57     # 下面逐步调节参数,需要至少检测到11条,根据不同图形参数需要调节一下
 58     # 条数过少,可能检测到的是非文本行,而是边界的直线;
 59     # 条数过多,可能出现一些无意义的错误直线
 60     while len(lines) < 11:
 61         lines = cv2.HoughLines(edges, 1, np.pi / 180, length)
 62         if lines is None:
 63             lines = []
 64         length -= 5  # 如果没有检测到直线,自动调节参数
 65         if length <= 5:
 66             break
 67 
 68     print('检测到直线条数:', len(lines))
 69     # 在图像上绘制检测到的直线
 70     angles = []
 71     if len(lines) > 0:
 72         for line in lines:
 73             rho, theta = line[0]
 74             a = np.cos(theta)
 75             b = np.sin(theta)
 76             x0 = a * rho
 77             y0 = b * rho
 78             x1 = int(x0 + 1000 * (-b))
 79             y1 = int(y0 + 1000 * a)
 80             x2 = int(x0 - 1000 * (-b))
 81             y2 = int(y0 - 1000 * a)
 82             # 此处可以画出图像查看结果
 83             cv2.line(image, (x1, y1), (x2, y2), (0, 0, 255), 2)
 84             angle = calc_angle(x1, y1, x2, y2)
 85             print(angle)
 86             angles.append(angle)
 87 
 88         # 下面从得到的角度中迭代获取角度集中区间
 89         dmin = 0
 90         dmax = 100
 91         while dmax - dmin > 1 and len(angles) >= 3:  # 如果数据区间>1且数据个数>=3则继续
 92             dmin, dmax = group_data(angles, 3)
 93             if dmin:
 94                 angles = [i for i in angles if dmin <= i <= dmax]  # 剔除少数派数据
 95             else:
 96                 break
 97         if len(angles) > 0:
 98             return -sum(angles) / len(angles)  # 返回剩余数据平均值
 99         else:
100             return None
101     else:
102         return None
103 
104 
105 img_src = r"c:/users/eerso/desktop/002.png"
106 # 读取图像
107 image = cv2.imread(img_src)
108 angle = get_angle_auto(image)
109 print(f'angle={angle}')
110 # 显示结果图像
111 cv2.imshow('image', image)
112 cv2.waitKey(0)
113 cv2.destroyAllWindows()
View Code