用 python 解碼實價登錄的地址圖片
早上搭車的時候,在手機上看到了這個 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()