【问题标题】:Find the corners of a polygon represented by a region mask查找由区域掩码表示的多边形的角
【发布时间】:2010-12-15 06:39:39
【问题描述】:

BW = poly2mask(x, y, m, n) 计算一个 二元感兴趣区域 (ROI) 掩码, BW,来自 ROI 多边形,表示 通过向量 x 和 y。 BW的大小 是 m×n。

poly2mask 设置像素为 BW 在多边形 (X,Y) 内到 1 并将多边形外的像素设置为 0.

问题: 给定凸四边形的这种二进制掩码BW,确定四个角的最有效方法是什么?

例如,

迄今为止的最佳解决方案: 使用edge 找到边界线,使用霍夫变换找到边缘图像中的 4 条线,然后找到这 4 条线的交点或在边缘图像上使用角点检测器。看起来很复杂,我不禁觉得那里有一个更简单的解决方案。

顺便说一句,convhull 并不总是返回 4 个点(也许有人可以建议 qhull 选项来防止这种情况):它也会沿边缘返回几个点。

编辑: Amro's answer 看起来非常优雅和高效。但是每个真正的角落可能有多个“角落”,因为峰不是唯一的。我可以基于 θ 对它们进行聚类并平均一个真实角落周围的“角落”,但主要问题是使用order(1:10)

10 是否足以解释所有角落,或者这会排除真正角落的“角落”吗?

