Python机器人的基本计算机视觉技术。

时间:2013-03-28 15:31:01

标签: python opencv computer-vision bots

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

enter image description here

我的第一次尝试是跟随。我将每个球的RGB颜色相加并得到[R,G,B]数组。然后我为场中的每个块求和RGB颜色。如果block的数组与球的数组有类似的[R,G,B],我建议这个数据块有一个球。 问题是很难找到“相似性”的良好价值。即使是不同的空块也会显着地变化。

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

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

2 个答案:

答案 0 :(得分:7)

球的颜色看起来非常明显。您最初描述的问题似乎与图像中存在的一些更精细,随机的细节有关 - 特别是在背景和球的不同阴影/姿势中。

在此基础上,我会说你可以通过应用一系列预处理步骤来显着简化任务,以便"崩溃"图像中的颜色范围。

有许多更有原则的方法可以实现准确的颜色分割(这是更正式的,你想要实现的) - 但是采取更务实的观点,这里有几个快速&# 39; n'肮脏的黑客。

因此,例如,我们最初可以平滑图像以减少更高频率的成分......

enter image description here

然后,转换为规范化RGB 表示...

enter image description here

之前,最后用mean shift filtering step ...

对其进行分色

enter image description here

这是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)

答案 1 :(得分:4)

您的任务在某些方面比您将找到的通用计算机视觉算法更简单:您确切地知道要查找的内容,并且您确切地知道在哪里寻找它。因此,我认为涉及外部库是一种不必要的复杂问题,除非您已经熟悉它并且可以有效地将其用作解决您自己问题的工具。在这篇文章中,我将只使用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)))

结果的可视化在这里:

Ad-hoc ball recognition

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

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

请注意:

  • 当球完成滚动并且仍然躺在瓷砖的中心时,你需要确保你采取了屏幕抓取。这极大地简化了问题。
  • 游戏的背景会影响问题 - 如果有海洋主题或沙漠主题级别出现,你需要测试并可能调整识别器,以确保它仍然可靠地工作。
  • 覆盖比赛场地的特效和/或GUI元素会使问题复杂化。 (例如,考虑游戏是否有“云”或“冒烟”效果,有时会浮动在比赛场地上。)你可能想调整识别器,如果不确定则能够返回“无结果” - 那么你可以稍后尝试另一个screengrab。您可能需要拍几张屏幕并对结果取平均值。
  • 我认为只有球和非球。如果以后的级别有其他类型的对象,则必须进行更多实验以了解如何最好地识别这些对象。
  • 我没有使用'参考图片'的方法。但是,如果您的图像包含游戏中的所有对象,并且您可以将像素与切片完全对齐,那么这可能是最可靠的方法。不要将前景与采样背景进行比较,而是将前景与一组已知前景图像进行比较。