【问题标题】:Fast sub-pixel laser dot detection快速亚像素激光点检测
【发布时间】:2009-07-20 14:08:19
【问题描述】:

我正在使用 XNA 构建一个项目,在该项目中,我可以使用 LCD 投影仪和单色相机在墙上绘制“涂鸦”,该相机经过过滤后只能看到手持激光点指针。我想使用任意数量的激光笔——此时并不真正关心区分它们。

墙是 10' x 10',相机只有 640x480,所以我尝试使用样条曲线进行亚像素测量,如下所述:tpub.com

相机以 120fps(8 位)运行,所以我向大家提出的问题是找到亚像素激光点中心的最快方法。目前,在进行样条插值之前,我正在使用蛮力 2D 搜索来查找图像上最亮的像素(0 - 254)。这种方法不是很快,而且每一帧对计算机的时间都比它们输入的时间要长。

编辑:澄清一下,最后我的相机数据由表示像素亮度的二维字节数组表示。

我想做的是使用 XNA 着色器为我处理图像。那实用吗?据我了解,确实没有办法在像素着色器中保留持久变量,例如运行总计、平均值等。

但是为了争论,假设我使用蛮力找到了最亮的像素,然后使用 texcoords 将样条曲线的它们及其相邻像素存储到 X 个顶点中。那么使用 HLSL 使用 texcoords 计算样条曲线是否实用?

我也愿意接受我的 XNA 框之外的建议,无论是 DX10/DX11,也许是某种 FPGA 等。我只是对以这种方式处理数据的方式并没有太多经验。我想如果他们可以在使用 2 节 AA 电池的 Wii-Mote 上做这样的事情,那么我可能会走错路。

有什么想法吗?

【问题讨论】:

  • 什么是慢,做扫描还是做样条插值?
  • 样条插值是迄今为止最慢的组件,具体取决于我评估样条的增量。在这种情况下,我想要 0.1px 的精度。
  • 我应该补充一点,如果我有 20 个激光器,则样条计算在 CPU 方面将变得非常昂贵。
  • 酷。这对于任何数量的激光器都是完全可行的。实时显示粗略版本。一次从粗略到更高分辨率的屏幕矩形线,或者在绘制完成后。
  • 如果您有投影仪,则可以投影您正在拍摄的同一区域,只要颜色不同并且您将输出过滤为不可见。

标签: c# visual-studio-2008 xna


【解决方案1】:

如果暴力破解是指独立查看每个像素,那么它基本上是唯一的方法。无论您想对图像做什么,都必须扫描所有图像像素。虽然您可能不需要找到最亮的像素,但您可以按颜色过滤图像(例如:如果您使用红色激光)。这很容易使用 HSV 彩色编码图像完成。如果您正在寻找一些更快的算法,请尝试 OpenCV。它已经针对图像处理进行了一次又一次的优化,您可以通过包装器在 C# 中使用它:

