填充形状边缘的间隙

时间:2014-10-27 11:03:30

标签: opencv image-processing

是否有一种算法在填充样本图像上的孔方面表现良好?膨胀不能很好地工作,因为在我最终设法连接这些曲线的末端之前,曲线变得非常厚。我想避免加粗线条。谢谢你的帮助。

enter image description here

是的,图像中可能只有任何字母或形状有这样的洞。

4 个答案:

答案 0 :(得分:5)

另一种更简单的方法,可能会更好地转换为OpenCV,因为它使用卷积而不是顺序Perl / C代码。

基本上将所有黑色像素设置为值10,将所有白色像素设置为值0,然后将图像与以下3x3内核进行卷积:

1  1  1
1  10 1
1  1  1

现在,内核中间的黑色像素将给出100(10x10),并且邻域中的任何其他黑色像素将给出10(10x1)。因此,如果我们想要具有仅具有一个相邻黑色像素的中心黑色像素的点,则其值将为110(100 + 10)。因此,让所有具有红色值110的像素着色。这给出了这个命令:

convert EsmKh.png -colorspace gray -fill gray\(10\) -opaque black -fill gray\(0\) -opaque white -morphology convolve '3x3: 1,1,1 1,10,1 1,1,1' -fill red -opaque gray\(110\) out.png

生成的图像(您可能需要放大间隙才能看到红色):

enter image description here

如果您需要红色像素列表,请将输出文件名替换为txt:并按以下方式搜索:

convert EsmKh.png -colorspace gray -fill rgb\(10,10,10\) -opaque black -fill rgb\(0,0,0\) -opaque white -morphology convolve '3x3: 1,1,1 1,10,1 1,1,1' txt: | grep "110,110,110"

给出:

86,55: (110,110,110)  #6E6E6E  grey43
459,55: (110,110,110)  #6E6E6E  grey43
83,56: (110,110,110)  #6E6E6E  grey43
507,59: (110,110,110)  #6E6E6E  grey43
451,64: (110,110,110)  #6E6E6E  grey43
82,65: (110,110,110)  #6E6E6E  grey43
134,68: (110,110,110)  #6E6E6E  grey43
519,75: (110,110,110)  #6E6E6E  grey43
245,81: (110,110,110)  #6E6E6E  grey43
80,83: (110,110,110)  #6E6E6E  grey43
246,83: (110,110,110)  #6E6E6E  grey43
269,84: (110,110,110)  #6E6E6E  grey43
288,85: (110,110,110)  #6E6E6E  grey43
315,87: (110,110,110)  #6E6E6E  grey43
325,87: (110,110,110)  #6E6E6E  grey43
422,104: (110,110,110)  #6E6E6E  grey43
131,116: (110,110,110)  #6E6E6E  grey43
524,116: (110,110,110)  #6E6E6E  grey43
514,117: (110,110,110)  #6E6E6E  grey43
122,118: (110,110,110)  #6E6E6E  grey43
245,122: (110,110,110)  #6E6E6E  grey43
76,125: (110,110,110)  #6E6E6E  grey43
456,128: (110,110,110)  #6E6E6E  grey43
447,129: (110,110,110)  #6E6E6E  grey43
245,131: (110,110,110)  #6E6E6E  grey43
355,135: (110,110,110)  #6E6E6E  grey43
80,146: (110,110,110)  #6E6E6E  grey43
139,151: (110,110,110)  #6E6E6E  grey43
80,156: (110,110,110)  #6E6E6E  grey43
354,157: (110,110,110)  #6E6E6E  grey43
144,160: (110,110,110)  #6E6E6E  grey43
245,173: (110,110,110)  #6E6E6E  grey43
246,183: (110,110,110)  #6E6E6E  grey43
76,191: (110,110,110)  #6E6E6E  grey43
82,197: (110,110,110)  #6E6E6E  grey43
126,200: (110,110,110)  #6E6E6E  grey43
117,201: (110,110,110)  #6E6E6E  grey43
245,204: (110,110,110)  #6E6E6E  grey43
248,206: (110,110,110)  #6E6E6E  grey43
297,209: (110,110,110)  #6E6E6E  grey43
309,210: (110,110,110)  #6E6E6E  grey43

