在图像中找到斑马纹图案(从照片中检测结构光条纹中心线)


12

我在一个项目上工作,该项目将条纹投射在主体上,并拍摄了照片。任务是找到条纹的中心线,这些中心线在数学上代表条纹平面与对象表面之间的3D交点曲线。

该照片是PNG(RGB),以前的尝试是使用灰度,然后使用差异阈值来获得黑白的“斑马纹”摄影,从中可以轻松找到每个条纹的每个像素列的中点。问题在于,通过阈值化并通过获取离散像素列的平均高度,我们会遇到一些精度损失和量化问题,这是根本不希望的。

通过查看图像,我的印象是,如果通过某种统计扫描方法直接从非阈值图像(RGB或灰度)中检测到中心线,则中心线可能会更连续(更多点)和更平滑(未量化) (无论是泛洪/迭代卷积)。

下面是一个实际的示例图像:

在此处输入图片说明

任何建议将不胜感激!


这很有趣。但是,顺便说一下,我正在做一些使用色带检测3d对象的研究。由于使用色带,因此很容易从投影机中找到每个色带的对应关系。因此使用三角学可以计算3d信息。如果颜色相同,如何找到对应关系?我想您的项目也涉及3d重建?

@johnyoung:请不要添加评论作为答案。我意识到您需要信誉才能发表评论,但是请避免使用当前的做法。我建议您问自己(相关)的问题或回答其他人的问题以增加您的代表。
Peter K.

很抱歉,还有一个问题而不是给出答案,在相移方法中,我们计算投影图像中每个像素的相位,但是在这里为什么我们需要找出条纹的中心线,可能是我的问题很愚蠢但我不知道否,所以请给我打电话确切的原因。U给出答案后可以删除我的问题

这些是不同的方法。我正在通过投影一系列白色条纹来建模一系列几何平面(每个条纹在3D空间中形成一个“平面”)。因此,我需要找到条纹的中心线,因为平面没有厚度。当然可以执行相移分析,但是有一个问题:我的投影是二值的(黑白条纹交替),强度没有正弦变化,因此我无法执行相移(并且目前不需要) )。
heltonbiker 2014年

Answers:


13

我建议采取以下步骤:

  1. 找到一个阈值以将前景与背景分开。
  2. 对于二进制图像中的每个斑点(一个斑马条纹),对于每个斑点x,找到方向上的加权中心(按像素强度)y
  3. 可能会平滑y值以消除噪音。
  4. (x,y)通过拟合某种曲线来连接点。本文可能会为您提供帮助。我认为您也可以拟合高级多项式,尽管更糟。

这是一个Matlab代码,显示了步骤1,2和4。我跳过了自动阈值选择。相反,我选择了manual th=40

这些是通过找到每列的加权平均值而找到的曲线: 在此处输入图片说明

这些是拟合多项式后的曲线: 在此处输入图片说明

这是代码:

function Zebra()
    im = imread('http://i.stack.imgur.com/m0sy7.png');
    im = uint8(mean(im,3));

    th = 40;
    imBinary = im>th;
    imBinary = imclose(imBinary,strel('disk',2));
    % figure;imshow(imBinary);
    labels = logical(imBinary);
    props =regionprops(labels,im,'Image','Area','BoundingBox');

    figure(1);imshow(im .* uint8(imBinary));
    figure(2);imshow(im .* uint8(imBinary));

    for i=1:numel(props)
        %Ignore small ones
        if props(i).Area < 10
            continue
        end
        %Find weighted centroids
        boundingBox = props(i).BoundingBox;
        ul = boundingBox(1:2)+0.5;
        wh = boundingBox(3:4);
        clipped = im( ul(2): (ul(2)+wh(2)-1), ul(1): (ul(1)+wh(1)-1) );
        imClip = double(props(i).Image) .* double(clipped);
        rows = transpose( 1:size(imClip,1) );
        %Weighted calculation
        weightedRows  = sum(bsxfun(@times, imClip, rows),1) ./ sum(imClip,1);
        %Calculate x,y
        x = ( 1:numel(weightedRows) ) + ul(1) - 1;
        y = ( weightedRows ) + ul(2) - 1;
        figure(1);
        hold on;plot(x,y,'b','LineWidth',2);
        try %#ok<TRYNC>
            figure(2);
            [xo,yo] = FitCurveByPolynom(x,y);
            hold on;plot(xo,yo,'g','LineWidth',2);
        end
        linkaxes( cell2mat(get(get(0,'Children'),'Children')) )
    end        
