通过对灰度值进行统计,从扫描图像自动裁剪黑色边框(Java)

通过对灰度值进行统计,从扫描图像自动裁剪黑色边框(Java),java,image-processing,crop,Java,Image Processing,Crop,我正在写一段代码来自动检测扫描图像上的黑色噪声边界并将其裁剪掉。 我的算法基于两个变量:灰度平均值(行/列中的像素)和位置(图像中的行/列) 灰色平均值 图像为灰度:这意味着任何像素的灰度值都在0(黑色)和255(白色)之间。 对于每行/每列像素,我估计该行/每列中所有像素的平均灰度值。 如果结果为黑色,则当前行/列是要切断的边框的一部分 位置 位置是一行/一列距图像左上角的距离(以像素为单位) 请查看以下图片以了解更好的想法。 扫描图像的缩略图: 结果图表: 通过查看图表,可以很容易地估计

我正在写一段代码来自动检测扫描图像上的黑色噪声边界并将其裁剪掉。 我的算法基于两个变量:灰度平均值(行/列中的像素)和位置(图像中的行/列)

灰色平均值
图像为灰度:这意味着任何像素的灰度值都在0(黑色)和255(白色)之间。
对于每行/每列像素,我估计该行/每列中所有像素的平均灰度值。
如果结果为黑色,则当前行/列是要切断的边框的一部分

位置
位置是一行/一列距图像左上角的距离(以像素为单位)

请查看以下图片以了解更好的想法。
扫描图像的缩略图:

结果图表:

通过查看图表,可以很容易地估计裁剪点的位置,因为以下规则:大多数样本位于白色窄范围(150-200)内,即实际纸张,然后在尾部有一个快速变暗的值。
这些快速变化是裁剪点(还要注意,在尾部的末端,对于一些像素,仍然可以是白色,但这种情况很少发生)

我想自动完成,有什么统计数据可以帮助我吗?
PS:我是一名计算机工程师,我学过一些统计数据,但是。。。太多年以前了

在最好的情况下,代码应该适用于任何受黑边界问题影响的扫描图像,但是,如果是真的,我会满意地使用这些示例:

对图像进行预处理使统计数据更容易计算出来。对于您的情况,使用宽水平线进行形态学闭合,然后使用大津阈值(统计上最优)使任务变得更容易。这里的形态开口很有趣,因为在中,将特别使纸张区域更轻。您有两个示例,其中边界区域是模糊的,即它也包含灯光部分,但这并不意味着此步骤无效。之后,只需按列和行求和,并根据平均值和标准偏差划定边界。如果该值低于
平均值-x*stddev
,则该值不在纸张范围内。这样可以定义纸张的左上角和右下角,用于裁剪图像。定义这些角点的最简单方法是向前和向后线性遍历找到的和,在不满足之前的条件时停止

对于您的图像,
x
在[-1.5,-1]范围内工作(以及其他图像,我在那里进行了测试)。我将闭合操作符的水平线的大小固定在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.

您能不做任何修改就包含实际图像吗?@mmgp问题已编辑,请参阅最后一部分!到目前为止你试过什么?你的问题似乎解释了一种尝试解决方案的直截了当的方法——这是否可行,或者你是在问如何计算平均值?@Greybeardgeek如果我用眼睛看结果图表,我可以清楚地识别裁剪点的位置(这是我手动添加到图表中的绿色注释)。但是我想自动完成这项工作:因此我的代码必须分析数据,并找到与我眼睛相同的裁剪点。对于任何最终在这里寻找CLI解决方案的人来说,ImageMagick可以通过组合
-fuzz X%-trim
一次非常有效,或者通过两步过程中使用
-blur
获得更大的公差。详情请参见链接。太棒了!我花了一段时间才理解你写的东西,因为我对图像处理知之甚少,但我想我现在明白了。。。而且很聪明!只是一个问题:您的解决方案在大多数情况下都有效,但假设扫描图像在边缘的黑色噪声边界之前有一条窄的白色条纹,在这种情况下,使用mean、std-dev和test
mean-x*stddev
的统计方法将失败,对吗?我认为它会失败,因为窄的白色条纹会被认为是真正的纸张。你知道如何处理这些(少数)图像吗?这里的例子:最后一点:我正在用Java编写代码(必须)。因此,为了预处理图像,我不能使用您使用的Python库。您是否已经知道任何用于形态学闭合和大津阈值处理的Java库?我想我可以用ImageMagick进行形态学关闭:但可能有一个真正的Java库(JMagick要求在系统中安装ImageMagick)。我还没有用Java进行图像处理,但看起来ImageJ是一个被广泛接受的库。我会看看我是否能为你提出的新情况想出一些好办法,它确实失败了,因为裁剪区域比页面大(在本例中,在底部,但如果在所有方面都发生了相同的情况。)好的,为了适应这种情况,我建议使用形态开口,然后只保留最大的部分(纸张)。第一步希望将纸张组件与外部边框断开。第二步后,代码保持不变。大