原标題: 使用圖像文字識别技術擷取失信黑名單 最近接了一個新需求,需要擷取一些信用黑名單資料,但是找了很多資料源,都是同樣的幾張圖檔,目測是excel表格的截圖,就像下面這樣: https://img.shangyexinzhi.com/xztest-image/article/24c974232082ce2e38f7addf537b2364.jpg
既然沒有找到文本類型的資料源,隻能對圖檔上的文字進行識别了。
嘗試一,利用第三方API識别:
說到圖像識别我首先想到了網上的各類圖像識别服務。試用了一下百度、騰訊的識别服務,效果并不好,部分文字識别錯誤甚至無法識别,不付費隻能使用有限的幾次。總之,使用第三方的識别服務是行不通的。
嘗試二,利用Tesseract-OCR識别:
接下來隻能自己想辦法識别了,首先試一下google的工具Tesseract-OCR。Python裡的pytesseract子產品對這個工具進行了封裝,使用起來很友善。
https://img.shangyexinzhi.com/xztest-image/article/79130935d1ce31b49a69257d070ff643.pngtest.png
In [1]: import Image
In [2]: import pytesseract
In [3]: image = Image.open('/home/****/shixin/test.png')
In [4]: image = image.convert('L')
In [5]: text = ''.join(pytesseract.image_to_string(image,, config='-psm 6'))
In [6]: print text
〔Zol l 〕西中執字第
口口o22号
識别結果不太準确,原因是識别場景比較複雜,識别的内容包含了标點符号、漢字、數字和字母。對于隻含有數字或者字母的識别場景,pytesseract 的識别已經足夠了,但是對于目前較複雜的識别需求,識别的準确率不高。
對于這種情況,可以采用訓練字庫的方式提高準确率,感興趣的同學可以參考這篇文章(http://www.cnblogs.com/wzben/p/5930538.html)。但是對于目前的需求,我們并不能得到足夠的訓練樣本,是以此路不通。
嘗試三,利用機器學習識别:
機器學習我沒有接觸過,也沒有做過相關的需求,于是我開始學習它。然後我發現這是一門很廣博的學科,短期的學習難有成效,隻好暫時放棄,嘗試用其他辦法解決問題。
嘗試四,利用圖像對比識别:
雖然新技能Get失敗了,但是對于搞定需求,我從來都是不抛棄不放棄的。我想到了利用圖像相似度識别文字的方法,在這裡感謝大學教導我數字圖像處理的導師。經過嘗試,這是一個可行的方案,接下來就介紹一下識别的過程。識别過程主要分為以下幾個步驟:
1. 圖像預處理
從網上下載下傳的圖檔需要進行預處理,包括:
(1)灰階化
将彩色圖像轉化成為灰階圖像的過程稱為圖像的灰階化。彩色圖像中每個像素點的顔色由R、G、B三個分量決定,分别代表紅綠藍三種原色。灰階圖像就是R、G、B三個分量相同的特殊彩色圖像,每個像素點隻有一個代表亮度的灰階值,将圖像轉變成灰階圖像可以友善後續的計算。
(2)二值化
圖像的二值化,就是将圖像的灰階值(https://baike.baidu.com/item/%E7%81%B0%E5%BA%A6%E5%80%BC)設定為0或255,使得圖像上隻存在黑白兩種顔色,而沒有中間的灰色。二值化後灰階圖像的噪點會被去除,可以使後續的圖像對比更簡單。二值化需要指定一個閥值,經過測試,這次要識别的圖像的最優二值化閥值為69,即灰階圖像中灰階值低于69的像素的灰階值會變為0,反之變為255。代碼如下:
def binarizing(image, threshold): pixdata = image.load() w, h = image.size for y in range(h): for x in range(w): if pixdata[x, y] < threshold: pixdata[x, y] = 0 else: pixdata[x, y] = 255 return image
https://img.shangyexinzhi.com/xztest-image/article/688b90e9f0ceeed99539d1f01e156d48.jpg 原圖.png
https://img.shangyexinzhi.com/xztest-image/article/b988a7df4402b332cfd73d3bb8e32bf9.jpg灰階化.png
https://img.shangyexinzhi.com/xztest-image/article/c73a71680fd222ec558e8520ab0fd65b.jpg二值化.png
2. 圖像切割為單元格
從上面的圖可以看出,圖像是一個表格的截圖,我們需要把它分割成單元格,這是為了友善資料的分類和圖像的二次切割。
圖檔像素矩陣輪廓如下所示:
https://img.shangyexinzhi.com/xztest-image/article/95305b9630b5ac263e96ffa0a54597d7.jpg使用橫向和縱向掃描線分别掃描圖像的像素矩陣,根據像素灰階值的變化确定表格分割線的坐标,再根據坐标把圖像切割成單元格。
有的單元格有多行内容,需要把多行合并為一行,如下所示:
https://img.shangyexinzhi.com/xztest-image/article/4c44e3a3f79c2426503ee93f82623dac.png使用橫向掃描線掃描這一單元格圖像,找到行之間空白部分的坐标,根據坐标确定分割線的位置,然後根據分割線分割圖像,最後合并為一行。要注意的是類似于下圖的情況:
https://img.shangyexinzhi.com/xztest-image/article/c0790cf4d58e3a755f79b7458c413410.png這種情況下第二行的“号”字是上下結構而且單獨占一行,單元格會被分成三行,解決辦法是根據漢字的高度跳過間隔高度不足的分割線。
3. 單元格分類
圖像的第一行是表頭,圖像分割為單元格後先使用tesseract識别表頭,這樣就可以根據表頭判斷列的類型,如案号、組織機構代碼等,進而指定不同的政策将單元格分割為字元。比如案号的内容含有數字、字母、漢字和标點符号,而組織機構代碼隻含有數字,這就需要使用不同的分割方式。
4. 單元格分割為字元
單元格中字元的分割可以說是耗時最久最難的部份了,有很多需要注意的點。
對于如下所示的隻含有數字和字母的單元格,分割起來比較簡單,直接使用縱向掃描線掃描,得出字元間的空白部分的坐标,然後根據坐标計算分割線進行分割即可。
https://img.shangyexinzhi.com/xztest-image/article/0eb7fc1efc3c0ce2adffee66d38712d6.png而對于含有數字、字母、漢字和标點符号的單元格,需要對分割線進行二次加工,這是因為存在左右結構、左中右結構、左中中右結構的漢字。如下所示:
https://img.shangyexinzhi.com/xztest-image/article/05b381dc122add0888bf99154ca3914c.png其中“刑”字是左右結構,“川”字是左中右結構,“順”字是左中中右結構。
在本次識别過程中,對含有漢字的單元格分割出的每個字元做如下處理:
(1)判斷是否是左右結構的漢字
若目前字元與它後面一個字元的高度均大于9px,或者這兩個字元中有一個的寬度小于4px,說明這兩個字元可能是一個左右結構的漢字。那麼忽略兩個字元間的分割線,将這兩個字元作為一個完整字元進行識别,識别成功則說明這兩個字元是一個漢字,去除兩個字元中間的分割線,為二次分割做準備。
(2)判斷是否是左中右結構的漢字
若上一步的兩個字元沒有判斷為漢字,将目前字元與它後面的兩個字元作為一個新字元切割下來,如果這三個字元合并後的新字元寬度等于12px且高度大于10px,說明新字元是一個左中右結構的漢字,去除三個字元中間的分割線,為二次分割做準備。
(3)判斷是否是左中中右結構的漢字
若上一步的三個字元沒有判斷為漢字,将目前字元與它後面的三個字元作為一個新字元切割下來,如果這四個字元合并後的新字元寬度等于12px且高度大于10px,說明新字元是一個左中中右結構的漢字,去除四個字元中間的分割線,為二次分割做準備。代碼邏輯片段如下:
tmp_img_l = clear_image(image.crop((white_line[index - 1] + 1, 0, white_line[index], h))) tmp_img_r = clear_image(image.crop((white_line[index] + 1, 0, white_line[step_1], h))) tmp_w_l, tmp_h_l = tmp_img_l.size tmp_w_r, tmp_h_r = tmp_img_r.size word_image = clear_image(image.crop((white_line[index - 1] + 1, 0, white_line[step_1], h))) if ((tmp_h_r > 9 and tmp_h_l > 9) or (tmp_w_l < 4 or tmp_w_r < 4)) and parse_character(word_image, word_data): for tmp_index in range(index, step_1): tmp_white_line[tmp_index] = None else: for tmp_index in range(step_1 + 1, len(white_line)): if white_line[tmp_index] - white_line[tmp_index - 1] > 1: step_2 = tmp_index if None not in tmp_white_line[index - 1: step_2] and white_line[step_2] - white_line[index - 1] == 12: tmp_img = clear_image(image.crop((white_line[index - 1] + 1, 0, white_line[step_2], h))) tmp_w, tmp_h = tmp_img.size if tmp_h > 10: for tmp_index in range(index, step_2): tmp_white_line[tmp_index] = None else: for tmp_index in range(step_2 + 1, len(white_line)): if white_line[tmp_index] - white_line[tmp_index - 1] > 1: step_3 = tmp_index if None not in tmp_white_line[index - 1: step_3] and white_line[step_3] - white_line[index - 1] == 12: tmp_img = clear_image(image.crop((white_line[index - 1] + 1, 0, white_line[step_3], h))) tmp_w, tmp_h = tmp_img.size if tmp_h > 10: for tmp_index in range(index, step_3): tmp_white_line[tmp_index] = None break break
5. 生成對比字元時使用的參照資料集
仔細的觀察圖檔裡的文字,再利用網站識别字型,很幸運的找到了圖檔原作者使用的字型。接下來我們就可以生成對比字元時使用的參照資料集了。
首先下載下傳字型檔案,然後利用字型檔案把文字渲染到空白圖檔上,最後把圖檔轉換為矩陣存儲到檔案中。渲染的字型的大小要和識别的圖檔上的字型一緻,這裡是12px。下面給出文字轉換為圖像矩陣的函數:
def paste_word(word): # 生成單個文字矩陣 pygame.init() font = pygame.font.Font('***/***.TTF', 12) rtext = font.render(word, False, (0, 0, 0), (255, 255, 255)) sio = StringIO.StringIO() pygame.image.save(rtext, sio) sio.seek(0) image = Image.open(sio) image = clear_image(image.convert('L')) # 去除圖檔四周的空白 if image: return numpy.asarray(image) return None
本次識别搜錄的字元有7869個,生成的資料集儲存到一個檔案中。
6. 對比識别字元
最後的一步是對比識别字元,代碼如下:
def parse_character(word_image, word_data): word_matrix = numpy.asarray(word_image) for word, matrix in word_data.items(): try: if word in '4679BCDEFHIJKLMNOQRSTUVWXYZ': threshold = 256 elif word in u'()AG': threshold = 516 else: threshold = 1 if numpy.subtract(matrix, word_matrix).sum() <= threshold: if word == 'B' and numpy.subtract(word_data['P'], word_matrix).sum() <= 1: word = 'P' if word == 'E' and numpy.subtract(word_data['F'], word_matrix).sum() <= 1: word = 'F' return word except Exception: pass return ''
其中word_image是分割得到的字元圖檔,word_data是預先生成的參照字元矩陣,把word_image轉化為矩陣,然後對兩個矩陣的差求和,如果求得的和小于等于閥值threshold,說明字元比對完成,字元識别成功。
總結:
這種識别方法的準确率并不是100%,識别失敗的情況有兩種。一種情況是有些含有多行文本的單元格高度不足,單元格中最上和最下兩行的文字隻顯示了一半,如下圖所示:
https://img.shangyexinzhi.com/xztest-image/article/542c0f5aab9c1f702b6044b358457e0f.png這種情況人眼也無法識别,隻能放棄;另一種情況是識别的漢字中存在異體字,如“昇”、“堃”等,字型檔案無法生成這類文字的圖像矩陣,導緻識别失敗。識别失敗的情況極少,是以手動矯正就可以了。
這種識别方法的缺點是适用範圍小,識别的效率也不高。不過識别效率仍有提升空間,比如可以利用多線程和多程序并發識别,提高資源使用率,也可以使用二分法、插值法等算法優化識别字元時的比對過程。
識别過程中最大的難點是漢字的分割,這需要對漢字的特點進行深入了解。
這種識别方法隻能算是“權宜之計”,要更快更好的識别圖像,還是要用機器學習,有興趣的同學可以一起學習。更多文字識别内容詳見
商業新知-文字識别