end

function [xo,yo] = FitCurveByPolynom(x,y)
   p = polyfit(x,y,15); 
   yo = polyval(p,x);
   xo = x;
end

我发现这很有趣。我使用Python,但是无论如何我都必须研究所有这些的原理。作为独立评论,我倾向于不执行经典的图像处理(直接在诸如uint8数组之类的量化图像容器上),而是在应用操作之前将所有内容作为浮点数组加载到内存中。另外,我对图像下半部分的结果感到惊讶,蓝线没有沿着预期的边缘中线行进(...)。现在,谢谢,我一得到结果就会给我一些反馈!
heltonbiker 2012年

@heltonbiker,请检查更新的答案。您对浮点数是正确的,当我转换为时,就使用了它double。关于下半部分的结果,我需要检查一下,这可能是软件错误
Andrey Rubshtein 2012年

1
@heltonbiker,完成了。这确实是与基于1的索引相关的错误。
Andrey Rubshtein 2012年

太好了!的确如此。使用这种技术,就我的目的而言,不仅不需要平滑,而且会造成危害。非常感谢您的关注!
heltonbiker 2012年

3

我不会使用RGB图像。彩色图像通常是通过在相机传感器上放置“拜耳滤镜”来制作的,通常会降低分辨率。

如果您使用灰度图像,我认为您所描述的步骤(将“斑马”图像二值化,找到中线)是一个很好的开始。最后一步,我会

  • 取中线中的每个点
  • 取上下“斑马线”中像素的灰度值
  • 使用最小均方将抛物线拟合到这些灰度值
  • 该抛物线的顶点是对中线位置的改进估计

不错的想法。我计划沿每个像素列的峰值使用某种抛物线或样条曲线,但是我仍然想知道是否应该检查像素列还是沿该线检查像素“区域” ...还要再等一会儿更多答案。现在谢谢!
heltonbiker 2012年

@heltonbiker-作为快速测试,请仅使用绿色通道。通常,彩色传感器上的绿色像素数量是红色像素的2倍,而红色像素和蓝色像素的绿色像素更少
马丁·贝克特

@MartinBeckett感谢您的关注,我已经分析了每个频道,确实绿色的频道似乎比红色的频道要解析得多。但是,绘制每个通道的垂直横截面强度值时,“条纹图案”似乎在通道之间变化不大,我目前在转换为灰度时将它们均匀混合。即使如此,我仍然计划研究通道之间的最佳线性组合以获得最佳对比度结果,或者获取已经为灰度的图像。再次感谢!
heltonbiker 2012年

3

通过将问题建模为“路径优化问题”,这是您的问题的另一种解决方案。尽管它比简单的二值化然后弯曲的解决方案更为复杂,但在实践中却更为强大。

从非常高的层次来看,我们应该将此图像视为图形,其中

  1. 每个图像像素都是该图上的一个节点

  2. 每个节点都连接到其他一些节点(称为邻居),并且此连接定义通常称为此图的拓扑。

  3. 每个节点都有权重(功能,成本,能源或任何您想要称呼的权重),反映出该节点处于我们正在寻找的最佳中心线上的可能性。

只要我们可以对这种可能性进行建模,那么您找到“条纹的中心线”的问题就变成了在图形上找到局部最优路径的问题,这可以通过动态编程(例如维特比算法)有效地解决。

以下是采用这种方法的一些优点:

  1. 您的所有结果都是连续的(与阈值方法不同,阈值方法可能会将一条中心线弄成碎片)

  2. 构造此类图有很多自由度,可以选择不同的功能以及图拓扑。

  3. 在路径优化方面,您的结果是最佳的

  4. 您的解决方案将对噪声更具鲁棒性,因为只要噪声在所有像素之间平均分配,这些最佳路径便会保持稳定。

这是上述想法的简短演示。由于我不使用任何先验知识来指定可能的起始节点和结束节点,因此我仅对每个可能的起始节点进行解码。 解码的维特比路径

对于模糊结局,是由于我们正在寻找每个可能的结局节点的最佳路径这一事实造成的。结果,尽管对于位于黑暗区域中的某些节点,突出显示的路径仍然是其局部最优路径。

对于模糊路径,可以在找到后对其进行平滑处理,也可以使用一些平滑后的特征代替原始强度。

通过更改起点和终点可以恢复部分路径。

