從 Google 的無人駕駛汽車到能夠識別假鈔的自動售賣機,機器視覺一直都是一個應用普遍且具備深遠的影響和雄偉的願景的領域。html
咱們將重點介紹機器視覺的一個分支:文字識別,介紹如何用一些Python庫來識別和使用在線圖片中的文字。python
咱們能夠很輕鬆的閱讀圖片裏的文字,可是機器閱讀這些圖片就會很是困難,利用這種人類用戶能夠正常讀取可是大多數機器人都無法讀取的圖片,驗證碼 (CAPTCHA)就出現了。驗證碼讀取的難易程度也大不相同,有些驗證碼比其餘的更加難讀。git
將圖像翻譯成文字通常被稱爲光學文字識別(Optical Character Recognition, OCR)。能夠實現OCR的底層庫並很少,目前不少庫都是使用共同的幾個底層 OCR 庫,或者是在上面進行定製。github
在讀取和處理圖像、圖像相關的機器學習以及建立圖像等任務中,Python一直都是很是出色的語言。雖然有不少庫能夠進行圖像處理,但在這裏咱們只重點介紹:Tesseractweb
Tesseract 是一個OCR庫,目前由Google贊助(Google也是一家以OCR和機器學習技術聞名於世的公司)。Tesseract是目前公認最優秀、最精確的開源OCR系統。除了極高的精確度,Tesseract也具備很高的靈活性。它能夠經過訓練識別出任何字體,也能夠識別出任何 Unicode字符。數據庫
Windows系統
下載可執行安裝文件 tesseract-ocr 安裝。瀏覽器
Linux系統
能夠經過 apt-get 安裝: $sudo apt-get tesseract-ocr服務器
Mac OS系統
用 Homebrew等第三方庫能夠很方便地安裝 : brew install tesseract網絡
要使用 Tesseract 的功能,好比後面的示例中訓練程序識別字母,要先在系統中設置一 個新的環境變量 $TESSDATA_PREFIX,讓 Tesseract 知道訓練的數據文件存儲在哪裏,而後搞一份tessdata數據文件,放到Tesseract目錄下。session
在多數 Linux 系統和 Mac OS X 系統上,能夠這麼設置: $export TESSDATA_PREFIX=/usr/local/share/Tesseract
在 Windows 系統上,你能夠經過下面這行命令設置環境變量: #setx TESSDATA_PREFIX C:\Program Files\Tesseract OCR\Tesseract
Tesseract 是一個Python的命令行工具,不是經過import語句導入的庫。安裝以後,要用 tesseract 命令在 Python 的外面運行,但咱們能夠經過 pip3 安裝支持Python版本的 Tesseract庫:
pip install pytesseract
你要處理的大多數文字都是比較乾淨、格式規範的。格式規範的文字一般能夠知足一些需求,不過究竟什麼是「格式混亂」,什麼算「格式規範」,確實因人而異。 一般,格式規範的文字具備如下特色:
文字的一些格式問題在圖片預處理時能夠進行解決。例如,能夠把圖片轉換成灰度圖,調整亮度和對比度,還能夠根據須要進行裁剪和旋轉(詳情請關注圖像與信號處理),可是,這些作法在進行更具擴展性的訓練時會遇到一些限制。
格式規範文字的理想示例:
經過下面的命令運行 Tesseract,讀取文件並把結果寫到一個文本文件中:
tesseract test.jpg text
識別結果很準確,不過符號^和*分別被表示成了雙引號和單引號。大致上可讓你很舒服地閱讀。
import pytesseract from PIL import Image image = Image.open('test.jpg') text = pytesseract.image_to_string(image) print(text)
不少時候咱們在網上會看到這樣的圖片:
隨着背景色從左到右不斷加深,文字變得愈來愈難以識別,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("text.jpg", "textclean.png")
經過一個閾值對前面的「模糊」圖片進行過濾的結果:
用Tesseract讀取硬盤裏圖片上的文字,可能不怎麼使人興奮,但咱們把它和網絡爬蟲組合使用時,就能成爲一個強大的工具。
網站上的圖片可能並非故意把文字作得很花哨(就像餐館菜單的JPG圖片上的藝術字),但它們上面的文字對網絡爬蟲來講就是隱藏起來了,舉個例子:
雖然亞馬遜的robots.txt文件容許抓取網站的產品頁面,可是圖書的預覽頁一般不讓網絡機器人採集。
圖書的預覽頁是經過用戶觸發Ajax腳本進行加載的,預覽圖片隱藏在div節點下面;其實,普通的訪問者會以爲它們看起來更像是一個 Flash 動畫,而不是一個圖片文件。固然,即便咱們能得到圖片,要把它們讀成文字也沒那麼簡單。
下面的程序就解決了這個問題:首先導航到托爾斯泰的《戰爭與和平》的大字號印刷版,打開閱讀器,收集圖片的 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 提供大量已知的文字與圖片映射集,通過訓練 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%)。
大多數其餘的驗證碼都是比較簡單的。例如,流行的PHP內容管理系統Drupal有一個著名的驗證碼模塊:https://www.drupal.org/project/captcha 能夠生成不一樣難度的驗證碼。
那麼與其餘驗證碼相比,到底是什麼讓這個驗證碼更容易被人類和機器讀懂呢?
- 字母沒有相互疊加在一塊兒,在水平方向上也沒有彼此交叉。也就是說,能夠在每個字母外面畫一個方框,而不會重疊在一塊兒。
- 圖片沒有背景色、線條或其餘對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提供每一個字符不一樣形式的樣本。
作這個枯燥的工做可能要花好幾個小時的時間,你可能更想用這點兒時間找個好看的視頻或電影看看。首先要把大量的驗證碼樣本下載到一個文件夾裏。
下載的樣本數量由驗證碼的複雜程度決定;我在訓練集裏一共放了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,由於它不須要安裝,也沒有其餘依賴,只要有瀏覽器就能夠運行,並且用法很簡單:上傳圖片,若是要增長新矩形就單擊「add」按鈕,還能夠根據須要調整矩形的尺寸,最後把新生成的矩形定位文件複製到一個新文件裏就能夠了。
矩形定位文件必須保存在一個 .box後綴的文本文件中。和圖片文件同樣,文本文件也是用驗證碼的實際結果命名(例如,4MmC3.box)。另外,這樣便於檢查 .box文件的內容和文件的名稱,並且按文件名對目錄中的文件排序以後,就可讓 .box文件與對應的圖片文件的實際結果進行對比。
你還須要建立大約 100 個 .box文件來保證你有足夠的訓練數據。由於Tesseract會忽略那些不能讀取的文件,因此建議你儘可能多作一些矩形定位文件,以保證訓練足夠充分。若是你以爲訓練的 OCR 結果沒有達到你的目標,或者Tesseract識別某些字符時老是出錯,多建立一些訓練數據而後從新訓練將是一個不錯的改進方法。
建立完滿載 .box 文件和圖片文件的數據文件夾以後,在作進一步分析以前最好備份一下這個文件夾。雖然在數據上運行訓練程序不太可能刪除任何數據,可是建立 .box 文件用了你好幾個小時的時間,來之不易,穩妥一點兒總沒錯。此外,可以抓取一個盡是編譯數據的 混亂目錄,而後再嘗試一次,老是好的。
前面的內容只是對Tesseract庫強大的字體訓練和識別能力的一個簡略概述。若是你對 Tesseract 的其餘訓練方法感興趣,甚至打算創建本身的驗證碼訓練文件庫,或者想和全世界的 Tesseract 愛好者分享本身對一種新字體的識別成果,推薦閱讀 Tesseract 的文檔:https://github.com/tesseract-ocr/tesseract/wiki