【问题讨论】:

    标签: matlab geometry computer-vision polygon corner-detection


    【解决方案1】:

    这有点类似于@AndyL 的建议。但是我在极坐标中使用边界签名而不是切线。

    请注意,我首先提取边缘,获取边界,然后将其转换为签名。最后我们找到边界上离质心最远的点,这些点构成了找到的角。 (或者,我们也可以检测角点特征中的峰值)。

    以下是完整的实现:

    I = imread('oxyjj.png');
    if ndims(I)==3
        I = rgb2gray(I);
    end
    subplot(221), imshow(I), title('org')
    
    %%# Process Image
    %# edge detection
    BW = edge(I, 'sobel');
    subplot(222), imshow(BW), title('edge')
    
    %# dilation-erosion
    se = strel('disk', 2);
    BW = imdilate(BW,se);
    BW = imerode(BW,se);
    subplot(223), imshow(BW), title('dilation-erosion')
    
    %# fill holes
    BW = imfill(BW, 'holes');
    subplot(224), imshow(BW), title('fill')
    
    %# get boundary
    B = bwboundaries(BW, 8, 'noholes');
    B = B{1};
    
    %%# boudary signature
    %# convert boundary from cartesian to ploar coordinates
    objB = bsxfun(@minus, B, mean(B));
    [theta, rho] = cart2pol(objB(:,2), objB(:,1));
    
    %# find corners
    %#corners = find( diff(diff(rho)>0) < 0 );     %# find peaks
    [~,order] = sort(rho, 'descend');
    corners = order(1:10);
    
    %# plot boundary signature + corners
    figure, plot(theta, rho, '.'), hold on
    plot(theta(corners), rho(corners), 'ro'), hold off
    xlim([-pi pi]), title('Boundary Signature'), xlabel('\theta'), ylabel('\rho')
    
    %# plot image + corners
    figure, imshow(BW), hold on
    plot(B(corners,2), B(corners,1), 's', 'MarkerSize',10, 'MarkerFaceColor','r')
    hold off, title('Corners')
    


    编辑: 在回应 Jacob 的评论时,我应该解释一下,我首先尝试使用一阶/二阶导数找到签名中的峰值,但最终得到了最远的 N 点。 10 只是一个临时值,很难一概而论(我尝试将 4 与角的数量相同,但它并没有涵盖所有角)。我认为将它们聚类以删除重复项的想法值得研究。

    据我所知,第一种方法的问题在于,如果您绘制 rho 而不考虑 θ,您将得到不同的形状(不是相同的峰),因为 我们追踪边界的速度是不同的,取决于曲率。如果我们能弄清楚如何规范化这种效果,我们就可以使用导数得到更准确的结果。

    【讨论】:

    • 我刚刚意识到你已经有一个二进制图像 BW。因此,您可以直接跳到从调用 bwboundaries 开始的获取边界部分
    • +1 - 用于边界签名。但是,我在编辑后的帖子中详细说明了一些问题。
    • @Amro:非常感谢边界签名的想法,但我决定挖掘我的哈里斯角检测器的旧实现并使用它。
    • 你可以使用一些线近似技术来解决拐角重复问题。 OpenCV 使用 CV_CHAIN_APPROX_SIMPLE 标志在 findContours 函数中执行此操作。至于matlab,我没有找到内置的方法,但是在文件交换上找到了Douglas-Peucker简化算法的实现mathworks.com/matlabcentral/fileexchange/…
    • OpenCV approxPolyDP 也实现了 Douglas-Peucker 算法docs.opencv.org/modules/imgproc/doc/…
    【解决方案2】:

    如果您有Image Processing Toolbox,则有一个名为cornermetric 的函数可以实现Harris 角点检测器或Shi 和Tomasi 的最小特征值方法。此函数自 Image Processing Toolbox(MATLAB 版本 R2008b)的 6.2 版开始提供。

    使用此功能,我想出了与其他答案略有不同的方法。下面的解决方案基于这样一种想法,即以每个“真实”角点为中心的圆形区域与多边形重叠的量小于以实际位于边缘的错误角点为中心的圆形区域。此方案还可以处理在同一个角落检测到多个点的情况...

    第一步是加载数据:

    rawImage = imread('oxyjj.png');
    rawImage = rgb2gray(rawImage(7:473, 9:688, :));  % Remove the gray border
    subplot(2, 2, 1);
    imshow(rawImage);
    title('Raw image');
    

    接下来,使用cornermetric 计算角点度量。请注意,我用原始多边形掩盖了角度量,因此我们正在寻找多边形内部的角点(即试图找到多边形的角像素)。 imregionalmax 然后用于查找局部最大值。由于您可以拥有具有相同角度量的大于 1 个像素的集群,因此我将噪声添加到最大值并重新计算,以便在每个最大区域中仅获得 1 个像素。然后使用bwlabel标记每个最大区域:

    cornerImage = cornermetric(rawImage).*(rawImage > 0);
    maxImage = imregionalmax(cornerImage);
    noise = rand(nnz(maxImage), 1);
    cornerImage(maxImage) = cornerImage(maxImage)+noise;
    maxImage = imregionalmax(cornerImage);
    labeledImage = bwlabel(maxImage);
    

    然后用圆盘形结构元素(使用strel 创建)扩展标记区域(使用imdilate):

    diskSize = 5;
    dilatedImage = imdilate(labeledImage, strel('disk', diskSize));
    subplot(2, 2, 2);
    imshow(dilatedImage);
    title('Dilated corner points');
    

    现在标记的角区域已经扩大,它们将部分重叠原始多边形。多边形边缘上的区域将有大约 50% 的重叠,而角上的区域将有大约 25% 的重叠。函数regionprops可用于查找每个标记区域的重叠区域,因此可以将重叠量最少的4个区域视为真正的角点:

    maskImage = dilatedImage.*(rawImage > 0);       % Overlap with the polygon
    stats = regionprops(maskImage, 'Area');         % Compute the areas
    [sortedValues, index] = sort([stats.Area]);     % Sort in ascending order
    cornerLabels = index(1:4);                      % The 4 smallest region labels
    maskImage = ismember(maskImage, cornerLabels);  % Mask of the 4 smallest regions
    subplot(2, 2, 3);
    imshow(maskImage);
    title('Regions of minimal overlap');
    

    我们现在可以使用findismember 获得角的像素坐标:

    [r, c] = find(ismember(labeledImage, cornerLabels));
    subplot(2, 2, 4);
    imshow(rawImage);
    hold on;
    plot(c, r, 'r+', 'MarkerSize', 16, 'LineWidth', 2);
    title('Corner points');
    

    这是一个菱形区域的测试:

    【讨论】:

    • +1 我不知道这个。看看它的实现,它与@Jacob 的类似,但更优化。
    • 我使用的是 R2009a,所以我想这是一个相对较新的添加。
    【解决方案3】:

    我喜欢通过使用边界来解决这个问题,因为它将二维问题简化为一维问题。

    使用图像处理工具包中的bwtraceboundary() 提取边界上的点列表。然后将边界转换为一系列切向量(有很多方法可以做到这一点,一种方法是减去 ith 沿边界从i+deltath 点。)一旦你有一个向量列表,取相邻向量的点积。点积最小的四个点就是你的角!

    如果您希望您的算法在具有任意数量的顶点的多边形上工作,那么只需搜索低于中值点积一定数量标准差的点积。

    【讨论】:

      【解决方案4】:

      我决定使用Harris corner detector(这里是more formal description)来获得角点。这可以按如下方式实现:

      %% Constants
      Window = 3;
      Sigma = 2;
      K = 0.05;
      nCorners = 4;
      
      %% Derivative masks
      dx = [-1 0 1; -1 0 1; -1 0 1];
      dy = dx';   %SO code color fix '
      
      %% Find the image gradient
      % Mask is the binary image of the quadrilateral
      Ix = conv2(double(Mask),dx,'same');   
      Iy = conv2(double(Mask),dy,'same');
      
      %% Use a gaussian windowing function and compute the rest
      Gaussian = fspecial('gaussian',Window,Sigma);
      Ix2 = conv2(Ix.^2,  Gaussian, 'same');  
      Iy2 = conv2(Iy.^2,  Gaussian, 'same');
      Ixy = conv2(Ix.*Iy, Gaussian, 'same');    
      
      %% Find the corners
      CornerStrength = (Ix2.*Iy2 - Ixy.^2) - K*(Ix2 + Iy2).^2;
      [val ind] = sort(CornerStrength(:),'descend');    
      [Ci Cj] = ind2sub(size(CornerStrength),ind(1:nCorners));
      
      %% Display
      imshow(Mask,[]);
      hold on;
      plot(Cj,Ci,'r*');
      

      这里,多角的问题要归功于平滑强度变化的高斯窗函数。下面是带有hot 颜色图的角落的缩放版本。

      【讨论】:

      • 不错!作为说明,我喜欢在 MATLAB cmets 中将 # 放在 % 之后,以便获得正确的 SO 颜色
      • 谢谢!使用 ' 的任何修复?
      • 不。听我在测试你的方法,它似乎也不完美(在计算机视觉中总是如此!)。使用上面的代码尝试以下掩码:x = [16 282 276 30 16]; y = [14 29 200 225 14]; BW = poly2mask(x,y, 246,300); 你会得到一些重复的角(即使尝试调整其他参数),并且也必须增加 nCorners..
      • 你完全正确 - 看起来某些多边形需要聚类。
      • 我不想太强烈地推动我的想法,但我认为如果我们找到确切的 4 个 theta 峰,签名方法可以完善大多数四边形,这毕竟在情节中清晰可见。选择最高的 N 点可能不是最好的方法,但也许有人可以改进这部分..
      【解决方案5】:

      这是一个使用 Ruby 和 HornetsEye 的示例。基本上,该程序会创建量化 Sobel 梯度方向的直方图,以找到主要方向。如果找到四个主要方向,则拟合线并假定相邻线之间的交点是投影矩形的角。

      #!/usr/bin/env ruby
      require 'hornetseye'
      include Hornetseye
      Q = 36
      img = MultiArray.load_ubyte 'http://imgur.com/oxyjj.png'
      dx, dy = 8, 6
      box = [ dx ... 688, dy ... 473 ]
      crop = img[ *box ]
      crop.show
      s0, s1 = crop.sobel( 0 ), crop.sobel( 1 )
      mag = Math.sqrt s0 ** 2 + s1 ** 2
      mag.normalise.show
      arg = Math.atan2 s1, s0
      msk = mag >= 500
      arg_q = ( ( arg.mask( msk ) / Math::PI + 1 ) * Q / 2 ).to_int % Q
      hist = arg_q.hist_weighted Q, mag.mask( msk )
      segments = ( hist >= hist.max / 4 ).components
      lines = arg_q.map segments
      lines.unmask( msk ).normalise.show
      if segments.max == 4
        pos = MultiArray.scomplex *crop.shape
        pos.real = MultiArray.int( *crop.shape ).indgen! % crop.shape[0]
        pos.imag = MultiArray.int( *crop.shape ).indgen! / crop.shape[0]
        weights = lines.hist( 5 ).major 1.0
        centre = lines.hist_weighted( 5, pos.mask( msk ) ) / weights
        vector = pos.mask( msk ) - lines.map( centre )
        orientation = lines.hist_weighted( 5, vector ** 2 ) ** 0.5
        corner = Sequence[ *( 0 ... 4 ).collect do |i|
          i1, i2 = i + 1, ( i + 1 ) % 4 + 1
          l1, a1, l2, a2 = centre[i1], orientation[i1], centre[i2], orientation[i2]
          ( l1 * a1.conj * a2 - l2 * a1 * a2.conj -
            l1.conj * a1 * a2 + l2.conj * a1 * a2 ) /
            ( a1.conj * a2 - a1 * a2.conj )
        end ] 
        result = MultiArray.ubytergb( *img.shape ).fill! 128
        result[ *box ] = crop
        corner.to_a.each do |c|
          result[ c.real.to_i + dx - 1 .. c.real.to_i + dx + 1,
                  c.imag.to_i + dy - 1 .. c.imag.to_i + dy + 1 ] = RGB 255, 0, 0
        end
        result.show
      end
      

      【讨论】:

        猜你喜欢
        • 2016-05-10
        • 1970-01-01
        • 1970-01-01
        • 2018-02-12
        • 1970-01-01
        • 2021-05-29
        • 1970-01-01
        • 2012-11-02
        • 1970-01-01
        相关资源
        最近更新 更多