あなたのタスクは、一般的なコンピューター ビジョン アルゴリズムが設計されたものよりもいくつかの点で単純です。何を探すべきか、どこを探すべきかを正確に知っているのです。そのため、外部ライブラリを使用することは、既にそれに慣れていて、自分の問題を解決するためのツールとして効果的に使用できる場合を除き、不要な複雑さだと思います。この投稿では、PIL のみを使用します。
まず、タスクを 2 つの単純なタスクに区別します。
- 与えられたタイルにボールがあるかどうかを判断します。
- ボールがあると確信しているタイルが与えられた場合、ボールの色を特定します。
2 番目のタスクは単純なはずなので、ここでは時間を割きません。基本的に、ボールの主な色が見えるピクセルをいくつかサンプリングし、見つけた色を既知のボールの色と比較します。
それでは、最初のタスクを見てみましょう。
まず、ボールがタイルの端まで伸びていないことに注意してください。したがって、タイルの端に沿ってピクセルをサンプリングすることで、そこにボールがあるかどうかに関係なく、タイルの背景のかなり代表的なサンプルを見つけることができます。
処理を進める簡単な方法は、タイル内のすべてのピクセルをタイル背景のこのサンプルと比較し、一般的に類似している (ボールがない) か、類似していない (ボールがない) かの何らかの尺度を取得することです。
以下は、これを行う 1 つの方法です。ここで使用される基本的なアプローチは、背景ピクセルの平均と標準偏差を、赤、緑、青のチャネルごとに個別に計算することです。次に、すべてのピクセルについて、すべてのチャネルの平均からの標準偏差の数を計算します。非類似度の尺度として、最も類似度の低いチャネルのこの値を使用します。
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 要素は、問題を複雑にします。(たとえば、ゲームに「雲」または「煙」の効果があり、それが時々競技場に浮かぶかどうかを検討してください。) よくわからない場合は、「結果なし」を返すことができるように認識機能を微調整したい場合があります。後で別のスクリーングラブを試してください。いくつかのスクリーン グラブを取得し、結果を平均化することができます。
- ボールと非ボールしかないと仮定しました。後のレベルに他の種類のオブジェクトがある場合は、それらを最もよく認識する方法を見つけるために、さらに実験する必要があります。
- 「参照画像」アプローチは使用していません。ただし、ゲーム内のすべてのオブジェクトを含む画像があり、ピクセルをタイルに正確に配置できる場合、それが最も信頼できるアプローチになる可能性があります。前景をサンプリングされた背景と比較する代わりに、前景を一連の既知の前景画像と比較します。