现在你可以处理红点列表,并为每一个红点找到最近的其他红点并用直线连接它们 - 或者如果你感觉非常敏锐的话,做一些曲线拟合。当然,可能需要进行一些改进,您可能希望设置最大间隙填充线长度。

答案 1 :(得分:2)

我对此有点尝试。它可能需要一些调整,但这是一个想法。我的算法如下:

查找所有具有1个黑色相邻像素的黑色像素,将其着色为红色并将其放在pixels at ends列表中。

浏览所有红色像素的列表,找到最近的其他红色像素,并在两者之间绘制直线。

顺便说一下,我只实现了第一部分 - 要留给读者做的东西; - )

#!/usr/bin/perl
use strict;
use warnings;
use Image::Magick;
use Data::Dumper;

my $im=Image::Magick->new();
$im->Read('EsmKh.png');

my ($width,$height)=$im->Get('width','height');
my $out=Image::Magick->new();
$out->Read('EsmKh.png');

my @pixels;
# Iterate over pixels
for my $y (0..($height-1)){
   for my $x (0..($width-1)){
      my (@pixel) = split(/,/, $im->Get("pixel[$x,$y]"));
      $pixels[$x][$y]=$pixel[0];
   }
}

# Find black pixels that have precisely 1 black neighbour
for my $y (1..($height-2)){
   for my $x (1..($width-2)){
      next if $pixels[$x][$y]!=0;
      my $neighbours=0;
      for(my $i=$x-1;$i<=$x+1;$i++){
         for(my $j=$y-1;$j<=$y+1;$j++){
            $neighbours++ if $pixels[$i][$j]==0;
         }
      }
      $neighbours--;    # Uncount ourself !
      if($neighbours==1){
         $out->Set("pixel[$x,$y]"=>'red');
      }
   }
}
$out->Write(filename=>'out.png');

<强>结果

您必须放大才能看到红色像素...

enter image description here

缩放图片

enter image description here

答案 2 :(得分:1)

在获得加厚的图像后,您可以恢复“瘦身”#34;形状使用skeletonization。我发现了一个骨架化here的实现。

如果你想避免太多的增厚(因为它会扭曲图像并将形状的某些部分合并在一起),请使用温和的侵蚀和骨架化,直到你填满孔。

答案 3 :(得分:0)

这是Mark Setchell算法的OpenCVC++实现。这非常简单,使用相同的内核,并通过cv::filter2D函数对输入图像进行卷积。我有选择地反转了输入图像,因此目标像素的值为255

//Read input Image
cv::Mat inputImage = cv::imread( "C://opencvImages//blobs.png", cv::IMREAD_GRAYSCALE );

//Invert the image:
inputImage = 255 - inputImage;

//Threshold the image so that white pixels get a value of 0 and
//black pixels a value of 10:
cv::threshold( inputImage, inputImage, 128, 10, cv::THRESH_BINARY );

现在,设置内核并对图像进行卷积,如下所示:

//Set up the end-point kernel:
cv::Mat kernel = ( cv::Mat_<int>(3, 3) <<
  1, 1, 1,
  1, 10, 1,
  1, 1, 1
);

//Convolute image with kernel:
cv::filter2D( inputImage, inputImage, -1 , kernel, cv::Point( -1, -1 ), 0, cv::BORDER_DEFAULT );

卷积的直接结果是这样,端点上的像素现在具有110的值,可以在此输出中(几乎)看到它:

让这些像素阈值化并将它们覆盖在原始图像上。结果就是(红色像素):

此外,可以在开始时计算图像的skeleton。骨架的归一化线宽为1 pixel。该函数是OpenCV的Extended Image Processing module的一部分:

#include <opencv2/ximgproc.hpp>

//Compute the skeleton of the input:
cv::Mat skel;
int algorithmType = 1;
cv::ximgproc::thinning( inputImage, skel, algorithmType );