我有一个应用程序,该应用程序将用于从扫描的文档中裁剪空白,例如this image。我要做的是仅提取卡,然后删除所有白色/空白区域。我正在使用Emgucv FindContours进行此操作,目前,我可以在图像中找到卡片轮廓和扫描仪捕获的一些噪音,如下所示。
我的问题是如何裁剪找到的最大轮廓,或者如何通过除去其他轮廓和空白/空格来提取轮廓?也许轮廓索引是可能的?
编辑:也许另一个可能的解决方案是,是否可以将轮廓绘制到另一个pictureBox。
这是我正在使用的代码:
Image<Bgr, byte> imgInput;
Image<Bgr, byte> imgCrop;
private void abrirToolStripMenuItem_Click(object sender, EventArgs e)
{
try
{
OpenFileDialog dialog = new OpenFileDialog();
if (dialog.ShowDialog() ==DialogResult.OK)
{
imgInput = new Image<Bgr, byte>(dialog.FileName);
pictureBox1.Image = imgInput.Bitmap;
imgCrop = imgInput;
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void shapeToolStripMenuItem_Click(object sender, EventArgs e)
{
if (imgCrop == null)
{
return;
}
try
{
var temp = imgCrop.SmoothGaussian(5).Convert<Gray, byte>().ThresholdBinaryInv(new Gray(230), new Gray(255));
VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint();
Mat m = new Mat();
CvInvoke.FindContours(temp, contours, m, Emgu.CV.CvEnum.RetrType.External, Emgu.CV.CvEnum.ChainApproxMethod.ChainApproxSimple);
for (int i = 0; i < contours.Size; i++)
{
double perimeter = CvInvoke.ArcLength(contours[i], true);
VectorOfPoint approx = new VectorOfPoint();
CvInvoke.ApproxPolyDP(contours[i], approx, 0.04 * perimeter, true);
CvInvoke.DrawContours(imgCrop, contours, i, new MCvScalar(0, 0, 255), 2);
pictureBox2.Image = imgCrop.Bitmap;
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
答案 0 :(得分:1)
我会在C++
中给您答案,但是在Emgu CV
中应该可以使用相同的操作。
我提出以下方法:使用 HSV 颜色空间分割(即–分离)目标对象。计算感兴趣对象的二进制掩码。获取二进制掩码中的最大blob ,它应该是卡。计算卡的边界框。从输入图像中裁剪卡
好,首先获取(或读取)输入图像。应用median blur
滤波器,它将有助于消除您在输入中看到的高频噪声(灰色的小斑点)。不过,要调整的主要参数是size
(或滤镜光圈)中的kernel
,但要小心-高值会产生侵略性效果并可能会破坏图像:
//read input image:
std::string imageName = "C://opencvImages//yoshiButNotYoshi.png";
cv::Mat imageInput = cv::imread( imageName );
//apply a median blur filter, the size of the kernel is 5 x 5:
cv::Mat blurredImage;
cv::medianBlur ( imageInput, blurredImage, 5 );
这是模糊滤镜(调整了嵌入图像的大小)的结果:
接下来,分割图像。利用背景为白色的事实,其他所有东西(主要是感兴趣的对象)都具有 some 颜色信息。您可以使用HSV
颜色空间。首先,将BGR
图像转换为HSV
:
//BGR to HSV conversion:
cv::Mat hsvImg;
cv::cvtColor( blurredImage, hsvImg, CV_RGB2HSV );
HSV
颜色空间对颜色信息的编码与典型的BGR/RGB
颜色空间不同。它相对于其他颜色模型的优势在很大程度上取决于应用程序,但通常来说,在使用色相渐变时,它的功能更加强大。我将尝试为感兴趣的对象获取基于HSV的二进制掩码。
在二进制掩码中,您对输入图像感兴趣的所有内容都用white
上色,其他所有内容都用black
上色(反之亦然)。您可以使用inRange
函数获得此掩码。但是,您必须指定将在输出蒙版中以白色(或黑色)呈现的颜色范围。对于您的图像,使用HSV颜色模型,这些值为:
cv::Scalar minColor( 0, 0, 100 ); //the lower range of colors
cv::Scalar maxColor( 0, 0, 255 ); //the upper range of colors
现在,获取二进制掩码:
//prepare the binary mask:
cv::Mat binaryMask;
//create the binary mask using the specified range of color
cv::inRange( hsvImg, minColor, maxColor, binaryMask );
//invert the mask:
binaryMask = 255 - binaryMask;
您得到此图像:
现在,您可以通过morphological filtering
消除一些噪音(在模糊滤镜中幸存下来)。形态过滤器实质上是应用于二进制(或灰度)图像的逻辑规则。它们在输入中采用像素的“邻域” 并应用逻辑函数以获得输出。它们在清理二进制映像时非常方便。我将应用一系列逻辑过滤器来实现这一目标。
我将首先erode
图像,然后使用dilate
3 iterations
。 structuring element
的大小为rectangle
的{{1}}:
3 x 3
您将获得此输出。看看嘈杂的斑点大部分都消失了:
现在,很酷的部分来了。您可以遍历此图像中的所有 //apply some morphology the clean the binary mask a little bit:
cv::Mat SE = cv::getStructuringElement( cv::MORPH_RECT, cv::Size(3, 3) );
int morphIterations = 3;
cv::morphologyEx( binaryMask, binaryMask, cv::MORPH_ERODE, SE, cv::Point(-1,-1), morphIterations );
cv::morphologyEx( binaryMask, binaryMask, cv::MORPH_DILATE, SE, cv::Point(-1,-1), morphIterations );
,并全部获得最大。这是我经常执行的典型操作,因此,我编写了一个执行该操作的函数。它称为contours
。稍后再介绍该功能。查看找到并提取出最大斑点后得到的结果:
findBiggestBlob
您得到了:
现在,您可以使用 //find the biggest blob in the binary image:
cv::Mat biggestBlob = findBiggestBlob( binaryMask );
来获得最大斑点的bounding box
:
boundingRect
让我们在输入图像上绘制 //Get the bounding box of the biggest blob:
cv::Rect bBox = cv::boundingRect( biggestBlob );
:
bounding box
最后,让我们从输入图像中裁剪卡片:
cv::Mat imageClone = imageInput.clone();
cv::rectangle( imageClone, bBox, cv::Scalar(255,0,0), 2 );
这是裁剪后的输出:
这是 cv::Mat croppedImage = imageInput( bBox );
函数的代码。这个想法只是计算二进制输入中的所有轮廓,计算它们的面积,并存储束中面积最大的轮廓:
findBiggestBlob