[http://www.codeproject.com/KB/cs/Intel_OpenCV.aspx][1]

OpenCV 还可以帮助您轻松找到点中心并跟踪每个点。

您使用 120fps 相机有什么原因吗?你知道人眼只能看到大约 30fps 吗?我猜它是跟随非常快速的激光运动...您可能要考虑降低它,因为 120fps 的实时处理将很难实现。

【讨论】:

    【解决方案2】:

    运行 640*480 字节以找到最高字节应该在一毫秒内运行。即使在慢速处理器上。无需走着色器的路线。

    我建议优化您的循环。 例如:这真的很慢(因为它对每个数组查找进行乘法运算):

    byte highest=0;
    foundX=-1, foundY=-1;
    for(y=0; y<480; y++)
    {
        for(x=0; x<640; x++)
        {
            if(myBytes[x][y] > highest)
            {
                highest = myBytes[x][y];
                foundX = x;
                foundY = y;
            }
        }
    }
    

    这要快得多:

    byte [] myBytes = new byte[640*480];
    //fill it with your image
    
    byte highest=0;
    int found=-1, foundX=-1, foundY=-1;
    int len = 640*480;
    for(i=0; i<len; i++)
    {
        if(myBytes[i] > highest)
        {
            highest = myBytes[i];
            found = i;
        }
    }
    if(found!=-1)
    {
        foundX = i%640;
        foundY = i/640;
    }
    

    这不是我的想法,非常抱歉出现错误;^)

    【讨论】:

      【解决方案3】:

      如果您想要亚像素精度,您需要处理一些相当复杂的数学运算。我认为this paper 是值得考虑的事情。不幸的是,您必须付费才能使用该网站查看它。如果您可以访问合适的图书馆,他们可能会为您获取它。

      原始帖子中的链接建议对每个轴进行 1000 次样条计算 - 它独立处理 x 和 y,这对于圆形图像是可以的,但如果图像是倾斜的椭圆,则有点偏离。您可以使用以下方法来获得合理的估计:

      xc = sum (xn.f(xn)) / sum (f(xn子>))

      其中 xc 是平均值,xn 是沿 x 轴的 a 点,f(xn) 是xn 处的值。所以为此:

                *
             *  *
             *  *
             *  *
             *  *
             *  *
             *  *  *
          *  *  *  *
          *  *  *  *
       *  *  *  *  *  *
      ------------------
       2  3  4  5  6  7 
      

      给予:

      sum (xn.f(xn)) = 1 * 2 + 3 * 3 + 4 * 9 + 5 * 10 + 6 * 4 + 7 * 1

      总和 (f(xn)) = 1 + 3 + 9 + 10 + 4 + 1

      xc = 128 / 28 = 4.57

      并在 y 轴上重复。

      【讨论】:

      【解决方案4】:

      蛮力是唯一真正的方法,但是您使用着色器的想法很好 - 您将从 CPU 中卸载蛮力检查,它只能同时查看少量像素(大约 1每个核心),到 GPU,它可能有 100 多个哑核(管道),可以同时比较像素(您的算法可能需要稍作修改才能与 GPU 的 1 指令多核排列一起工作)。

      我看到的最大问题是您能否足够快地将数据移至 GPU。

      【讨论】:

      • 如何在 GPU 中跟踪帧上的单个最高像素?我是否理解正确,因为哑核无法以一种让它们将像素与之前最亮像素进行比较的方式共享内存?
      • GPU 可以被认为是为数学运算优化的非常简单的 CPU 的大集合。最大的区别在于指令解码器已被剥离——单个控制器将相同的指令发送到每个哑核,在那里它在该核的数据片段上执行。 IF 语句是通过两次运行相同的代码来执行的——而不是分支,不匹配条件的核心会停止。然后再次发送相同的数据,但比较指令相反。
      • 在您的情况下,您无法与 immediate 之前的最亮像素进行比较,因为(假设)128 个内核中的任何一个都可能找到了更亮的像素。相反,尝试让每个核心只专注于它自己的行 - 找到行中最亮的像素(同时处理 128 行)。一旦图像中的线条被有效地总结为该线条中最亮的像素,您就可以以串行方式找到整体最亮的像素。
      【解决方案5】:

      要考虑的另一个优化:如果您正在绘图,则指针的当前位置可能接近指针的最后一个位置。记住帧之间指针的最后记录位置,并且只扫描靠近该位置的区域......比如说一个 1'x1' 区域。仅当在该区域中未找到指针时,您才应扫描整个表面。

      显然,在您的程序扫描速度与在相机“丢失”指针并不得不进入缓慢的全图像扫描之前移动鼠标的速度之间需要权衡取舍.稍作试验可能会发现最佳值。

      顺便说一句,很酷的项目。

      【讨论】:

      • 为什么只有 1x1。为什么不增加大小 1^2。因此,如果在 1x1 中找不到,它将检查 2x2、4x4 等。稍加注意,您就可以跨过已经检查过的区域。
      • 我也想过这个问题。但是他可以拥有多个激光器,所以无论如何他都必须查看每个像素。激光可以在没有通知的情况下打开或重新进入该区域。
      【解决方案6】:

      将相机稍微偏离焦点,然后对中性样本进行 bitblt。您可以快速扫描非 0 值的行。此外,如果您使用 8 位并一次拾取 4 个字节,您可以更快地处理图像。正如其他人指出的那样,您可能会降低帧速率。如果您的保真度低于生成的图像,那么高扫描率就没有什么意义了。

      (如果你有一个繁忙的表面,轻微的失焦相机将有助于获得最亮的点并减少误报......当然假设你不是在拍摄光滑/平坦的表面)

      【讨论】:

        【解决方案7】:

        从黑色输出缓冲区开始。暂时忘掉亚像素吧。每一帧,每一像素,都这样做:

        outbuff=max(outbuff,inbuff);

        处理完图像后,对第三个“干净”缓冲区进行亚像素过滤。或者实时一次做一块或一行屏幕。优点:绘图的实时“粗略”视图,随手清理。

        当您从粗糙输出缓冲区转换为“干净”的第三个缓冲区时,您可以将粗糙输出清除为黑色。这让您可以一直画下去而不会放慢速度。

        通过在“粗糙”之上绘制“干净”,也许颜色略有不同,您将拥有两全其美。

        这类似于绘图程序所做的——如果你画得非常快,你会看到一个粗略的版本,然后绘图程序会在有时间时“清理”图像。


        算法上的一些cmets:

        我在这个领域看到了很多作弊。我在 Sega Genesis 模拟器上玩过 Sonic,它可以上采样。它有一些非常疯狂的算法,运行良好且速度非常快。

        实际上,您可以获得一些优势,因为您可能知道圆点的亮度和半径。

        您可以只查看每个像素及其 8 个相邻像素,然后让这 9 个像素根据其亮度“投票”确定子像素所在的位置。


        其他想法

        当您控制激光笔时,您的手不太准确。尝试每 10 帧左右获取所有点,确定哪些光束是哪些(基于先前的运动,并考虑新的点、关闭的激光以及进入或离开视野的点),然后只画一个高分辨率曲线。不用担心输入中的亚像素——只需将曲线绘制到高分辨率输出中即可。

        使用穿过所有控制点的 Catmull-Rom 样条。

        【讨论】:

        • 我喜欢这个想法,非常有意义。因此,可能将“干净”像素表示为包含 5x5 像素值的结构,并在单独线程的队列中通过它们。尽管如此,烧毁这个队列将是一项巨大的 CPU 任务。也许我可以将这些结构中的每一个存储在一个顶点中并在视频卡上制作样条曲线?我仍在寻找一种快速计算 5x5 2D 样条曲线的方法。感谢您的高水平建议!
        • 速度有多慢并不重要。您可以稍后进行优化。您只需观看计时器,看看每帧可以在屏幕上显示多少。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-11-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-01-17
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多