修剪这些不需要的局部最优路径将很困难。因为在维特比解码之后,我们具有所有路径的可能性,并且您可能会使用各种先验知识(例如,我们看到,对于共享同一源的人,我们只需要一条最佳路径就可以了。)

有关更多详细信息,请参阅本文。

 Wu, Y.; Zha, S.; Cao, H.; Liu, D., & Natarajan, P.  (2014, February). A Markov Chain Line Segmentation Method for Text Recognition. In IS&T/SPIE 26th Annual Symposium on Electronic Imaging (DRR), pp. 90210C-90210C.

这是用于制作上图的一小段python代码。


import cv2
import numpy as np
from matplotlib import pyplot
# define your image path
image_path = ;
# read in an image
img = cv2.imread( image_path, 0 );
rgb = cv2.imread( image_path, -1 );

# some feature to reflect how likely a node is in an optimal path
img = cv2.equalizeHist( img ); # equalization
img = img - img.mean(); # substract DC
img_pmax = img.max(); # get brightest intensity
img_nmin = img.min(); # get darkest intensity
# express our preknowledge
img[ img > 0 ] *= +1.0  / img_pmax; 
img[ img = 1 :
    prev_idx = vt_path[ -1 ].astype('int');
    vt_path.append( path_buffer[ prev_idx, time ] );
    time -= 1;
vt_path.reverse();    
vt_path = np.asarray( vt_path ).T;

# plot found optimal paths for every 7 of them
pyplot.imshow( rgb, 'jet' ),
for row in range( 0, h, 7 ) :
    pyplot.hold(True), pyplot.plot( vt_path[row,:], c=np.random.rand(3,1), lw = 2 );
pyplot.xlim( ( 0, w ) );
pyplot.ylim( ( h, 0 ) );

这是一个非常有趣的方法。我承认“图形”的主题一直困扰着我,直到最近(在同一项目上)我只能使用图形解决另一个问题。在“理解”之后,我意识到了这些最​​短路径算法的强大功能。您的想法很有趣,如果有需要/机会,我会重新实现这一想法。非常感谢你。
heltonbiker 2014年

至于您当前的结果,根据我的经验,在构建图形之前,最好先使用高斯和/或中值滤波器对图像进行平滑处理。这样可以使线条更平滑(也更正确)。同样,一种可能的技巧是扩展邻域以允许在两个或更多像素(达到给定限制,例如8或10个像素)上进行“直接跳跃”。当然应该选择合适的成本函数,但是我认为这很容易调整。
heltonbiker 2014年

哦是的 我只是选择了一些东西,您肯定可以使用其他拓扑和能量函数。实际上,此框架也是可训练的。特别是,您从原始强度开始,解码以获得最佳路径,仅以高置信度拾取那些最佳节点,并以此方式获得“标记数据”。借助这小部分自动标记的数据,您可以学习许多有用的知识。
陷阱

3

我认为我应该发布我的答案,因为它与其他方法有些不同。我在Matlab中尝试过。

  • 对所有通道求和并创建图像,因此所有通道均被加权
  • 对图像进行形态学闭合和高斯滤波
  • 对于结果图像的每一列,找到局部最大值并构建图像
  • 找到该图像的连接组件

我在这里看到的一个缺点是,这种方法在条带的某些方向上效果不佳。在这种情况下,我们必须纠正其方向并应用此过程。

这是Matlab代码:

im = imread('m0sy7.png');
imsum = sum(im, 3); % sum all channels
h = fspecial('gaussian', 3);
im2 = imclose(imsum, ones(3)); % close
im2 = imfilter(im2, h); % smooth
% for each column, find regional max
mx = zeros(size(im2));
for c = 1:size(im2, 2)
    mx(:, c) = imregionalmax(im2(:, c));
end
% find connected components
ccomp = bwlabel(mx);

例如,如果采用图像的中间列,则其配置文件应如下所示:(蓝色为配置文件。绿色为局部最大值) 中档和局部最大值

包含所有列的局部最大值的图像如下所示: 在此处输入图片说明

这是连接的组件(尽管一些条纹被折断了,但大多数都变成了连续区域):

在此处输入图片说明


这实际上是我们现在正在做的,唯一的区别是如何为每个像素列找到局部最大值:我们使用抛物线插值法来找到抛物线的确切顶点,该顶点穿过具有最大值的像素及其上下相邻像素。这使s可以在“像素之间”,从而更好地表示线条的微妙平滑度。感谢您的回答!
heltonbiker 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.