【问题标题】:Rudimentary Computer Vision Techniques for Python Bot.Python Bot 的基本计算机视觉技术。
【发布时间】:2016-07-30 23:53:46
【问题描述】:

在完成计算机视觉书籍的几章后,我决定应用这些方法为游戏创建一些原始机器人。我选择了几乎没有动态的Fling,我需要做的就是找到球。球可能有 5 种不同的颜色,也可以指向 4 个方向中的任何一个(取决于眼睛的位置)。我裁剪了场地中的每个方块,这样我就可以检查每个方块是否包含球。我的问题是我无法正确找到球。

我的第一次尝试是跟随。我对每个球的 RGB 颜色求和并得到 [R, G, B] 数组。然后我对字段中每个块的 RGB 颜色求和。如果块的数组与球的数组有相似的 [R, G, B] 我建议这个块有一个球。 问题是很难为“相似性”找到好的价值。甚至不同的空块在这样的总和上也有很大的不同。

其次,我尝试使用具有matchTemplate功能的openCV模块。此函数将图像与另一个源图像匹配,并与 minMaxLoc 函数一起返回一个值 maxLoc。如果 maxLoc 接近 1,则图像可能在源图像中。我做了所有可能的球变化(总共20个),并在整个场地上传球。此功能运行良好,但不幸的是,它有时会错过球场上的一些球,或者为一个球分配两种不同类型的球(比如绿色和黄色)。我试图通过匹配球而不是整个场地而是每个街区来改进这个过程(这种方法的优点是它检查每个街区并且应该检测场地中正确数量的球,当与整个场地匹配时,每个场地只给出一个位置球的颜色。如果有两个相同颜色的球匹配模板丢失关于第 2 个球的信息)。令人惊讶的是,它仍然有误报\正数。

可能有更简单的方法来解决这个问题(也许是一个我还不知道的库),但现在我找不到。欢迎任何建议。

