Python & OpenCV:如何裁剪半成型的边界框
Python & OpenCV: How to crop half-formed bounding boxes
我有一个为无网格创建网格线的脚本table:
脚本之前:
脚本之后:
是否有使用 OpenCV 裁剪“脚本后”图像使其仅包含四边边界框的简单方法?示例输出:
编辑:
我目前正在研究一种解决方案,可以找到 first/last 全黑像素线走向 vertically/horizontally。它会工作,但想知道是否有更优雅的东西。
这是 Python/OpenCV 中的一种方法,即从除最大轮廓之外的所有轮廓中获取最小和最大 x 和 y。
输入:
import cv2
import numpy as np
# read image
img = cv2.imread('test_table.png')
hh, ww = img.shape[:2]
# convert to gray
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# threshold
thresh = cv2.threshold(gray, 128, 255, cv2.THRESH_BINARY)[1]
# crop 1 pixel and add 1 pixel white border to ensure outer white regions not considered small contours
thresh = thresh[1:hh-1, 1:ww-1]
thresh = cv2.copyMakeBorder(thresh, 1,1,1,1, borderType=cv2.BORDER_CONSTANT, value=(255,255,255))
# get contours
contours = cv2.findContours(thresh, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
big_contour = max(contours, key=cv2.contourArea)
# get min and max x and y from all bounding boxes larger than half the image size
area_thresh = hh * ww / 2
xmin = ww
ymin = hh
xmax = 0
ymax = 0
for cntr in contours:
area = cv2.contourArea(cntr)
if area < area_thresh:
x,y,w,h = cv2.boundingRect(cntr)
xmin = x if (x < xmin) else xmin
ymin = y if (y < ymin) else ymin
xmax = x+w-1 if (x+w-1 > xmax ) else xmax
ymax = y+h-1 if (y+h-1 > ymax) else ymax
# draw bounding box
bbox = img.copy()
cv2.rectangle(bbox, (xmin, ymin), (xmax, ymax), (0, 0, 255), 2)
# crop img at bounding box, but add 2 all around to keep the black lines
result = img[ymin-3:ymax+3, xmin-3:xmax+3]
# save results
cv2.imwrite('test_table_bbox.png',bbox)
cv2.imwrite('test_table_trimmed.png',result)
# show results
cv2.imshow("thresh", thresh)
cv2.imshow("bbox", bbox)
cv2.imshow("result", result)
cv2.waitKey(0)
cv2.destroyAllWindows()
输入的所有边界框的边界框:
修剪过的图片:
注意:我知道已经有一个可接受的答案,但我想提供一个更简单的版本。
基本上,首先找到图像中每个形状的轮廓(每个单元格),其面积大于选定的数字,将滤除任何噪音。
遍历轮廓,找到最小和最大的 x 和 y 坐标。有了这4个点,我们就可以把图片中4个坐标内的像素保存到一个单独的数组中,用白色填充原图,然后把table画回图片上。
代码:
import cv2
img = cv2.imread("table.png")
h, w, _ = img.shape
x1, y1 = w, h
x2, y2 = 0, 0
contours, _ = cv2.findContours(cv2.Canny(img, 0, 0), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
for cnt in contours:
if cv2.contourArea(cnt) > 1000:
x1 = min(cnt[..., 0].min(), x1)
y1 = min(cnt[..., 1].min(), y1)
x2 = max(cnt[..., 0].max(), x2)
y2 = max(cnt[..., 1].max(), y2)
pad = 2
x1 -= pad
y1 -= pad
x2 += pad * 2
y2 += pad * 2
table = img[y1:y2, x1:x2].copy()
img.fill(255)
img[y1:y2, x1:x2] = table
cv2.imshow("lined_table.png", img)
cv2.waitKey(0)
输出:
解释:
- 导入opencv模块,读入图像。获取图像的尺寸并为 table 的第一个角和 table 的最后一个角定义临时坐标:
import cv2
img = cv2.imread("table.png")
h, w, _ = img.shape
x1, y1 = w, h
x2, y2 = 0, 0
- 获取图像的轮廓,并循环遍历每个轮廓,过滤掉面积小于1000的轮廓:
contours, _ = cv2.findContours(cv2.Canny(img, 0, 0), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
for cnt in contours:
if cv2.contourArea(cnt) > 999:
- 更新第一个角点和最后一个角点的坐标值 table:
x1 = min(cnt[..., 0].min(), x1)
y1 = min(cnt[..., 1].min(), y1)
x2 = max(cnt[..., 0].max(), x2)
y2 = max(cnt[..., 1].max(), y2)
- 根据线条的宽度在每个坐标周围应用填充:
pad = 2
x1 -= pad
y1 -= pad
x2 += pad * 2
y2 += pad * 2
- 根据找到的x和y坐标将部分图片复制到一个变量中,清空图片,重绘table到图片上。最后,显示图像:
table = img[y_1:y_2, x_1:x_2].copy()
img.fill(255)
img[y_1:y_2, x_1:x_2] = table
cv2.imwrite("lined_table.png", img)
cv2.waitKey(0)
我有一个为无网格创建网格线的脚本table:
脚本之前:
脚本之后:
是否有使用 OpenCV 裁剪“脚本后”图像使其仅包含四边边界框的简单方法?示例输出:
编辑:
我目前正在研究一种解决方案,可以找到 first/last 全黑像素线走向 vertically/horizontally。它会工作,但想知道是否有更优雅的东西。
这是 Python/OpenCV 中的一种方法,即从除最大轮廓之外的所有轮廓中获取最小和最大 x 和 y。
输入:
import cv2
import numpy as np
# read image
img = cv2.imread('test_table.png')
hh, ww = img.shape[:2]
# convert to gray
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# threshold
thresh = cv2.threshold(gray, 128, 255, cv2.THRESH_BINARY)[1]
# crop 1 pixel and add 1 pixel white border to ensure outer white regions not considered small contours
thresh = thresh[1:hh-1, 1:ww-1]
thresh = cv2.copyMakeBorder(thresh, 1,1,1,1, borderType=cv2.BORDER_CONSTANT, value=(255,255,255))
# get contours
contours = cv2.findContours(thresh, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
big_contour = max(contours, key=cv2.contourArea)
# get min and max x and y from all bounding boxes larger than half the image size
area_thresh = hh * ww / 2
xmin = ww
ymin = hh
xmax = 0
ymax = 0
for cntr in contours:
area = cv2.contourArea(cntr)
if area < area_thresh:
x,y,w,h = cv2.boundingRect(cntr)
xmin = x if (x < xmin) else xmin
ymin = y if (y < ymin) else ymin
xmax = x+w-1 if (x+w-1 > xmax ) else xmax
ymax = y+h-1 if (y+h-1 > ymax) else ymax
# draw bounding box
bbox = img.copy()
cv2.rectangle(bbox, (xmin, ymin), (xmax, ymax), (0, 0, 255), 2)
# crop img at bounding box, but add 2 all around to keep the black lines
result = img[ymin-3:ymax+3, xmin-3:xmax+3]
# save results
cv2.imwrite('test_table_bbox.png',bbox)
cv2.imwrite('test_table_trimmed.png',result)
# show results
cv2.imshow("thresh", thresh)
cv2.imshow("bbox", bbox)
cv2.imshow("result", result)
cv2.waitKey(0)
cv2.destroyAllWindows()
输入的所有边界框的边界框:
修剪过的图片:
注意:我知道已经有一个可接受的答案,但我想提供一个更简单的版本。
基本上,首先找到图像中每个形状的轮廓(每个单元格),其面积大于选定的数字,将滤除任何噪音。
遍历轮廓,找到最小和最大的 x 和 y 坐标。有了这4个点,我们就可以把图片中4个坐标内的像素保存到一个单独的数组中,用白色填充原图,然后把table画回图片上。
代码:
import cv2
img = cv2.imread("table.png")
h, w, _ = img.shape
x1, y1 = w, h
x2, y2 = 0, 0
contours, _ = cv2.findContours(cv2.Canny(img, 0, 0), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
for cnt in contours:
if cv2.contourArea(cnt) > 1000:
x1 = min(cnt[..., 0].min(), x1)
y1 = min(cnt[..., 1].min(), y1)
x2 = max(cnt[..., 0].max(), x2)
y2 = max(cnt[..., 1].max(), y2)
pad = 2
x1 -= pad
y1 -= pad
x2 += pad * 2
y2 += pad * 2
table = img[y1:y2, x1:x2].copy()
img.fill(255)
img[y1:y2, x1:x2] = table
cv2.imshow("lined_table.png", img)
cv2.waitKey(0)
输出:
解释:
- 导入opencv模块,读入图像。获取图像的尺寸并为 table 的第一个角和 table 的最后一个角定义临时坐标:
import cv2
img = cv2.imread("table.png")
h, w, _ = img.shape
x1, y1 = w, h
x2, y2 = 0, 0
- 获取图像的轮廓,并循环遍历每个轮廓,过滤掉面积小于1000的轮廓:
contours, _ = cv2.findContours(cv2.Canny(img, 0, 0), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
for cnt in contours:
if cv2.contourArea(cnt) > 999:
- 更新第一个角点和最后一个角点的坐标值 table:
x1 = min(cnt[..., 0].min(), x1)
y1 = min(cnt[..., 1].min(), y1)
x2 = max(cnt[..., 0].max(), x2)
y2 = max(cnt[..., 1].max(), y2)
- 根据线条的宽度在每个坐标周围应用填充:
pad = 2
x1 -= pad
y1 -= pad
x2 += pad * 2
y2 += pad * 2
- 根据找到的x和y坐标将部分图片复制到一个变量中,清空图片,重绘table到图片上。最后,显示图像:
table = img[y_1:y_2, x_1:x_2].copy()
img.fill(255)
img[y_1:y_2, x_1:x_2] = table
cv2.imwrite("lined_table.png", img)
cv2.waitKey(0)