从图像中的统一背景中提取页面
Extract a page from a uniform background in an image
如果我有一张图片,其中一页文字拍摄在统一的背景上,我如何自动检测纸张和背景之间的边界?
我想要检测的图像示例如下所示。我将要处理的图像由统一背景上的单个页面组成,它们可以任意角度旋转。
一种简单的方法是在将图像转换为灰度后,通过某个已知值对图像进行阈值处理。该方法的问题在于我们正在应用全局阈值,因此如果您将阈值设置得太高,图像底部的一些纸张将会丢失。如果您将阈值设置得太低,那么您肯定会得到论文,但是您也会包含很多背景像素,并且可能很难通过 post-processing.
我可以建议的一件事是使用自适应阈值算法。过去对我有用的算法是 Bradley-Roth adaptive thresholding algorithm。您可以在 post 上阅读相关内容,我不久前评论过:
但是,如果您想了解它的要点,请先拍摄图像的 integral image 灰度版本。积分图像很重要,因为它允许您以 O(1)
复杂度计算 window 内的像素总和。然而,积分图像的计算通常是O(n^2)
,但你只需要做一次。使用积分图像,您扫描大小为 s x s
的像素邻域,并检查平均强度是否小于此 s x s
window 内实际平均值的 t%
然后这是分类为背景的像素。如果它更大,则它被归类为前景的一部分。这是自适应的,因为阈值处理是使用局部像素邻域而不是使用全局阈值完成的。
我已经为您编写了 Bradley-Roth 算法的实现代码。该算法的默认参数是 s
是图像宽度的 1/8,t
是 15%。因此,你可以这样调用它来调用默认参数:
out = adaptiveThreshold(im);
im
是输入图像,out
是二值图像,表示前景 (logical true
) 或背景 (logical false
)。您可以使用第二个和第三个输入参数:s
是阈值的大小 window 和 t
我们上面讨论的百分比,可以像这样调用函数:
out = adaptiveThreshold(im, s, t);
因此,算法的代码如下所示:
function [out] = adaptiveThreshold(im, s, t)
%// Error checking of the input
%// Default value for s is 1/8th the width of the image
%// Must make sure that this is a whole number
if nargin <= 1, s = round(size(im,2) / 8); end
%// Default value for t is 15
%// t is used to determine whether the current pixel is t% lower than the
%// average in the particular neighbourhood
if nargin <= 2, t = 15; end
%// Too few or too many arguments?
if nargin == 0, error('Too few arguments'); end
if nargin >= 4, error('Too many arguments'); end
%// Convert to grayscale if necessary then cast to double to ensure no
%// saturation
if size(im, 3) == 3
im = double(rgb2gray(im));
elseif size(im, 3) == 1
im = double(im);
else
error('Incompatible image: Must be a colour or grayscale image');
end
%// Compute integral image
intImage = cumsum(cumsum(im, 2), 1);
%// Define grid of points
[rows, cols] = size(im);
[X,Y] = meshgrid(1:cols, 1:rows);
%// Ensure s is even so that we are able to index the image properly
s = s + mod(s,2);
%// Access the four corners of each neighbourhood
x1 = X - s/2; x2 = X + s/2;
y1 = Y - s/2; y2 = Y + s/2;
%// Ensure no co-ordinates are out of bounds
x1(x1 < 1) = 1;
x2(x2 > cols) = cols;
y1(y1 < 1) = 1;
y2(y2 > rows) = rows;
%// Count how many pixels there are in each neighbourhood
count = (x2 - x1) .* (y2 - y1);
%// Compute row and column co-ordinates to access each corner of the
%// neighbourhood for the integral image
f1_x = x2; f1_y = y2;
f2_x = x2; f2_y = y1 - 1; f2_y(f2_y < 1) = 1;
f3_x = x1 - 1; f3_x(f3_x < 1) = 1; f3_y = y2;
f4_x = f3_x; f4_y = f2_y;
%// Compute 1D linear indices for each of the corners
ind_f1 = sub2ind([rows cols], f1_y, f1_x);
ind_f2 = sub2ind([rows cols], f2_y, f2_x);
ind_f3 = sub2ind([rows cols], f3_y, f3_x);
ind_f4 = sub2ind([rows cols], f4_y, f4_x);
%// Calculate the areas for each of the neighbourhoods
sums = intImage(ind_f1) - intImage(ind_f2) - intImage(ind_f3) + ...
intImage(ind_f4);
%// Determine whether the summed area surpasses a threshold
%// Set this output to 0 if it doesn't
locs = (im .* count) <= (sums * (100 - t) / 100);
out = true(size(im));
out(locs) = false;
end
当我使用你的图片并设置 s = 500
和 t = 5
时,这是代码,这是我得到的图片:
im = imread('http://i.stack.imgur.com/MEcaz.jpg');
out = adaptiveThreshold(im, 500, 5);
imshow(out);
你可以看到图像底部的白色有一些虚假的白色像素,纸里面有一些我们需要填充的洞。因此,让我们使用一些形态学并声明一个 15 x 15 正方形的结构元素,执行开运算以移除噪声像素,然后在完成后填充空洞:
se = strel('square', 15);
out = imopen(out, se);
out = imfill(out, 'holes');
imshow(out);
这就是我在所有这些之后得到的:
还不错吧?现在如果你真的想看看图像被分割后的纸张是什么样子,我们可以使用这个蒙版并将它与原始图像相乘。这样,所有属于纸张的像素都会保留,而属于背景的像素会消失:
out_colour = bsxfun(@times, im, uint8(out));
imshow(out_colour);
我们得到这个:
您必须尝试使用这些参数,直到它适合您为止,但上面的参数是我用来让它在您向我们展示的特定页面上工作的参数。图像处理就是反复试验,将处理步骤按正确的顺序排列,直到你得到足够好的东西来满足你的目的。
快乐的图像过滤!
如果我有一张图片,其中一页文字拍摄在统一的背景上,我如何自动检测纸张和背景之间的边界?
我想要检测的图像示例如下所示。我将要处理的图像由统一背景上的单个页面组成,它们可以任意角度旋转。
一种简单的方法是在将图像转换为灰度后,通过某个已知值对图像进行阈值处理。该方法的问题在于我们正在应用全局阈值,因此如果您将阈值设置得太高,图像底部的一些纸张将会丢失。如果您将阈值设置得太低,那么您肯定会得到论文,但是您也会包含很多背景像素,并且可能很难通过 post-processing.
我可以建议的一件事是使用自适应阈值算法。过去对我有用的算法是 Bradley-Roth adaptive thresholding algorithm。您可以在 post 上阅读相关内容,我不久前评论过:
但是,如果您想了解它的要点,请先拍摄图像的 integral image 灰度版本。积分图像很重要,因为它允许您以 O(1)
复杂度计算 window 内的像素总和。然而,积分图像的计算通常是O(n^2)
,但你只需要做一次。使用积分图像,您扫描大小为 s x s
的像素邻域,并检查平均强度是否小于此 s x s
window 内实际平均值的 t%
然后这是分类为背景的像素。如果它更大,则它被归类为前景的一部分。这是自适应的,因为阈值处理是使用局部像素邻域而不是使用全局阈值完成的。
我已经为您编写了 Bradley-Roth 算法的实现代码。该算法的默认参数是 s
是图像宽度的 1/8,t
是 15%。因此,你可以这样调用它来调用默认参数:
out = adaptiveThreshold(im);
im
是输入图像,out
是二值图像,表示前景 (logical true
) 或背景 (logical false
)。您可以使用第二个和第三个输入参数:s
是阈值的大小 window 和 t
我们上面讨论的百分比,可以像这样调用函数:
out = adaptiveThreshold(im, s, t);
因此,算法的代码如下所示:
function [out] = adaptiveThreshold(im, s, t)
%// Error checking of the input
%// Default value for s is 1/8th the width of the image
%// Must make sure that this is a whole number
if nargin <= 1, s = round(size(im,2) / 8); end
%// Default value for t is 15
%// t is used to determine whether the current pixel is t% lower than the
%// average in the particular neighbourhood
if nargin <= 2, t = 15; end
%// Too few or too many arguments?
if nargin == 0, error('Too few arguments'); end
if nargin >= 4, error('Too many arguments'); end
%// Convert to grayscale if necessary then cast to double to ensure no
%// saturation
if size(im, 3) == 3
im = double(rgb2gray(im));
elseif size(im, 3) == 1
im = double(im);
else
error('Incompatible image: Must be a colour or grayscale image');
end
%// Compute integral image
intImage = cumsum(cumsum(im, 2), 1);
%// Define grid of points
[rows, cols] = size(im);
[X,Y] = meshgrid(1:cols, 1:rows);
%// Ensure s is even so that we are able to index the image properly
s = s + mod(s,2);
%// Access the four corners of each neighbourhood
x1 = X - s/2; x2 = X + s/2;
y1 = Y - s/2; y2 = Y + s/2;
%// Ensure no co-ordinates are out of bounds
x1(x1 < 1) = 1;
x2(x2 > cols) = cols;
y1(y1 < 1) = 1;
y2(y2 > rows) = rows;
%// Count how many pixels there are in each neighbourhood
count = (x2 - x1) .* (y2 - y1);
%// Compute row and column co-ordinates to access each corner of the
%// neighbourhood for the integral image
f1_x = x2; f1_y = y2;
f2_x = x2; f2_y = y1 - 1; f2_y(f2_y < 1) = 1;
f3_x = x1 - 1; f3_x(f3_x < 1) = 1; f3_y = y2;
f4_x = f3_x; f4_y = f2_y;
%// Compute 1D linear indices for each of the corners
ind_f1 = sub2ind([rows cols], f1_y, f1_x);
ind_f2 = sub2ind([rows cols], f2_y, f2_x);
ind_f3 = sub2ind([rows cols], f3_y, f3_x);
ind_f4 = sub2ind([rows cols], f4_y, f4_x);
%// Calculate the areas for each of the neighbourhoods
sums = intImage(ind_f1) - intImage(ind_f2) - intImage(ind_f3) + ...
intImage(ind_f4);
%// Determine whether the summed area surpasses a threshold
%// Set this output to 0 if it doesn't
locs = (im .* count) <= (sums * (100 - t) / 100);
out = true(size(im));
out(locs) = false;
end
当我使用你的图片并设置 s = 500
和 t = 5
时,这是代码,这是我得到的图片:
im = imread('http://i.stack.imgur.com/MEcaz.jpg');
out = adaptiveThreshold(im, 500, 5);
imshow(out);
你可以看到图像底部的白色有一些虚假的白色像素,纸里面有一些我们需要填充的洞。因此,让我们使用一些形态学并声明一个 15 x 15 正方形的结构元素,执行开运算以移除噪声像素,然后在完成后填充空洞:
se = strel('square', 15);
out = imopen(out, se);
out = imfill(out, 'holes');
imshow(out);
这就是我在所有这些之后得到的:
还不错吧?现在如果你真的想看看图像被分割后的纸张是什么样子,我们可以使用这个蒙版并将它与原始图像相乘。这样,所有属于纸张的像素都会保留,而属于背景的像素会消失:
out_colour = bsxfun(@times, im, uint8(out));
imshow(out_colour);
我们得到这个:
您必须尝试使用这些参数,直到它适合您为止,但上面的参数是我用来让它在您向我们展示的特定页面上工作的参数。图像处理就是反复试验,将处理步骤按正确的顺序排列,直到你得到足够好的东西来满足你的目的。
快乐的图像过滤!