【问题讨论】:

  • 你有完全匹配的背景(绿场)?
  • 我认为您可能会通过引入外部计算机视觉库来过度复杂化。为了区分球和非球,我会更多地尝试特定任务的像素旋转。除了平均颜色通道值之外,我认为考虑过渡的清晰度会很有用(例如,草到球和球到眼睛应该很容易检测到)。一旦你确定你有一个球,通过在中心采样像素(并与已知的球颜色进行比较),应该很容易找到球的颜色。
  • 就特定任务的像素旋转而言,因为您可以生成“所有可能的球变体”,并且可以对图块进行像素完美对齐(球似乎总是在当你要移动时,我认为只是比较像素的百分比几乎等于参考图片中对齐的像素的百分比会让你走得很远。 (数字太小了,我预计性能在这里不太可能成为问题,即使是一个相当幼稚的解决方案。)
  • @IanMedeiros 你是说没有球的场地?
  • @svk 什么是特定于任务的像素旋转?以及如何找到过渡?

标签: python opencv computer-vision bots


【解决方案1】:

就颜色而言,这些球看起来非常不同。您最初描述的问题似乎与图像中出现的一些更精细、随机的细节有关——尤其是在背景和球的不同阴影/姿势中。

在此基础上,我想说您可以通过应用一组预处理步骤来“折叠”图像中的颜色范围,从而显着简化任务。

实现准确的颜色分割有许多更原则的方法(更正式地说,这是您想要实现的) - 但从更务实的角度来看,这里有一些快速的' n'dirty hack。

因此,例如,我们可以先对图像进行平滑处理以减少高频分量...

然后,转换为 标准化的 RGB 表示...

之前,最后用mean shift filtering step 对其进行分色...

这是 Python 中的代码,使用 OpenCV 绑定,按顺序执行所有这些操作:

import cv 

# get orginal image
orig = cv.LoadImage('fling.png') 

# show original 
cv.ShowImage("orig", orig)

# blur a bit to remove higher frequency variation
cv.Smooth(orig,orig,cv.CV_GAUSSIAN,5,5)

# normalise RGB
norm = cv.CreateImage(cv.GetSize(orig), 8, 3) 
red = cv.CreateImage(cv.GetSize(orig), 8, 1) 
grn = cv.CreateImage(cv.GetSize(orig), 8, 1) 
blu = cv.CreateImage(cv.GetSize(orig), 8, 1) 
total = cv.CreateImage(cv.GetSize(orig), 8, 1) 
cv.Split(orig,red,grn,blu,None)
cv.Add(red,grn,total)
cv.Add(blu,total,total)
cv.Div(red,total,red,255.0)
cv.Div(grn,total,grn,255.0)
cv.Div(blu,total,blu,255.0)
cv.Merge(red,grn,blu,None,norm)
cv.ShowImage("norm", norm)

# posterize simply with mean shift filtering
post = cv.CreateImage(cv.GetSize(orig), 8, 3) 
cv.PyrMeanShiftFiltering(norm,post,20,30)
cv.ShowImage("post", post)

【讨论】:

  • 最后一张照片看起来棒极了。
【解决方案2】:

您的任务在几个方面比您发现的通用计算机视觉算法的设计目标更简单:您确切地知道要寻找什么,并且确切地知道在哪里寻找它。因此,我认为涉及外部库是不必要的复杂化,除非您已经熟悉它并且可以有效地将其用作解决您自己问题的工具。在这篇文章中,我将只使用 PIL。

首先,将任务分为两个更简单的任务:

  • 给定一块瓷砖,确定那里是否有球。
  • 给定一个我们非常确定有球的图块,确定球的颜色。

第二个任务应该很简单,我不会在这里花时间。基本上,在球的主要颜色可见的一些像素中取样,并将找到的颜色与已知的球颜色进行比较。

那么让我们看看第一个任务。

首先,请注意球不会延伸到瓷砖的边缘。因此,您可以通过沿图块边缘的像素采样来找到图块的背景相当有代表性的样本,无论那里是否有球。

一个简单的方法是将图块中的每个像素与该图块背景样本进行比较,并获得某种衡量标准,判断它通常是相似的(无球)还是不同的(球)。

以下是执行此操作的一种方法。这里使用的基本方法是计算背景像素的平均值和标准差——分别针对红色、绿色和蓝色通道。对于每个像素,我们然后计算我们与每个通道的平均值的标准偏差数。我们将此值用于最不相似的渠道作为我们的不同程度的衡量标准。

import Image
import math

def fetch_pixels(col, row):
    img = Image.open( "image.png" )
    img = img.crop( (col*32,row*32,(col+1)*32,(row+1)*32) )
    return img.load()

def border_pixels( a ):
    rv = [ a[x,y] for x in range(32) for y in (0,31) ]
    rv.extend( [ a[x,y] for x in (0,31) for y in range(1,31) ] )
    return rv

def mean_and_stddev( xs ):
    mean = float(sum( xs )) / len(xs)
    dev = math.sqrt( float(sum( [ (x-mean)**2 for x in xs ] )) / len(xs) )
    return mean, dev

def calculate_deviations(cols = 7, rows = 8):
    outimg = Image.new( "L", (cols*32,rows*32) )
    pixels = outimg.load()
    for col in range(cols):
        for row in range(rows):
            rv = calculate_deviations_for( col, row, pixels )
            print rv
    outimg.save( "image_output.png" )

def calculate_deviations_for( col, row, opixels ):
    a = fetch_pixels( col, row )
    border = border_pixels( a )
    bru, brd = mean_and_stddev( map( lambda x : x[0], border ) )
    bgu, bgd = mean_and_stddev( map( lambda x : x[1], border ) )
    bbu, bbd = mean_and_stddev( map( lambda x : x[2], border ) )
    rv = []
    for y in range(32):
        for x in range(32):
            r, g, b = a[x,y]
            dr = (bru-r) / brd
            dg = (bgu-g) / bgd
            db = (bbu-b) / bbd
            t = max(abs(dr), abs(dg), abs(db))
            opixel = 0
            limit, span = 2.5, 8.0
            if t > limit:
                v = min(1.0, (t - limit) / span)
                print t,v
                opixel = 127 + int( 128 * v )
            opixels[col*32+x,row*32+y] = opixel
            rv.append( t )
    return (sum(rv) / float(len(rv)))

结果的可视化在这里:

请注意,大多数非球像素是纯黑色的。现在应该可以通过简单地计算黑色像素来确定是否存在球。 (或者更可靠的是:计算最大的单个非黑色像素块的大小。)

现在,这是一种非常临时的方法,我当然不会声称它是最好的方法。 “极限”值是通过实验确定的——本质上是通过反复试验确定的。它包含在这里是为了说明我认为你应该探索的那种方法,并给你一个调整的起点。 (如果你想要一个开始试验的地方,你可以尝试让它为顶部的紫色球提供更好的结果。你能想到上面方法中的弱点可能会导致这样的结果吗?永远记住,但是,您不需要一个看起来很完美的结果,只要一个就足够了。您想要的最终答案是“有球”或“没有球”,而您只希望能够可靠地回答。)

注意:

  • 您需要确保在球完成滚动并且静止在其瓷砖中心时进行屏幕抓取。这极大地简化了问题。
  • 游戏的背景会影响问题 - 如果即将推出海洋主题或沙漠主题的关卡,您将需要测试并可能调整识别器以确保它仍然可靠地工作。
  • 覆盖比赛场地的特殊效果和/或 GUI 元素会使问题复杂化。 (例如,考虑游戏是否具有“云”或“烟雾”效果,有时会漂浮在比赛场地上。)如果不确定,您可能需要调整识别器以便能够返回“无结果” - 然后您可以稍后尝试另一个屏幕截图。您可能需要获取多个屏幕截图并对结果进行平均。
  • 我假设只有球和非球。如果后面的关卡有其他类型的对象,您将不得不进行更多实验以找出如何最好地识别这些对象。
  • 我没有使用“参考图片”方法。但是,如果您的图像包含游戏中的所有对象,并且您可以将像素与图块精确对齐,那么这可能是最可靠的方法。无需将前景与采样背景进行比较,而是将前景与一组已知的前景图像进行比较。

【讨论】:

  • 首先,感谢您详细的回答。其次,只是好奇你是如何计算一块瓷砖的尺寸的?我问是因为我很难在 Paint.net 中为原始图像找到正确的边框像素。最后也是更重要的是,正如我在最后一个函数中所理解的那样,您逐个像素地计算 dr、dg、db,它们是每种颜色的某种相似性(顺便说一句,为什么它有这种形式?我的意思是分数)而不是你比较它到最大偏差(限制),如果它不相似,你让它更亮。我不明白什么是“跨度”以及函数返回什么?
猜你喜欢
  • 2011-05-23
  • 2020-06-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-11-07
  • 1970-01-01
  • 2018-03-19
  • 2011-10-18
相关资源
最近更新 更多