我从Ann Zen 扩展了excellent answer。
您必须调整两个变量:
-
threshold: 面积低于此阈值的轮廓将被丢弃
-
row_amt: 图片中的行数
Ann Zen 使用的概念是将图像沿 y 轴分成n 段,形成n 行。对于图像的每个片段,找到每个以该片段为中心的形状。最后,按 x 坐标对每个段中的形状进行排序。
- 导入必要的库。我有一个
DEBUG 标志,它将显示一些有助于调试的额外功能。
import cv2
import numpy as np
from collections import OrderedDict
DEBUG = False
- 定义一个函数,该函数将接受图像输入并将处理后的图像返回到允许 python 稍后检索其轮廓的东西:
def process_img(image):
# grayscale
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
se = cv2.getStructuringElement(cv2.MORPH_RECT, (8, 8))
bg = cv2.morphologyEx(gray, cv2.MORPH_DILATE, se)
out_gray = cv2.divide(gray, bg, scale=255)
out_binary = cv2.threshold(out_gray, 0, 255, cv2.THRESH_OTSU)[1]
# binary
(ret, thresh) = cv2.threshold(out_binary, 127, 255,
cv2.THRESH_BINARY_INV)
# opening
kernel = np.ones((3, 3), np.uint8)
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)
# dilation 40 for segmenting words 15 for letters
kernel = np.ones((5, 40), np.uint8)
img_dilation = cv2.dilate(opening, kernel, iterations=1)
return img_dilation
- 定义一个返回轮廓中心的函数:
def get_centeroid(cnt):
length = len(cnt)
sum_x = np.sum(cnt[..., 0])
sum_y = np.sum(cnt[..., 1])
return int(sum_x / length), int(sum_y / length)
- 定义一个函数,返回面积高于阈值的所有轮廓:
def get_contours(processed_img, threshold):
(contours, hierarchies) = cv2.findContours(processed_img,
cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
return [cnt for cnt in contours if cv2.contourArea(cnt) > threshold]
- 定义一个函数,该函数将接受轮廓列表并返回图像中找到的形状的中心点:
def get_centers(contours):
return [get_centeroid(cnt) for cnt in contours]
- 定义一个函数,该函数将沿轮廓的 y 轴查找上限和下限。根据您提供的行数,它会在
amt_row 高度为row_h 的行中分割这些边界之间的区域。
def get_bounds(contours, img, row_amt):
min_y = img.shape[0]
max_y = 0
for ctr in contours:
(x, y, w, h) = cv2.boundingRect(ctr)
if y < min_y:
min_y = y
if y + h > max_y:
max_y = y + h
row_h = (max_y - min_y) / row_amt
if DEBUG:
line_thickness = 2
x1 = 0
x2 = img.shape[1]
for i in range(row_amt + 1):
y1 = y2 = int(min_y + row_h * i)
cv2.line(img, (x1, y1), (x2, y2), (0, 255, 0),
thickness=line_thickness)
return (min_y, max_y, row_h)
- 定义一个函数,该函数将接收图像数组
img、图像的段数row_amt 和threshold。面积低于此阈值的轮廓将被丢弃。它将返回row_amt OrderedDicts。每个 OrderedDict 包含其对应行的中心作为键,按它们的 x 坐标排序,每个键的值是其对应的轮廓。
def get_rows(img, row_amt, threshold):
processed_img = process_img(img)
contours = get_contours(processed_img, threshold)
centers = get_centers(contours)
centers_to_contours = dict(zip(centers, contours))
centers = np.array(centers)
min_y, max_y, row_h = get_bounds(contours, img, row_amt)
for i in range(row_amt):
f = centers[:, 1] - min_y - row_h * i
a = centers[(f < row_h) & (f > 0)]
c = a[a.argsort(0)[:, 0]]
od = OrderedDict()
for center in map(tuple, c):
od[center] = centers_to_contours[center]
yield od
- 读入图像,遍历行,在每一行中遍历中心/轮廓,然后绘制矩形和数字。
img = cv2.imread('RrU0o.jpg')
count = 0
for row in get_rows(img, row_amt=7, threshold=1330):
if DEBUG:
centerpoints = np.array(list(row.keys()))
cv2.polylines(img, [centerpoints], False, (255, 0, 255), 2)
for ((x, y), ctr) in row.items():
count += 1
if DEBUG:
cv2.circle(img, (x, y), 10, (0, 0, 255), -1)
cv2.putText(img, f'#{count}', (x - 10, y + 5), cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 0, 0), 2)
(x, y, w, h) = cv2.boundingRect(ctr)
cv2.rectangle(img, (x, y), (x + w, y + h), (90, 0, 255), 2)
- 最后,显示图片:
cv2.imshow("Final", img)
cv2.waitKey(0)
结果:
DEBUG = True 的结果。
- 绿色水平线表示分段
- 红点表示轮廓的中心
- 粉色线按顺序连接线段中的中心
总共:
#!/usr/bin/python
import cv2
import numpy as np
from google.colab.patches import cv2_imshow
from collections import OrderedDict
DEBUG = False
def process_img(image):
# grayscale
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
se = cv2.getStructuringElement(cv2.MORPH_RECT, (8, 8))
bg = cv2.morphologyEx(gray, cv2.MORPH_DILATE, se)
out_gray = cv2.divide(gray, bg, scale=255)
out_binary = cv2.threshold(out_gray, 0, 255, cv2.THRESH_OTSU)[1]
# binary
(ret, thresh) = cv2.threshold(out_binary, 127, 255,
cv2.THRESH_BINARY_INV)
# opening
kernel = np.ones((3, 3), np.uint8)
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)
# dilation 40 for segmenting words 15 for letters
kernel = np.ones((5, 40), np.uint8)
img_dilation = cv2.dilate(opening, kernel, iterations=1)
return img_dilation
def get_centeroid(cnt):
length = len(cnt)
sum_x = np.sum(cnt[..., 0])
sum_y = np.sum(cnt[..., 1])
return (int(sum_x / length), int(sum_y / length))
def get_contours(processed_img, threshold):
(contours, hierarchies) = cv2.findContours(processed_img,
cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
return [cnt for cnt in contours if cv2.contourArea(cnt) > threshold]
def get_centers(contours):
return [get_centeroid(cnt) for cnt in contours]
def get_bounds(contours, img, row_amt):
min_y = img.shape[0]
max_y = 0
for ctr in contours:
(x, y, w, h) = cv2.boundingRect(ctr)
if y < min_y:
min_y = y
if y + h > max_y:
max_y = y + h
row_h = (max_y - min_y) / row_amt
if DEBUG:
line_thickness = 2
x1 = 0
x2 = img.shape[1]
for i in range(row_amt + 1):
y1 = y2 = int(min_y + row_h * i)
cv2.line(img, (x1, y1), (x2, y2), (0, 255, 0),
thickness=line_thickness)
return (min_y, max_y, row_h)
def get_rows(img, row_amt, threshold):
processed_img = process_img(img)
contours = get_contours(processed_img, threshold)
centers = get_centers(contours)
centers_to_contours = dict(zip(centers, contours))
centers = np.array(centers)
(min_y, max_y, row_h) = get_bounds(contours, img, row_amt)
for i in range(row_amt):
f = centers[:, 1] - min_y - row_h * i
a = centers[(f < row_h) & (f > 0)]
c = a[a.argsort(0)[:, 0]]
od = OrderedDict()
for center in map(tuple, c):
od[center] = centers_to_contours[center]
yield od
img = cv2.imread('RrU0o.jpg')
count = 0
for row in get_rows(img, row_amt=7, threshold=1330):
if DEBUG:
centerpoints = np.array(list(row.keys()))
cv2.polylines(img, [centerpoints], False, (255, 0, 255), 2)
for ((x, y), ctr) in row.items():
count += 1
if DEBUG:
cv2.circle(img, (x, y), 10, (0, 0, 255), -1)
cv2.putText(img, f'#{count}', (x - 10, y + 5), cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 0, 0), 2)
(x, y, w, h) = cv2.boundingRect(ctr)
cv2.rectangle(img, (x, y), (x + w, y + h), (90, 0, 255), 2)
cv2.imshow("Final", img)
cv2.waitKey(0)