基于大空白区域对扫描图像进行切片
Slicing of a scanned image based on large white spaces
我打算拆分 this PDF 文档中的问题。挑战在于问题没有按顺序排列。比如第一个问题占了一整页,第二个也一样,第三个和第四个一起占一页。如果我必须手动切片,那将是很长时间。所以,我想把它分成图像并处理它们。有没有可能像这样拍照
并像这样拆分成单独的组件?
这是 dilate 的典型情况。这个想法是相邻的文本对应于同一个问题,而距离较远的文本是另一个问题的一部分。每当您想将多个项目连接在一起时,您可以扩大它们以将相邻的轮廓连接成一个轮廓。这是一个简单的方法:
获取二值图像。 Load the image, convert to grayscale, Gaussian blur, then Otsu's threshold获取二值图像。
去除小噪声和伪影。我们创建一个rectangular kernel and morph open来去除图像中的小噪声和伪影。
将相邻的单词连接在一起。我们创建一个更大的矩形内核,并dilate将各个轮廓合并在一起。
检测问题。我们从这里find contours, sort contours from top-to-bottom using imutils.sort_contours()
, filter with a minimum contour area, obtain the rectangular bounding rectangle coordinates and highlight the rectangular contours。然后我们使用 Numpy 切片裁剪每个问题并保存 ROI 图像。
大津获取二值图像的阈值
这里是有趣的部分。我们假设相邻的 text/characters 是同一个问题的一部分,因此我们将单个单词合并为一个轮廓。一个问题是靠在一起的一段单词,所以我们扩大以将它们连接在一起。
个别问题以绿色突出显示
热门问题
底部问题
已保存的 ROI 问题(假设来自 top-to-bottom)
代码
import cv2
from imutils import contours
# Load image, grayscale, Gaussian blur, Otsu's threshold
image = cv2.imread('1.png')
original = image.copy()
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (7,7), 0)
thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
# Remove small artifacts and noise with morph open
open_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, open_kernel, iterations=1)
# Create rectangular structuring element and dilate
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9,9))
dilate = cv2.dilate(opening, kernel, iterations=4)
# Find contours, sort from top to bottom, and extract each question
cnts = cv2.findContours(dilate, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
(cnts, _) = contours.sort_contours(cnts, method="top-to-bottom")
# Get bounding box of each question, crop ROI, and save
question_number = 0
for c in cnts:
# Filter by area to ensure its not noise
area = cv2.contourArea(c)
if area > 150:
x,y,w,h = cv2.boundingRect(c)
cv2.rectangle(image, (x, y), (x + w, y + h), (36,255,12), 2)
question = original[y:y+h, x:x+w]
cv2.imwrite('question_{}.png'.format(question_number), question)
question_number += 1
cv2.imshow('thresh', thresh)
cv2.imshow('dilate', dilate)
cv2.imshow('image', image)
cv2.waitKey()
我们可以使用(主要)形态学运算来解决它:
- 将输入图像读取为灰度。
- 应用反演阈值。
使用 cv2.THRESH_OTSU
的自动阈值处理效果很好。
- 应用开放形态学操作去除小伪影(使用内核
np.ones(1, 3)
)
- 使用非常长的水平内核水平扩展 - 从文本行中创建水平线。
- 应用垂直关闭 - 创建两个大集群。
vertical kernel的大小要根据典型的gap来调整。
- 使用统计信息查找连通分量。
- 迭代连通分量并在垂直方向裁剪相关区域。
完整代码示例:
import cv2
import numpy as np
img = cv2.imread('scanned_image.png', cv2.IMREAD_GRAYSCALE) # Read image as grayscale
thesh = cv2.threshold(img, 0, 255, cv2.THRESH_OTSU + cv2.THRESH_BINARY_INV)[1] # Apply automatic thresholding with inversion.
thesh = cv2.morphologyEx(thesh, cv2.MORPH_OPEN, np.ones((1, 3), np.uint8)) # Apply opening morphological operation for removing small artifacts.
thesh = cv2.dilate(thesh, np.ones((1, img.shape[1]), np.uint8)) # Dilate horizontally - make horizontally lines out of the text.
thesh = cv2.morphologyEx(thesh, cv2.MORPH_CLOSE, np.ones((50, 1), np.uint8)) # Apply closing vertically - create two large clusters
nlabel, labels, stats, centroids = cv2.connectedComponentsWithStats(thesh, 4) # Finding connected components with statistics
parts_list = []
# Iterate connected components:
for i in range(1, nlabel):
top = int(stats[i, cv2.CC_STAT_TOP]) # Get most top y coordinate of the connected component
height = int(stats[i, cv2.CC_STAT_HEIGHT]) # Get the height of the connected component
roi = img[top-5:top+height+5, :] # Crop the relevant part of the image (add 5 extra rows from top and bottom).
parts_list.append(roi.copy()) # Add the cropped area to a list
cv2.imwrite(f'part{i}.png', roi) # Save the image part for testing
cv2.imshow(f'part{i}', roi) # Show part for testing
# Show image and thesh testing
cv2.imshow('img', img)
cv2.imshow('thesh', thesh)
cv2.waitKey()
cv2.destroyAllWindows()
结果:
第 1 阶段:
第 2 阶段:
第 3 阶段:
第 4 阶段:
顶部区域:
底部区域:
我打算拆分 this PDF 文档中的问题。挑战在于问题没有按顺序排列。比如第一个问题占了一整页,第二个也一样,第三个和第四个一起占一页。如果我必须手动切片,那将是很长时间。所以,我想把它分成图像并处理它们。有没有可能像这样拍照
并像这样拆分成单独的组件?
这是 dilate 的典型情况。这个想法是相邻的文本对应于同一个问题,而距离较远的文本是另一个问题的一部分。每当您想将多个项目连接在一起时,您可以扩大它们以将相邻的轮廓连接成一个轮廓。这是一个简单的方法:
获取二值图像。 Load the image, convert to grayscale, Gaussian blur, then Otsu's threshold获取二值图像。
去除小噪声和伪影。我们创建一个rectangular kernel and morph open来去除图像中的小噪声和伪影。
将相邻的单词连接在一起。我们创建一个更大的矩形内核,并dilate将各个轮廓合并在一起。
检测问题。我们从这里find contours, sort contours from top-to-bottom using
imutils.sort_contours()
, filter with a minimum contour area, obtain the rectangular bounding rectangle coordinates and highlight the rectangular contours。然后我们使用 Numpy 切片裁剪每个问题并保存 ROI 图像。
大津获取二值图像的阈值
这里是有趣的部分。我们假设相邻的 text/characters 是同一个问题的一部分,因此我们将单个单词合并为一个轮廓。一个问题是靠在一起的一段单词,所以我们扩大以将它们连接在一起。
个别问题以绿色突出显示
热门问题
底部问题
已保存的 ROI 问题(假设来自 top-to-bottom)
代码
import cv2
from imutils import contours
# Load image, grayscale, Gaussian blur, Otsu's threshold
image = cv2.imread('1.png')
original = image.copy()
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (7,7), 0)
thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
# Remove small artifacts and noise with morph open
open_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, open_kernel, iterations=1)
# Create rectangular structuring element and dilate
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9,9))
dilate = cv2.dilate(opening, kernel, iterations=4)
# Find contours, sort from top to bottom, and extract each question
cnts = cv2.findContours(dilate, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
(cnts, _) = contours.sort_contours(cnts, method="top-to-bottom")
# Get bounding box of each question, crop ROI, and save
question_number = 0
for c in cnts:
# Filter by area to ensure its not noise
area = cv2.contourArea(c)
if area > 150:
x,y,w,h = cv2.boundingRect(c)
cv2.rectangle(image, (x, y), (x + w, y + h), (36,255,12), 2)
question = original[y:y+h, x:x+w]
cv2.imwrite('question_{}.png'.format(question_number), question)
question_number += 1
cv2.imshow('thresh', thresh)
cv2.imshow('dilate', dilate)
cv2.imshow('image', image)
cv2.waitKey()
我们可以使用(主要)形态学运算来解决它:
- 将输入图像读取为灰度。
- 应用反演阈值。
使用cv2.THRESH_OTSU
的自动阈值处理效果很好。 - 应用开放形态学操作去除小伪影(使用内核
np.ones(1, 3)
) - 使用非常长的水平内核水平扩展 - 从文本行中创建水平线。
- 应用垂直关闭 - 创建两个大集群。
vertical kernel的大小要根据典型的gap来调整。 - 使用统计信息查找连通分量。
- 迭代连通分量并在垂直方向裁剪相关区域。
完整代码示例:
import cv2
import numpy as np
img = cv2.imread('scanned_image.png', cv2.IMREAD_GRAYSCALE) # Read image as grayscale
thesh = cv2.threshold(img, 0, 255, cv2.THRESH_OTSU + cv2.THRESH_BINARY_INV)[1] # Apply automatic thresholding with inversion.
thesh = cv2.morphologyEx(thesh, cv2.MORPH_OPEN, np.ones((1, 3), np.uint8)) # Apply opening morphological operation for removing small artifacts.
thesh = cv2.dilate(thesh, np.ones((1, img.shape[1]), np.uint8)) # Dilate horizontally - make horizontally lines out of the text.
thesh = cv2.morphologyEx(thesh, cv2.MORPH_CLOSE, np.ones((50, 1), np.uint8)) # Apply closing vertically - create two large clusters
nlabel, labels, stats, centroids = cv2.connectedComponentsWithStats(thesh, 4) # Finding connected components with statistics
parts_list = []
# Iterate connected components:
for i in range(1, nlabel):
top = int(stats[i, cv2.CC_STAT_TOP]) # Get most top y coordinate of the connected component
height = int(stats[i, cv2.CC_STAT_HEIGHT]) # Get the height of the connected component
roi = img[top-5:top+height+5, :] # Crop the relevant part of the image (add 5 extra rows from top and bottom).
parts_list.append(roi.copy()) # Add the cropped area to a list
cv2.imwrite(f'part{i}.png', roi) # Save the image part for testing
cv2.imshow(f'part{i}', roi) # Show part for testing
# Show image and thesh testing
cv2.imshow('img', img)
cv2.imshow('thesh', thesh)
cv2.waitKey()
cv2.destroyAllWindows()
结果:
第 1 阶段:
第 2 阶段:
第 3 阶段:
第 4 阶段:
顶部区域:
底部区域: