用 python 解碼實價登錄的地址圖片
Posted by tjwei on 星期一, 11月 19, 2012 with 9 comments
早上搭車的時候,在手機上看到了這個 twitter:
爺爺曾經說過:「如果手邊沒有好用的 OCR 工具,就自己寫一個」。所以我下班後就寫了一個。七拼八湊的,但勉強能跑就是了。
程式碼在 https://sites.google.com/site/xmktjw/Home/files/img2txt.zip&d=1
內含解碼結果。
需要榮尼王的地址原始圖片
Dropbox 22.37M: https://www.dropbox.com/s/4p9ol2xjib6v9zk/images-address-20121117.zip
解開放在 address 目錄。
然後還需要把新細明體 mingliu.ttc 及 ARIALN.TTF 放在 font 目錄下。
個人建議內政部和廠商可以進一步將地址及價格的圖片利用 CAPTCHA 的方式處理,就能簡單防止別人抓資料了。
更新:這不是一個一般性的OCR解法,如 Y. Chao 提到的,有開源的 OCR :tesseract-ocr, ocropus. 也有現成中文的訓練資料和 python module,不然自己訓練也成。
和本文作法的差異有點像是手排與自排的差異。
不,仔細想想,應該是腳踏車與汽車的差異。
改過的腳踏車可以順便處理價格的圖形,還沒有跟榮尼王的資料比對過。
https://sites.google.com/site/xmktjw/Home/files/img2txt.zip
需要的 python module 是 numpy, opencv, pygame, pil 。然後用 python 2.7 的 idle 跑即可。
演算法很簡單的就是把新細明體的字體解出來,然後用 opencv 暴力跟實價登錄的圖片地址比對。 阿拉柏數字不曉得是什麼字體,所以先用 ARIALN.TTF粗略的抓到圖片檔中的位置,然後把 bitmap 擷取下來。
pygame 是用來弄出 TTF 字型的 bitmap。PIL 是因為我不曉得怎麼直接把 pygame 的 surface 轉成可用的 numpy array。
freq.txt 是之前中文手寫輸入法蒐集到的常用字表,不過有點壞掉的樣子,所以補了幾個字回去。
我在痞客邦 PIXNET 新增了篇文章:2012年9月實價登錄已經爬完了2012年9月實價登錄資料已經爬完了 http://ronnywang.pixnet.net/blog/post/31391515 …聯結提到地址部份還沒有 OCR。
爺爺曾經說過:「如果手邊沒有好用的 OCR 工具,就自己寫一個」。所以我下班後就寫了一個。七拼八湊的,但勉強能跑就是了。
程式碼在 https://sites.google.com/site/xmktjw/Home/files/img2txt.zip&d=1
內含解碼結果。
需要榮尼王的地址原始圖片
Dropbox 22.37M: https://www.dropbox.com/s/4p9ol2xjib6v9zk/images-address-20121117.zip
解開放在 address 目錄。
然後還需要把新細明體 mingliu.ttc 及 ARIALN.TTF 放在 font 目錄下。
個人建議內政部和廠商可以進一步將地址及價格的圖片利用 CAPTCHA 的方式處理,就能簡單防止別人抓資料了。
更新:這不是一個一般性的OCR解法,如 Y. Chao 提到的,有開源的 OCR :tesseract-ocr, ocropus. 也有現成中文的訓練資料和 python module,不然自己訓練也成。
和本文作法的差異有點像是手排與自排的差異。
不,仔細想想,應該是腳踏車與汽車的差異。
改過的腳踏車可以順便處理價格的圖形,還沒有跟榮尼王的資料比對過。
https://sites.google.com/site/xmktjw/Home/files/img2txt.zip
需要的 python module 是 numpy, opencv, pygame, pil 。然後用 python 2.7 的 idle 跑即可。
演算法很簡單的就是把新細明體的字體解出來,然後用 opencv 暴力跟實價登錄的圖片地址比對。 阿拉柏數字不曉得是什麼字體,所以先用 ARIALN.TTF粗略的抓到圖片檔中的位置,然後把 bitmap 擷取下來。
pygame 是用來弄出 TTF 字型的 bitmap。PIL 是因為我不曉得怎麼直接把 pygame 的 surface 轉成可用的 numpy array。
freq.txt 是之前中文手寫輸入法蒐集到的常用字表,不過有點壞掉的樣子,所以補了幾個字回去。
# encoding: utf8 import Image import pygame import numpy as np import sys import os import cv2.cv as cv import cv2 import shutil def normalize(im): im=cv2.cvtColor(im, cv2.CV_32F) im=cv2.cvtColor(im, cv.CV_RGB2GRAY) im=cv2.equalizeHist(im) return im # load bitmap of a char from TTF pygame.init() default_font1= pygame.font.Font("font/mingliu.ttc", 12) cache={} internal_digit={} def get_char(txt, fname=None, size=None): if txt.isdigit() and fname==None and size==None: return internal_digit[txt] if fname==None: if txt in cache: return cache[txt] font= default_font1 else: if size == None: size=12 font = pygame.font.Font(fname, size) t = font.render(txt, False, (255, 255, 255), (0,0,0)) wh=(t.get_width(), t.get_height()) t_str=pygame.image.tostring(t, "RGBA") rtn=normalize(np.array(Image.fromstring("RGBA", wh, t_str))) if fname==None: cache[txt]=rtn return rtn # find the bitmap of a charater in an image def find_match(img, txt, fname=None, size=None): template=get_char(txt, fname, size) result=cv2.matchTemplate(img, template, cv2.TM_SQDIFF) r=result.min() c=np.unravel_index(result.argmin(),result.shape) pt2=(c[1]+template.shape[1], c[0]+template.shape[0]) return r, (c[1],c[0]), pt2 # built internal digit image bitmaps for i,nums in enumerate([u"41760", u"25", u"9", u"8", u"3"]): im0=normalize(cv2.imread("num%d.png"%(i+1))) for c in nums: r, pt1, pt2=find_match(im0, c, "font/ARIALN.TTF", 13 if i<3 else 12) internal_digit[c]=im0[pt1[1]:pt2[1], pt1[0]:pt2[0]] # load char table uchars=u"0123456789~一強沂"+open("freq.txt").read().decode("big5")[3:] uchars={x:0 for x in uchars if x not in u"\n "} # decode an image def decode_img(img): h,w=img.shape x=0 rtn=u"" uitems=list(reversed(sorted([(v,k) for k,v in uchars.items()]))) while x < w-8: part_img=img[0:h, x:x+14] if part_img.max()<0.1: x+=14 continue best_c=None for v,c in uitems: r, pt1, pt2=find_match(part_img, c) if pt1[0]<5 and 4> r: rtn+=c uchars[c]+=1 x+=(pt2[0]-1) best_c=c break if not best_c: rtn+="???" break return rtn # main cv2.namedWindow("win") for f in os.listdir("address"): im0=normalize(cv2.imread("address/"+f)) im1=cv2.cvtColor(im0, cv.CV_GRAY2RGB) cv2.imshow("win", im1) if cv2.waitKey(10)==27: break addr=decode_img(im0) print f, addr if len(addr)<3 or addr[-3:]=="???": shutil.copy("address/"+f, "error/"+f) cv2.destroyAllWindows()
Categories: python
9 意見:
其實有 OSS 的 OCR 可以用:tesseract-ocr and ocropus
謝謝你的資訊。用通用的ocr 是比較正確而且穩定的作法。因為以後字體可能會改變。在合適的訓練資料及設定下,我想辨識率應該也幾乎會是 100%。在相同的應用範圍下,暴力法慢到不可行。
因為這次的問題條件很確定,本文的暴力法幾乎保證能100% 辨識。我比較好奇的反而是速度是否可以接受。
您好 ~~
因為我拿了您的資料解析了一下,過濾掉沒有經緯度的筆數,7,8,9月的資料加起來只有不到14000筆,而9月份的資料更只有不到200筆。
不知道是什麼原因呢?
小弟才疏學淺,野人獻曝:
圖片字型固定,或許可以對「有色區塊比例」先進行排序/分群,加快依照字頻 (uchars) 的搜尋;這樣可能會需要依照全半形分開處理
'郵遞區號' 頁面 (http://www.post.gov.tw/post/internet/f_searchzone/index.jsp) 包含的路段資訊,可以取代現有字頻表,或者可以引入對「詞」的比對,例如「臺北市、南昌路、一段、二段、段」,進一步加速。
---
抱歉只能嘴砲,Python 我當堅定的初學者已經好幾年了 :p
實搜網同志:
資料室榮尼王抓的,所以我不清楚資料的正確性。
clifflu:
你的方法都挺有可行性的。當初其實也應該直接抓你說得頁面來取代 freq.txt。
給上面的實搜網
我是爬資料的榮尼王,我剛剛發現座標部份資料沒抓到XD ,我把座標更新上去了,再抓一次 realprice20121117.json 就可以了
既然是字型大小固定,又想要自己來比對的話,其實可以簡單用類似 maximum likelihood 的方式排序:轉成灰階後把對應的pixel值與template相乘,甚至是可以轉單色然後用AND運算,然後把所有pixel加起來。要加速的話,還可以先高斯模糊一下,然後做次取樣。(以免斷線)
另外,我也覺得把所有可能的縣市、路名組合來當template,會更快且正確應該更高。
您好 ~~
我重新捉一次檔案9月份的資料依然只有135筆。
因為新聞都報內政部這次公佈的筆數應該有2萬多筆,但我們只捉到6000多筆,也不知道是否有漏,想跟你們的資料互相比對一下。
Y. Chao, 謝謝!用 correlation 的確也是不錯的方式。如果 opencv 也不用,應該就是用 AND 配上 normed correlation 最快。次取樣也是降低維度的好方式,猜想辨識率應該不會下降(算是用經驗法則來替代機器學習吧)。
實搜網您好:
你所提到的 6000 : 20000 這件事情,也許是一個我們需要 open data 的很好理由。
張貼留言