機器視覺
從 Google 的無人駕駛汽車到可以識別假鈔的自動售賣機,機器視覺一直都是一個應用廣 泛且具有深遠的影響和雄偉的願景的領域。
我們將重點介紹機器視覺的一個分支:文字識別,介紹如何用一些 Python庫來識別和使用線上圖片中的文字。
我們可以很輕鬆的閱讀圖片裡的文字,但是機器閱讀這些圖片就會非常困難,利用這種人類使用者可以正常讀取但是大多數機器人都沒法讀取的圖片,驗證碼 (CAPTCHA)就出現了。驗證碼讀取的難易程度也大不相同,有些驗證碼比其他的更加難讀。
將影像翻譯成文字一般被稱為光學文字識別(Optical Character Recognition, OCR)。可以實現OCR的底層庫並不多,目前很多庫都是使用共同的幾個底層 OCR 庫,或者是在上面 進行定製。
ORC庫概述
在讀取和處理影像、影像相關的機器學習以及建立影像等任務中,Python 一直都是非常出色的語言。雖然有很多庫可以進行影像處理,但在這裡我們只重點介紹:Tesseract
Tesseract
Tesseract 是一個 OCR 庫,目前由 Google 贊助(Google 也是一家以 OCR 和機器學習技術聞名於世的公司)。Tesseract 是目前公認最優秀、最精確的開源 OCR 系統。 除了極高的精確度,Tesseract 也具有很高的靈活性。它可以通過訓練識別出任何字型,也可以識別出任何 Unicode 字元。
安裝Tesseract
Windows 系統
下載可執行安裝檔案https://code.google.com/p/tes…安裝。
Linux 系統
可以通過 apt-get 安裝: $sudo apt-get tesseract-ocr
Mac OS X系統
用 Homebrew(http://brew.sh/)等第三方庫可以很方便地安裝 brew install tesseract
要使用 Tesseract 的功能,比如後面的示例中訓練程式識別字母,要先在系統中設定一 個新的環境變數 $TESSDATA_PREFIX,讓 Tesseract 知道訓練的資料檔案儲存在哪裡,然後搞一份tessdata資料檔案,放到Tesseract目錄下。
在大多數 Linux 系統和 Mac OS X 系統上,你可以這麼設定: $export TESSDATA_PREFIX=/usr/local/share/Tesseract
在 Windows 系統上也類似,你可以通過下面這行命令設定環境變數: #setx TESSDATA_PREFIX C:Program FilesTesseract OCRTesseract
安裝pytesseract
Tesseract 是一個 Python 的命令列工具,不是通過 import 語句匯入的庫。安裝之後,要用 tesseract 命令在 Python 的外面執行,但我們可以通過 pip 安裝支援Python 版本的 Tesseract庫:
pip install pytesseract
處理給規範的文字
你要處理的大多數文字都是比較乾淨、格式規範的。格式規範的文字通常可以滿足一些需求,不過究竟什麼是“格式混亂”,什麼算“格式規範”,確實因人而異。 通常,格式規範的文字具有以下特點:
使用一個標準字型(不包含手寫體、草書,或者十分“花哨的”字型) • 雖然被影印或拍照,字型還是很清晰,沒有多餘的痕跡或汙點
排列整齊,沒有歪歪斜斜的字
沒有超出圖片範圍,也沒有殘缺不全,或緊緊貼在圖片的邊緣
文字的一些格式問題在圖片預處理時可以進行解決。例如,可以把圖片轉換成灰度圖,調 整亮度和對比度,還可以根據需要進行裁剪和旋轉(詳情請關注影像與訊號處理),但是,這些做法在進行更具擴充套件性的 訓練時會遇到一些限制。
格式規範文字的理想示例
通過下面的命令執行 Tesseract,讀取檔案並把結果寫到一個文字檔案中: `tesseract test.jpg text
cat text.txt 即可顯示結果。
識別結果很準確,不過符號^和*分別被表示成了雙引號和單引號。大體上可以讓你很舒服地閱讀。
通過Python程式碼實現
import pytesseract
from PIL import Image
image = Image.open(`test.jpg`)
text = pytesseract.image_to_string(image)
print text
執行結果:
This is some text, written in Arial, that will be read by
Tesseract. Here are some symbols: !@#$%”&*()
對圖片進行閾值過濾和降噪處理(瞭解即可)
很多時候我們在網上會看到這樣的圖片:
Tesseract 不能完整處理這個圖片,主要是因為圖片背景色是漸變的,最終結果是這樣:
隨著背景色從左到右不斷加深,文字變得越來越難以識別,Tesseract 識別出的 每一行的最後幾個字元都是錯的。
遇到這類問題,可以先用 Python 指令碼對圖片進行清理。利用 Pillow 庫,我們可以建立一個 閾值過濾器來去掉漸變的背景色,只把文字留下來,從而讓圖片更加清晰,便於 Tesseract 讀取:
from PIL import Image
import subprocess
def cleanFile(filePath, newFilePath):
image = Image.open(filePath)
# 對圖片進行閾值過濾,然後儲存
image = image.point(lambda x: 0 if x<143 else 255)
image.save(newFilePath)
# 呼叫系統的tesseract命令對圖片進行OCR識別
subprocess.call(["tesseract", newFilePath, "output"])
# 開啟檔案讀取結果
file = open("output.txt", `r`)
print(file.read())
file.close()
cleanFile("text2.jpg", "text2clean.png")
通過一個閾值對前面的“模糊”圖片進行過濾的結果
除了一些標點符號不太清晰或丟失了,大部分文字都被讀出來了。Tesseract 給出了最好的 結果:
從網站圖片中抓取文字
用 Tesseract 讀取硬碟裡圖片上的文字,可能不怎麼令人興奮,但當我們把它和網路爬蟲組合使用時,就能成為一個強大的工具。
網站上的圖片可能並不是故意把文字做得很花哨 (就像餐館選單的 JPG 圖片上的藝術字),但它們上面的文字對網路爬蟲來說就是隱藏起來 了,舉個例子:
雖然亞馬遜的 robots.txt 檔案允許抓取網站的產品頁面,但是圖書的預覽頁通常不讓網路機 器人採集。
圖書的預覽頁是通過使用者觸發 Ajax 指令碼進行載入的,預覽圖片隱藏在 div 節點 下面;其實,普通的訪問者會覺得它們看起來更像是一個 Flash 動畫,而不是一個圖片文 件。當然,即使我們能獲得圖片,要把它們讀成文字也沒那麼簡單。
下面的程式就解決了這個問題:首先導航到托爾斯泰的《戰爭與和平》的大字號印刷版 1, 開啟閱讀器,收集圖片的 URL 連結,然後下載圖片,識別圖片,最後列印每個圖片的文 字。因為這個程式很複雜,利用了前面幾章的多個程式片段,所以我增加了一些註釋以讓 每段程式碼的目的更加清晰:
import time
from urllib.request import urlretrieve
import subprocess
from selenium import webdriver
#建立新的Selenium driver
driver = webdriver.PhantomJS()
# 用Selenium試試Firefox瀏覽器:
# driver = webdriver.Firefox()
driver.get("http://www.amazon.com/War-Peace-Leo-Nikolayevich-Tolstoy/dp/1427030200")
# 單擊圖書預覽按鈕 driver.find_element_by_id("sitbLogoImg").click() imageList = set()
# 等待頁面載入完成
time.sleep(5)
# 當向右箭頭可以點選時,開始翻頁
while "pointer" in driver.find_element_by_id("sitbReaderRightPageTurner").get_attribute("style"):
driver.find_element_by_id("sitbReaderRightPageTurner").click()
time.sleep(2)
# 獲取已載入的新頁面(一次可以載入多個頁面,但是重複的頁面不能載入到集合中)
pages = driver.find_elements_by_xpath("//div[@class=`pageImage`]/div/img")
for page in pages:
image = page.get_attribute("src")
imageList.add(image)
driver.quit()
# 用Tesseract處理我們收集的圖片URL連結
for image in sorted(imageList):
# 儲存圖片
urlretrieve(image, "page.jpg")
p = subprocess.Popen(["tesseract", "page.jpg", "page"], stdout=subprocess.PIPE,stderr=subprocess.PIPE)
f = open("page.txt", "r")
p.wait() print(f.read())
和我們前面使用 Tesseract 讀取的效果一樣,這個程式也會完美地列印書中很多長長的段 落,第六頁的預覽如下所示:
6
"A word of friendly advice, mon
cher. Be off as soon as you can,
that`s all I have to tell you. Happy
he who has ears to hear. Good-by,
my dear fellow. Oh, by the by!" he
shouted through the doorway after
Pierre, "is it true that the countess
has fallen into the clutches of the
holy fathers of the Society of je-
sus?"
Pierre did not answer and left Ros-
topchin`s room more sullen and an-
gry than he had ever before shown
himself.
但是,當文字出現在彩色封面上時,結果就不那麼完美了:
WEI` nrrd Peace
Len Nlkelayevldu Iolfluy
Readmg shmdd be ax
wlnvame asnossxble Wenfler
an mm m our cram: Llhvary
- Leo Tmsloy was a Russian rwovelwst
I and moval phflmopher med lur
A ms Ideas 01 nonviolenx reswslance m 5 We range 0, "and"
如果想把文字加工成普通人可以看懂的 效果,還需要花很多時間去處理。
下一節將介紹另一種方法來解決文字混亂的問題,尤其是當你願意花一點兒時間訓練 Tesseract 的時候。
通過給 Tesseract 提供大量已知的文字與圖片對映集,經過訓練 Tesseract 就可以“學會”識別同一種字型,而且可以達到極高的精確率和準確率,甚至可以忽略圖 片中文字的背景色和相對位置等問題。
嘗試對知乎網驗證碼進行處理:
許多流行的內容管理系統即使加了驗證碼模組,其眾所周知的註冊頁面也經常會遭到網路 機器人的垃圾註冊。
那麼,這些網路機器人究,竟是怎麼做的呢?既然我們已經,可以成功地識別出儲存在電腦上 的驗證碼了,那麼如何才能實現一個全能的網路機器人呢?
大多數網站生成的驗證碼圖片都具有以下屬性。
它們是伺服器端的程式動態生成的圖片。驗證碼圖片的 src 屬性可能和普通圖片不太一 樣,比如 <img src=”WebForm.aspx?id=8AP85CQKE9TJ”>,但是可以和其他圖片一樣進行 下載和處理。
圖片的答案儲存在伺服器端的資料庫裡。
很多驗證碼都有時間限制,如果你太長時間沒解決就會失效。
常用的處理方法就是,首先把驗證碼圖片下載到硬碟裡,清理乾淨,然後用 Tesseract 處理 圖片,最後返回符合網站要求的識別結果。
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import requests
import time
import pytesseract
from PIL import Image
from bs4 import BeautifulSoup
def captcha(data):
with open(`captcha.jpg`,`wb`) as fp:
fp.write(data)
time.sleep(1)
image = Image.open("captcha.jpg")
text = pytesseract.image_to_string(image)
print "機器識別後的驗證碼為:" + text
command = raw_input("請輸入Y表示同意使用,按其他鍵自行重新輸入:")
if (command == "Y" or command == "y"):
return text
else:
return raw_input(`輸入驗證碼:`)
def zhihuLogin(username,password):
# 構建一個儲存Cookie值的session物件
sessiona = requests.Session()
headers = {`User-Agent`:`Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0`}
# 先獲取頁面資訊,找到需要POST的資料(並且已記錄當前頁面的Cookie)
html = sessiona.get(`https://www.zhihu.com/#signin`, headers=headers).content
# 找到 name 屬性值為 _xsrf 的input標籤,取出value裡的值
_xsrf = BeautifulSoup(html ,`lxml`).find(`input`, attrs={`name`:`_xsrf`}).get(`value`)
# 取出驗證碼,r後面的值是Unix時間戳,time.time()
captcha_url = `https://www.zhihu.com/captcha.gif?r=%d&type=login` % (time.time() * 1000)
response = sessiona.get(captcha_url, headers = headers)
data = {
"_xsrf":_xsrf,
"email":username,
"password":password,
"remember_me":True,
"captcha": captcha(response.content)
}
response = sessiona.post(`https://www.zhihu.com/login/email`, data = data, headers=headers)
print response.text
response = sessiona.get(`https://www.zhihu.com/people/maozhaojun/activities`, headers=headers)
print response.text
if __name__ == "__main__":
#username = raw_input("username")
#password = raw_input("password")
zhihuLogin(`xxxx@qq.com`,`ALAxxxxIME`)
值得注意的是,有兩種異常情況會導致這個程式執行失敗。第一種情況是,如果 Tesseract 從驗證碼圖片中識別的結果不是四個字元(因為訓練樣本中驗證碼的所有有效答案都必須 是四個字元),結果不會被提交,程式失敗。第二種情況是雖然識別的結果是四個字元, 被提交到了表單,但是伺服器對結果不認可,程式仍然失敗。
在實際執行過程中,第一種 情況發生的可能性大約為 50%,發生時程式不會向表單提交,程式直接結束並提示驗證碼 識別錯誤。第二種異常情況發生的概率約為 20%,四個字元都對的概率約是 30%(每個字 母的識別正確率大約是 80%,如果是五個字元都識別,正確的總概率是 32.8%)。
訓練Tesseract
大多數其他的驗證碼都是比較簡單的。例如,流行的 PHP 內容管理系統 Drupal 有一個著 名的驗證碼模組(https://www.drupal.org/projec…,可以生成不同難度的驗證碼。
那麼與其他驗證碼相比,究竟是什麼讓這個驗證碼更容易被人類和機器讀懂呢?
字母沒有相互疊加在一起,在水平方向上也沒有彼此交叉。也就是說,可以在每一個字 母外面畫一個方框,而不會重疊在一起。
圖片沒有背景色、線條或其他對 OCR 程式產生干擾的噪點。
雖然不能因一個圖片下定論,但是這個驗證碼用的字型種類很少,而且用的是 sans-serif 字型(像“4”和“M”)和一種手寫形式的字型(像“m”“C”和“3”)。
白色背景色與深色字母之間的對比度很高。
這個驗證碼只做了一點點改變,就讓 OCR 程式很難識別。
字母和資料都使用了,這會增加待搜尋字元的數量。
字母隨機的傾斜程度會迷惑 OCR 軟體,但是人類還是很容易識別的。
那個比較陌生的手寫字型很有挑戰性,在“C”和“3”裡面還有額外的線條。另外這 個非常小的小寫“m”,計算機需要進行額外的訓練才能識別。 用下面的程式碼執行 Tesseract 識別圖片:
tesseract captchaExample.png output
我們得到的結果 output.txt 是: 4N,,,C<3
訓練Tesseract
要訓練 Tesseract 識別一種文字,無論是晦澀難懂的字型還是驗證碼,你都需要向 Tesseract 提供每個字元不同形式的樣本。
做這個枯燥的工作可能要花好幾個小時的時間,你可能更想用這點兒時間找個好看的視訊 或電影看看。首先要把大量的驗證碼樣本下載到一個資料夾裡。
下載的樣本數量由驗證碼 的複雜程度決定;我在訓練集裡一共放了 100 個樣本(一共 500 個字元,平均每個字元 8 個樣本;a~z 大小寫字母加 0~9 數字,一共 62 個字元),應該足夠訓練的了。
提示:建議使用驗證碼的真實結果給每個樣本檔案命名(即4MmC3.jpg)。 這樣可以幫你 一次性對大量的檔案進行快速檢查——你可以先把圖片調成縮圖模式,然後通過檔名 對比不同的圖片。這樣在後面的步驟中進行訓練效果的檢查也會很方便。
第二步是準確地告訴 Tesseract 一張圖片中的每個字元是什麼,以及每個字元的具體位置。 這裡需要建立一些矩形定位檔案(box file),一個驗證碼圖片生成一個矩形定位檔案。一 個圖片的矩形定位檔案如下所示:
4 15 26 33 55 0
M 38 13 67 45 0
m 79 15 101 26 0
C 111 33 136 60 0
3 147 17 176 45 0
第一列符號是圖片中的每個字元,後面的 4 個數字分別是包圍這個字元的最小矩形的座標 (圖片左下角是原點 (0,0),4 個數字分別對應每個字元的左下角 x 座標、左下角 y 座標、右上角 x 座標和右上角 y 座標),最後一個數字“0”表示圖片樣本的編號。
顯然,手工建立這些圖片矩形定位檔案很無聊,不過有一些工具可以幫你完成。我很喜歡 線上工具 Tesseract OCR Chopper(http://pp19dd.com/tesseract-o…),因為它不需要 安裝,也沒有其他依賴,只要有瀏覽器就可以執行,而且用法很簡單:上傳圖片,如果要 增加新矩形就單擊“add”按鈕,還可以根據需要調整矩形的尺寸,最後把新生成的矩形 定位檔案複製到一個新檔案裡就可以了。
矩形定位檔案必須儲存在一個 .box 字尾的文字檔案中。和圖片檔案一樣,文字檔案也是用 驗證碼的實際結果命名(例如,4MmC3.box)。另外,這樣便於檢查 .box 檔案的內容和檔案的名稱,而且按檔名對目錄中的檔案排序之後,就可以讓 .box 檔案與對應的圖片檔案 的實際結果進行對比。
你還需要建立大約 100 個 .box 檔案來保證你有足夠的訓練資料。因為 Tesseract 會忽略那 些不能讀取的檔案,所以建議你儘量多做一些矩形定位檔案,以保證訓練足夠充分。如果 你覺得訓練的 OCR 結果沒有達到你的目標,或者 Tesseract 識別某些字元時總是出錯,多 建立一些訓練資料然後重新訓練將是一個不錯的改進方法。
建立完滿載 .box 檔案和圖片檔案的資料資料夾之後,在做進一步分析之前最好備份一下這 個資料夾。雖然在資料上執行訓練程式不太可能刪除任何資料,但是建立 .box 檔案用了你 好幾個小時的時間,來之不易,穩妥一點兒總沒錯。此外,能夠抓取一個滿是編譯資料的 混亂目錄,然後再嘗試一次,總是好的。
前面的內容只是對 Tesseract 庫強大的字型訓練和識別能力的一個簡略概述。如果你對 Tesseract 的其他訓練方法感興趣,甚至打算建立自己的驗證碼訓練檔案庫,或者想和全世 界的 Tesseract 愛好者分享自己對一種新字型的識別成果,推薦閱讀 Tesseract 的文件:https://github.com/tesseract-…,加油!