http://[blog.csdn.net/pipisorry/article/details/46564967](http://blog.csdn.net/pipisorry/article/details/46564967)
找茬游戲地址[[美女大家來找茬](http://www.3366.com/swf.html?gid=72567&type=0&needframe=0&open=1&FlashParam=&IFrameName=&basedirexp=&adid=3366_1_15168&apiflag=1&NeedOutLink=0)]
### 游戲窗口探查
下載安裝PyWin32庫(對windows接口的Python封裝)[http://sourceforge.net/projects/pywin32/](http://sourceforge.net/projects/pywin32/),但不能直接點Download圖標,不然下下來是一個Readme.txt,點“[Browse All Files](http://sourceforge.net/projects/pywin32/files/ "Browse All Files")”尋找需要的版本。
使用spy++找到窗口句柄(或者找到窗口類名lpClassName和窗口名lpWindowName)[[python指定窗口截圖-spy++用法](http://blog.csdn.net/pipisorry/article/details/46559139)]
~~~
hwnd = win32gui.FindWindow("MozillaWindowClass", "游戲全屏 - Mozilla Firefox")
if not hwnd:
print(RED, 'window not found!', DEFAULT)
else:
print(hwnd)
# hwnd = 918912 #或者直接使用句柄
~~~
**Note**:
1. **FindWindow函數:**
FindWindow這個函數檢索處理頂級窗口的類名和窗口名稱匹配指定的字符串。這個函數不搜索子窗口。
函數功能:該函數獲得一個頂層窗口的句柄,該窗口的類名和窗口名與給定的字符串相匹配。這個函數不查找子窗口。在查找時不區分大小寫。
函數型:HWND FindWindow(LPCTSTR IpClassName,LPCTSTR IpWindowName);
參數:
????IpClassName :指向一個指定了類名的空結束字符串,或一個標識類名字符串的成員的指針。如果該參數為一個成員,則它必須為前次調用theGlobafAddAtom函數產生的全局成員。該成員為16位,必須位于IpClassName的低16位,高位必須為0。
????IpWindowName:指向一個指定了窗口名(窗口標題)的空結束字符串。如果該參數為空,則為所有窗口全匹配。
????返回值:如果函數成功,返回值為具有指定類名和窗口名的窗口句柄;如果函數失敗,返回值為NULL。
[[FindWindow用法](http://blog.csdn.net/coolszy/article/details/5523486)]
[[findwindow](http://baike.baidu.com/view/373605.htm)-baike.baidu]
2. **句柄**
直接使用句柄可能的問題:
1)firefox中每個頁面tab都是同一個句柄,程序中找到的句柄對應當前tab,如果當前tab不是找茬游戲就不對了
2)使用窗口類名lpClassName和窗口名lpWindowName時如果當前tab不是找茬游戲會找不到相應句柄
上面問題一個有效的解決方法是將找茬游戲單獨在新窗口中打開
也可以使用spy++反向從句柄找到句柄對應窗口,檢查是否正確。
### 游戲圖片提取
提取圖片采用了截屏的方式,找到窗口后將窗口提到最前,再作窗口截屏。
下載安裝PIL圖形處理庫[[linux和windows下安裝python拓展包-...PIL、pythonqt...](http://blog.csdn.net/pipisorry/article/details/39902327)]
~~~
win32gui.ShowWindow(hwnd, win32con.SW_MAXIMIZE) # 強行顯示界面后才好截圖,SW_RESTORE初始大小
win32gui.SetForegroundWindow(hwnd) # 將窗口提到最前
# 裁剪得到全圖
game_rect = win32gui.GetWindowRect(hwnd)
# src_image = ImageGrab.grab(game_rect)
src_image = ImageGrab.grab((game_rect[0] + 20, game_rect[1] + 176, game_rect[2] - 523, game_rect[1] + 176 + 514))
# src_image.show()
width, hight = src_image.size
# 分別裁剪左右內容圖片
left_box = (0, 0, width // 2 + 1, hight)
right_box = (width // 2 + 1, 0, width, hight)
image_left = src_image.crop(left_box)
image_right = src_image.crop(right_box)
# image_left.show()
# image_right.show()
~~~
**Note**:
1.**win32gui.GetWindowRect函數**
```l,t,r,b ``=``win32gui.GetWindowRect(``self``.hwnd)`??? #返回圖形左、上、右、下邊界(或者說是左上、右下的坐標點)
2.**ImageGrab.grab函數**:ImageGrab是PIL的一個模塊,用于圖像的抓取。不帶參數的ImageGrab.grab()進行全屏截屏,返回一個Image對象,也可使用一個元組作為參數指定要截取的范圍(左上與右下兩點的坐標),這兩種截屏都是不帶鼠標指針的。
上面用到的坐標都為為了演示代碼簡單填的,實際上可使用了變量參數,而且要區分分辨率什么的。
還有一個ImageGrab.grabclipboard()可從系統剪貼板采集圖像。
3. 得到Image圖像后可用**show()方法**,使用系統默認的圖像查看工具打開,方便調試。
也可以用save(filename)保存成文件,對應的可以Image.open(filename)打開獲得。
4. **crop(box)方法**:參數為左上和右下標點坐標(或者說是左、上、右、下邊界)
grab得到了一個包含左右圖片的Image對象后,用crop(box)方法可裁剪得到其中指定的區域,分別拿到左右兩個游戲圖片。
**對比獲得兩圖內容不同的區域**
把兩圖裁剪成N個小圖片分別對比,左右統一區域對應的小圖片不相等則為“茬”區,問題是怎么判斷兩個圖片內容不一致?
**Image.histogram()函數:**用于得到圖像的顏色直方圖。直方圖可以表示一張圖片中各種亮度(或顏色)的數量,兩張自然圖片的直方圖基本是不一樣的,除非兩圖對稱、顏色一致但排列不一,但就算如此,將兩圖繼續分割下去,其子圖的直方圖也會不一樣。直方圖就是一種圖形到數值的轉換,對比兩圖的顏色數值就可知是否存在差異。一張用RBG顏色格式的圖像,histogram()函數將返回一個長度為768的數組,第0-255表示紅色的0-255,第256-511表色綠色的0-255,第512-767表色藍色的0-255,**數值表示該顏色像素的個數**。因此,histogram()列表所有成員之和等于改圖像的像素值 x 3。
**用來獲得兩圖比較的數值差的函數**
~~~
def channel_compare(cha_a, cha_b):
'''
比較兩個顏色通道的差異值并返回
'''
sum_a = sum([i * v for i, v in enumerate(cha_a)])
sum_b = sum([i * v for i, v in enumerate(cha_b)])
# red_a = 0
# red_b = 0
# for i in range(0, 256):
# red_a += histogram_a[i + 0] * i
# red_b += histogram_b[i + 0] * i
if sum_a + sum_b > 0:
diff_channel = abs(sum_a - sum_b) * 10000 / max(sum_a, sum_b)
return diff_channel
def image_compare(image_a, image_b):
'''
返回兩圖的差異值, 返回兩圖紅綠藍差值萬分比之和
'''
histogram_a = image_a.histogram()
histogram_b = image_b.histogram()
if len(histogram_a) != 768 or len(histogram_b) != 768:
print(RED, "get histogram error", DEFAULT)
return None
print([i * v for i, v in enumerate(histogram_a)][0:10])
diff_red = channel_compare(histogram_a[:256], histogram_b[:256])
diff_green = channel_compare(histogram_a[256:512], histogram_b[256:512])
diff_blue = channel_compare(histogram_a[512:768], histogram_b[512:768])
return diff_red, diff_green, diff_blue
~~~
**Note**:將函數返回的紅綠藍差值相加,如果超過了預定定的閥值2000,則表示該區域不同。這個計算方式有點“土”,但對這次要解決的問題很有效,就沒再繼續改進。
**把兩圖裁剪成N個小圖片分別對比**
~~~
# 將左右大圖裁剪成多個小圖分別進行對比
result = zeros((width // 10, hight // 10))
for col in range(0, width // 10):
for row in range(0, hight // 10):
clip_box = (col * 10, row * 10, (col + 1) * 10, (row + 1) * 10)
clip_image_left = image_left.crop(clip_box)
clip_image_right = image_right.crop(clip_box)
clip_diff = image_compare(clip_image_left, clip_image_right)
if sum(clip_diff) > 2000:
result[row][col] = 1
~~~
**Note**:大圖是780*520,分隔成10x10的小塊,定義一個78*52的二位數組存儲結果,分別比較后將差值大于閥值的數組區域標記為1.
**在游戲上標記兩邊不同的區域**
可以使用PyWin32的函數,獲得游戲窗口句柄后直接在上面繪制,但要熟悉Windows編程,解決游戲自身重繪后將我的標記擦除的問題。
也可以使用Qt。下面用Qt創建了一個和游戲大小一樣透明的QWidget窗口,疊加在游戲窗口上,用遮罩來繪制標記。標記數據已記錄在result數組中,在指定的位置繪制一個方格則表示該區域左右不同,要注意兩個方格間的邊界不要繪制,避免格子太多干擾了游戲。除標記外,還繪制了兩個按鈕來觸發對比與擦除。
**Note**:
1. 這里沒有替換變量,清楚算法就行。
2. The **QPixmap **class is an off-screen image representation that canbe used as a paint device.
from:[http://blog.csdn.net/pipisorry/article/details/46564967](http://blog.csdn.net/pipisorry/article/details/46564967)
ref:[PIL Document](http://www.pythonware.com/library/pil/handbook/index.htm)