我正在编写一段代码来自动检测扫描图像上的黑色嘈杂边框并将其裁剪掉。 我的算法基于2个变量:灰色平均值(行/列中的像素数)和位置(图像中行/列的位置)。
灰色平均值
图像为灰度:这意味着任何像素的灰度值都在0(白色),255(白色)范围内
对于每行/每列像素,我估计该行/列中所有像素的平均灰度值
如果结果很暗,则当前行/列是要切断的边框的一部分。
POSITION
位置是距离图像左上角的行/列的像素距离。
请查看以下图片以获得更好的主意
扫描图像的缩略图:
结果图:
通过查看图表,可以很容易地估算出裁剪点的位置,因为以下规则:大多数样本都在白色窄范围内(150-200),这是实际纸张,然后在尾巴中有一个快速改变暗值 那些快速变化是裁剪点(请注意,在尾部真正的末端,对于一些像素x仍然可以是白色的,但这种情况很少发生。)
我想自动完成,是否有任何可以帮助我的统计数据? PS:我是一名计算机工程师,我研究了一些统计数据,但是......太多年了!!
在最好的情况下,代码应该适用于受黑色边框问题影响的任何扫描图像,但是,实际上,我会满意地使它适用于这些样本:
https://docs.google.com/folder/d/0B8ubCWBwsuOON3d1VVo4Z1AxWDA/edit
答案 0 :(得分:4)
预处理图像可以使统计数据更加轻松。对于您的情况,具有宽水平线的形态学闭合以及Otsu阈值(统计上最优)使得任务更容易。这里的形态开口很有意思,因为它会特别使纸张区域更轻。你有两个边框区域模糊的例子,即它也包含亮部分,但这并不会使这一步变得无用。之后,只需按列和按行求和,并根据平均值和标准差来划分边界。如果该值低于mean - x*stddev
,那么它就在纸张之外。这样,您可以定义纸张的左上角和右下角,用于裁剪图像。定义这些角落的最简单方法是向前和向后线性遍历找到的总和,在不满足早期条件时停止。
对于你的图像,[-1.5,-1]范围内的x
有效(还有其他人,我在那里测试过)。我在101点固定了关闭操作员的水平线的大小。以下是结果(如果需要进行比较,可以包括角坐标):
正如已经指出的那样,问题在于这些图像中的一些还包含白色边框,如下一种情况(连接到纸张)。为了处理这个问题,在图像是二进制图像之后,考虑应用形态开口,因为这将有希望断开组件。您可以使用大型结构元素,我使用的尺寸为51 x 51,这对于图像的大小来说并不是那么大。主要的限制是您正在使用的库的实现,因为如果实现是一个糟糕的实现,这可能会变慢(scipy具体没有快速实现)。之后,只保留最大的组件并照常进行。
示例代码:
import sys
import numpy
import cv2 as cv
from PIL import Image, ImageOps, ImageDraw
from scipy.ndimage import morphology, label
img = ImageOps.grayscale(Image.open(sys.argv[1]))
im = numpy.array(img, dtype=numpy.uint8)
im = morphology.grey_closing(img, (1, 101))
t, im = cv.threshold(im, 0, 1, cv.THRESH_OTSU)
# "Clean noise".
im = morphology.grey_opening(im, (51, 51))
# Keep largest component.
lbl, ncc = label(im)
largest = 0, 0
for i in range(1, ncc + 1):
size = len(numpy.where(lbl == i)[0])
if size > largest[1]:
largest = i, size
for i in range(1, ncc + 1):
if i == largest[0]:
continue
im[lbl == i] = 0
col_sum = numpy.sum(im, axis=0)
row_sum = numpy.sum(im, axis=1)
col_mean, col_std = col_sum.mean(), col_sum.std()
row_mean, row_std = row_sum.mean(), row_sum.std()
row_standard = (row_sum - row_mean) / row_std
col_standard = (col_sum - col_mean) / col_std
def end_points(s, std_below_mean=-1.5):
i, j = 0, len(s) - 1
for i, rs in enumerate(s):
if rs > std_below_mean:
break
for j in xrange(len(s) - 1, i, -1):
if s[j] > std_below_mean:
break
return (i, j)
# Bounding rectangle.
x1, x2 = end_points(col_standard)
y1, y2 = end_points(row_standard)
#img.crop((x1, y1, x2, y2)).save(sys.argv[2]) # Crop.
result = img.convert('RGB')
draw = ImageDraw.Draw(result)
draw.line((x1, y1, x2, y1, x2, y2, x1, y2, x1, y1),
fill=(0, 255, 255), width=15)
result.save(sys.argv[2]) # Save with the bounding rectangle.