1???摘要
讓客戶滿意是我們工作的目標(biāo),不斷超越客戶的期望值來(lái)自于我們對(duì)這個(gè)行業(yè)的熱愛(ài)。我們立志把好的技術(shù)通過(guò)有效、簡(jiǎn)單的方式提供給客戶,將通過(guò)不懈努力成為客戶在信息化領(lǐng)域值得信任、有價(jià)值的長(zhǎng)期合作伙伴,公司提供的服務(wù)項(xiàng)目有:域名注冊(cè)、虛擬空間、營(yíng)銷軟件、網(wǎng)站建設(shè)、梁子湖網(wǎng)站維護(hù)、網(wǎng)站推廣。
驗(yàn)證碼是目前互聯(lián)網(wǎng)上非常常見(jiàn)也是非常重要的一個(gè)事物,充當(dāng)著很多系統(tǒng)的?防火墻?功能,但是隨時(shí)OCR技術(shù)的發(fā)展,驗(yàn)證碼暴露出來(lái)的安全問(wèn)題也越來(lái)越嚴(yán)峻。本文介紹了一套字符驗(yàn)證碼識(shí)別的完整流程,對(duì)于驗(yàn)證碼安全和OCR識(shí)別技術(shù)都有一定的借鑒意義。
然后經(jīng)過(guò)了一年的時(shí)間,筆者又研究和get到了一種更強(qiáng)大的基于CNN卷積神經(jīng)網(wǎng)絡(luò)的直接端到端的驗(yàn)證識(shí)別技術(shù)(文章不是我的,然后我把源碼整理了下,介紹和源碼在這里面):
基于python語(yǔ)言的tensorflow的‘端到端’的字符型驗(yàn)證碼識(shí)別源碼整理(github源碼分享)
2???關(guān)鍵詞
關(guān)鍵詞:安全,字符圖片,驗(yàn)證碼識(shí)別,OCR,Python,SVM,PIL
3???免責(zé)聲明
本文研究所用素材來(lái)自于某舊Web框架的網(wǎng)站?完全對(duì)外公開(kāi)?的公共圖片資源。
本文只做了該網(wǎng)站對(duì)外公開(kāi)的公共圖片資源進(jìn)行了爬取,?并未越權(quán)?做任何多余操作。
本文在書(shū)寫(xiě)相關(guān)報(bào)告的時(shí)候已經(jīng)?隱去?漏洞網(wǎng)站的身份信息。
本文作者?已經(jīng)通知?網(wǎng)站相關(guān)人員此系統(tǒng)漏洞,并積極向新系統(tǒng)轉(zhuǎn)移。
本報(bào)告的主要目的也僅是用于?OCR交流學(xué)習(xí)?和引起大家對(duì)?驗(yàn)證安全的警覺(jué)?。
4???引言
關(guān)于驗(yàn)證碼的非技術(shù)部分的介紹,可以參考以前寫(xiě)的一篇科普類的文章:
互聯(lián)網(wǎng)安全防火墻(1)--網(wǎng)絡(luò)驗(yàn)證碼的科普
里面對(duì)驗(yàn)證碼的種類,使用場(chǎng)景,作用,主要的識(shí)別技術(shù)等等進(jìn)行了講解,然而并沒(méi)有涉及到任何技術(shù)內(nèi)容。本章內(nèi)容則作為它的?技術(shù)補(bǔ)充?來(lái)給出相應(yīng)的識(shí)別的解決方案,讓讀者對(duì)驗(yàn)證碼的功能及安全性問(wèn)題有更深刻的認(rèn)識(shí)。
5???基本工具
要達(dá)到本文的目的,只需要簡(jiǎn)單的編程知識(shí)即可,因?yàn)楝F(xiàn)在的機(jī)器學(xué)習(xí)領(lǐng)域的蓬勃發(fā)展,已經(jīng)有很多封裝好的開(kāi)源解決方案來(lái)進(jìn)行機(jī)器學(xué)習(xí)。普通程序員已經(jīng)不需要了解復(fù)雜的數(shù)學(xué)原理,即可以實(shí)現(xiàn)對(duì)這些工具的應(yīng)用了。
主要開(kāi)發(fā)環(huán)境:
python3.5
python SDK版本
PIL
圖片處理庫(kù)
libsvm
開(kāi)源的svm機(jī)器學(xué)習(xí)庫(kù)
關(guān)于環(huán)境的安裝,不是本文的重點(diǎn),故略去。
6???基本流程
一般情況下,對(duì)于字符型驗(yàn)證碼的識(shí)別流程如下:
準(zhǔn)備原始圖片素材
圖片預(yù)處理
圖片字符切割
圖片尺寸歸一化
圖片字符標(biāo)記
字符圖片特征提取
生成特征和標(biāo)記對(duì)應(yīng)的訓(xùn)練數(shù)據(jù)集
訓(xùn)練特征標(biāo)記數(shù)據(jù)生成識(shí)別模型
使用識(shí)別模型預(yù)測(cè)新的未知圖片集
達(dá)到根據(jù)“圖片”就能返回識(shí)別正確的字符集的目標(biāo)
7???素材準(zhǔn)備
7.1???素材選擇
由于本文是以初級(jí)的學(xué)習(xí)研究目的為主,要求?“有代表性,但又不會(huì)太難”?,所以就直接在網(wǎng)上找個(gè)比較有代表性的簡(jiǎn)單的字符型驗(yàn)證碼(感覺(jué)像在找漏洞一樣)。
最后在一個(gè)比較舊的網(wǎng)站(估計(jì)是幾十年前的網(wǎng)站框架)找到了這個(gè)驗(yàn)證碼圖片。
原始圖:
放大清晰圖:
此圖片能滿足要求,仔細(xì)觀察其具有如下特點(diǎn)。
有利識(shí)別的特點(diǎn)?:
由純阿拉伯?dāng)?shù)字組成
字?jǐn)?shù)為4位
字符排列有規(guī)律
字體是用的統(tǒng)一字體
以上就是本文所說(shuō)的此驗(yàn)證碼簡(jiǎn)單的重要原因,后續(xù)代碼實(shí)現(xiàn)中會(huì)用到
不利識(shí)別的特點(diǎn)?:
圖片背景有干擾噪點(diǎn)
這雖然是不利特點(diǎn),但是這個(gè)干擾門檻太低,只需要簡(jiǎn)單的方法就可以除去
7.2???素材獲取
由于在做訓(xùn)練的時(shí)候,需要大量的素材,所以不可能用手工的方式一張張?jiān)跒g覽器中保存,故建議寫(xiě)個(gè)自動(dòng)化下載的程序。
主要步驟如下:
通過(guò)瀏覽器的抓包功能獲取隨機(jī)圖片驗(yàn)證碼生成接口
批量請(qǐng)求接口以獲取圖片
將圖片保存到本地磁盤(pán)目錄中
這些都是一些IT基本技能,本文就不再詳細(xì)展開(kāi)了。
關(guān)于網(wǎng)絡(luò)請(qǐng)求和文件保存的代碼,如下:
def downloads_pic(**kwargs):
pic_name = kwargs.get('pic_name', None)
url = 'httand_code_captcha/'
res = requests.get(url, stream=True)
with open(pic_path + pic_name+'.bmp', 'wb') as f: ? ? ? ?for chunk in res.iter_content(chunk_size=1024): ? ? ? ? ? ?if chunk: ?# filter out keep-alive new chunks ? ? ? ? ? ? ? ?f.write(chunk)
? ? ? ? ?f.flush()
?f.close()
循環(huán)執(zhí)行N次,即可保存N張驗(yàn)證素材了。
下面是收集的幾十張素材庫(kù)保存到本地文件的效果圖:
8???圖片預(yù)處理
雖然目前的機(jī)器學(xué)習(xí)算法已經(jīng)相當(dāng)先進(jìn)了,但是為了減少后面訓(xùn)練時(shí)的復(fù)雜度,同時(shí)增加識(shí)別率,很有必要對(duì)圖片進(jìn)行預(yù)處理,使其對(duì)機(jī)器識(shí)別更友好。
針對(duì)以上原始素材的處理步驟如下:
讀取原始圖片素材
將彩色圖片二值化為黑白圖片
去除背景噪點(diǎn)
8.1???二值化圖片
主要步驟如下:
將RGB彩圖轉(zhuǎn)為灰度圖
將灰度圖按照設(shè)定閾值轉(zhuǎn)化為二值圖
image = Image.open(img_path)
imgry = image.convert('L') ?# 轉(zhuǎn)化為灰度圖table = get_bin_table()
out = imgry.point(table, '1')
上面引用到的二值函數(shù)的定義如下:
1234567891011121314? ?def?get_bin_table(threshold=140):????"""????獲取灰度轉(zhuǎn)二值的映射table????:param threshold:????:return:????"""????table?=?[]????for?i?in?range(256):????????if?i threshold:????????????table.append(0)????????else:????????????table.append(1)?????return?table? ?
由PIL轉(zhuǎn)化后變成二值圖片:0表示黑色,1表示白色。二值化后帶噪點(diǎn)的?6937?的像素點(diǎn)輸出后如下圖:
1111000111111000111111100001111100000011
1110111011110111011111011110111100110111
1001110011110111101011011010101101110111
1101111111110110101111110101111111101111
1101000111110111001111110011111111101111
1100111011111000001111111001011111011111
1101110001111111101011010110111111011111
1101111011111111101111011110111111011111
1101111011110111001111011110111111011100
1110000111111000011101100001110111011111
如果你是近視眼,然后離屏幕遠(yuǎn)一點(diǎn),可以隱約看到?6937?的骨架了。
8.2???去除噪點(diǎn)
在轉(zhuǎn)化為二值圖片后,就需要清除噪點(diǎn)。本文選擇的素材比較簡(jiǎn)單,大部分噪點(diǎn)也是最簡(jiǎn)單的那種?孤立點(diǎn),所以可以通過(guò)檢測(cè)這些孤立點(diǎn)就能移除大量的噪點(diǎn)。
關(guān)于如何去除更復(fù)雜的噪點(diǎn)甚至干擾線和色塊,有比較成熟的算法:?洪水填充法 Flood Fill?,后面有興趣的時(shí)間可以繼續(xù)研究一下。
本文為了問(wèn)題簡(jiǎn)單化,干脆就用一種簡(jiǎn)單的自己想的?簡(jiǎn)單辦法?來(lái)解決掉這個(gè)問(wèn)題:
對(duì)某個(gè)?黑點(diǎn)?周邊的九宮格里面的黑色點(diǎn)計(jì)數(shù)
如果黑色點(diǎn)少于2個(gè)則證明此點(diǎn)為孤立點(diǎn),然后得到所有的孤立點(diǎn)
對(duì)所有孤立點(diǎn)一次批量移除。
下面將詳細(xì)介紹關(guān)于具體的算法原理。
將所有的像素點(diǎn)如下圖分成三大類
頂點(diǎn)A
非頂點(diǎn)的邊界點(diǎn)B
內(nèi)部點(diǎn)C
種類點(diǎn)示意圖如下:
其中:
A類點(diǎn)計(jì)算周邊相鄰的3個(gè)點(diǎn)(如上圖紅框所示)
B類點(diǎn)計(jì)算周邊相鄰的5個(gè)點(diǎn)(如上圖紅框所示)
C類點(diǎn)計(jì)算周邊相鄰的8個(gè)點(diǎn)(如上圖紅框所示)
當(dāng)然,由于基準(zhǔn)點(diǎn)在計(jì)算區(qū)域的方向不同,A類點(diǎn)和B類點(diǎn)還會(huì)有細(xì)分:
A類點(diǎn)繼續(xù)細(xì)分為:左上,左下,右上,右下
B類點(diǎn)繼續(xù)細(xì)分為:上,下,左,右
C類點(diǎn)不用細(xì)分
然后這些細(xì)分點(diǎn)將成為后續(xù)坐標(biāo)獲取的準(zhǔn)則。
主要算法的python實(shí)現(xiàn)如下:
def sum_9_region(img, x, y): ? ?"""
9鄰域框,以當(dāng)前點(diǎn)為中心的田字框,黑點(diǎn)個(gè)數(shù)
:param x:
:param y:
:return: ? ?"""
# todo 判斷圖片的長(zhǎng)寬度下限
cur_pixel = img.getpixel((x, y)) ?# 當(dāng)前像素點(diǎn)的值
width = img.width
height = img.height ? ?if cur_pixel == 1: ?# 如果當(dāng)前點(diǎn)為白色區(qū)域,則不統(tǒng)計(jì)鄰域值
?return 0 ? ?if y == 0: ?# 第一行
?if x == 0: ?# 左上頂點(diǎn),4鄰域
? ? ?# 中心點(diǎn)旁邊3個(gè)點(diǎn)
? ? ?sum = cur_pixel \ ? ? ? ? ? ? ? ? ?+ img.getpixel((x, y + 1)) \ ? ? ? ? ? ? ? ? ?+ img.getpixel((x + 1, y)) \ ? ? ? ? ? ? ? ? ?+ img.getpixel((x + 1, y + 1)) ? ? ? ? ? ?return 4 - sum ? ? ? ?elif x == width - 1: ?# 右上頂點(diǎn)
? ? ?sum = cur_pixel \ ? ? ? ? ? ? ? ? ?+ img.getpixel((x, y + 1)) \ ? ? ? ? ? ? ? ? ?+ img.getpixel((x - 1, y)) \ ? ? ? ? ? ? ? ? ?+ img.getpixel((x - 1, y + 1)) ? ? ? ? ? ?return 4 - sum ? ? ? ?else: ?# 最上非頂點(diǎn),6鄰域
? ? ?sum = img.getpixel((x - 1, y)) \ ? ? ? ? ? ? ? ? ?+ img.getpixel((x - 1, y + 1)) \ ? ? ? ? ? ? ? ? ?+ cur_pixel \ ? ? ? ? ? ? ? ? ?+ img.getpixel((x, y + 1)) \ ? ? ? ? ? ? ? ? ?+ img.getpixel((x + 1, y)) \ ? ? ? ? ? ? ? ? ?+ img.getpixel((x + 1, y + 1)) ? ? ? ? ? ?return 6 - sum ? ?elif y == height - 1: ?# 最下面一行
?if x == 0: ?# 左下頂點(diǎn)
? ? ?# 中心點(diǎn)旁邊3個(gè)點(diǎn)
? ? ?sum = cur_pixel \ ? ? ? ? ? ? ? ? ?+ img.getpixel((x + 1, y)) \ ? ? ? ? ? ? ? ? ?+ img.getpixel((x + 1, y - 1)) \ ? ? ? ? ? ? ? ? ?+ img.getpixel((x, y - 1)) ? ? ? ? ? ?return 4 - sum ? ? ? ?elif x == width - 1: ?# 右下頂點(diǎn)
? ? ?sum = cur_pixel \ ? ? ? ? ? ? ? ? ?+ img.getpixel((x, y - 1)) \ ? ? ? ? ? ? ? ? ?+ img.getpixel((x - 1, y)) \ ? ? ? ? ? ? ? ? ?+ img.getpixel((x - 1, y - 1)) ? ? ? ? ? ?return 4 - sum ? ? ? ?else: ?# 最下非頂點(diǎn),6鄰域
? ? ?sum = cur_pixel \ ? ? ? ? ? ? ? ? ?+ img.getpixel((x - 1, y)) \ ? ? ? ? ? ? ? ? ?+ img.getpixel((x + 1, y)) \ ? ? ? ? ? ? ? ? ?+ img.getpixel((x, y - 1)) \ ? ? ? ? ? ? ? ? ?+ img.getpixel((x - 1, y - 1)) \ ? ? ? ? ? ? ? ? ?+ img.getpixel((x + 1, y - 1)) ? ? ? ? ? ?return 6 - sum ? ?else: ?# y不在邊界
?if x == 0: ?# 左邊非頂點(diǎn)
? ? ?sum = img.getpixel((x, y - 1)) \ ? ? ? ? ? ? ? ? ?+ cur_pixel \ ? ? ? ? ? ? ? ? ?+ img.getpixel((x, y + 1)) \ ? ? ? ? ? ? ? ? ?+ img.getpixel((x + 1, y - 1)) \ ? ? ? ? ? ? ? ? ?+ img.getpixel((x + 1, y)) \ ? ? ? ? ? ? ? ? ?+ img.getpixel((x + 1, y + 1)) ? ? ? ? ? ?return 6 - sum ? ? ? ?elif x == width - 1: ?# 右邊非頂點(diǎn)
? ? ?# print('%s,%s' % (x, y))
? ? ?sum = img.getpixel((x, y - 1)) \ ? ? ? ? ? ? ? ? ?+ cur_pixel \ ? ? ? ? ? ? ? ? ?+ img.getpixel((x, y + 1)) \ ? ? ? ? ? ? ? ? ?+ img.getpixel((x - 1, y - 1)) \ ? ? ? ? ? ? ? ? ?+ img.getpixel((x - 1, y)) \ ? ? ? ? ? ? ? ? ?+ img.getpixel((x - 1, y + 1)) ? ? ? ? ? ?return 6 - sum ? ? ? ?else: ?# 具備9領(lǐng)域條件的
? ? ?sum = img.getpixel((x - 1, y - 1)) \ ? ? ? ? ? ? ? ? ?+ img.getpixel((x - 1, y)) \ ? ? ? ? ? ? ? ? ?+ img.getpixel((x - 1, y + 1)) \ ? ? ? ? ? ? ? ? ?+ img.getpixel((x, y - 1)) \ ? ? ? ? ? ? ? ? ?+ cur_pixel \ ? ? ? ? ? ? ? ? ?+ img.getpixel((x, y + 1)) \ ? ? ? ? ? ? ? ? ?+ img.getpixel((x + 1, y - 1)) \ ? ? ? ? ? ? ? ? ?+ img.getpixel((x + 1, y)) \ ? ? ? ? ? ? ? ? ?+ img.getpixel((x + 1, y + 1)) ? ? ? ? ? ?return 9 - sum
Tips:這個(gè)地方是相當(dāng)考驗(yàn)人的細(xì)心和耐心程度了,這個(gè)地方的工作量還是蠻大的,花了半個(gè)晚上的時(shí)間才完成的。
計(jì)算好每個(gè)像素點(diǎn)的周邊像素黑點(diǎn)(注意:PIL轉(zhuǎn)化的圖片黑點(diǎn)的值為0)個(gè)數(shù)后,只需要篩選出個(gè)數(shù)為?1或者2?的點(diǎn)的坐標(biāo)即為?孤立點(diǎn)?。這個(gè)判斷方法可能不太準(zhǔn)確,但是基本上能夠滿足本文的需求了。
經(jīng)過(guò)預(yù)處理后的圖片如下所示:
對(duì)比文章開(kāi)頭的原始圖片,那些?孤立點(diǎn)?都被移除掉,相對(duì)比較?干凈?的驗(yàn)證碼圖片已經(jīng)生成。
9???圖片字符切割
由于字符型?驗(yàn)證碼圖片?本質(zhì)就可以看著是由一系列的?單個(gè)字符圖片?拼接而成,為了簡(jiǎn)化研究對(duì)象,我們也可以將這些圖片分解到?原子級(jí)?,即:?只包含單個(gè)字符的圖片。
于是,我們的研究對(duì)象由?“N種字串的組合對(duì)象”?變成?“10種阿拉伯?dāng)?shù)字”?的處理,極大的簡(jiǎn)化和減少了處理對(duì)象。
9.1???分割算法
現(xiàn)實(shí)生活中的字符驗(yàn)證碼的產(chǎn)生千奇百怪,有各種扭曲和變形。關(guān)于字符分割的算法,也沒(méi)有很通用的方式。這個(gè)算法也是需要開(kāi)發(fā)人員仔細(xì)研究所要識(shí)別的字符圖片的特點(diǎn)來(lái)制定的。
當(dāng)然,本文所選的研究對(duì)象盡量簡(jiǎn)化了這個(gè)步驟的難度,下文將慢慢進(jìn)行介紹。
使用圖像編輯軟件(PhoneShop或者其它)打開(kāi)驗(yàn)證碼圖片,放大到像素級(jí)別,觀察其它一些參數(shù)特點(diǎn):
可以得到如下參數(shù):
整個(gè)圖片尺寸是 40*10
單個(gè)字符尺寸是 6*10
左右字符和左右邊緣相距2個(gè)像素
字符上下緊挨邊緣(即相距0個(gè)像素)
這樣就可以很容易就定位到每個(gè)字符在整個(gè)圖片中占據(jù)的像素區(qū)域,然后就可以進(jìn)行分割了,具體代碼如下:
def get_crop_imgs(img): ? ?"""
按照?qǐng)D片的特點(diǎn),進(jìn)行切割,這個(gè)要根據(jù)具體的驗(yàn)證碼來(lái)進(jìn)行工作. # 見(jiàn)原理圖
:param img:
:return: ? ?"""
child_img_list = [] ? ?for i in range(4):
?x = 2 + i * (6 + 4) ?# 見(jiàn)原理圖
?y = 0
?child_img = img.crop((x, y, x + 6, y + 10))
?child_img_list.append(child_img) ? ?return child_img_list
然后就能得到被切割的?原子級(jí)?的圖片元素了:
9.2???內(nèi)容小結(jié)
基于本部分的內(nèi)容的討論,相信大家已經(jīng)了解到了,如果驗(yàn)證碼的干擾(扭曲,噪點(diǎn),干擾色塊,干擾線……)做得不夠強(qiáng)的話,可以得到如下兩個(gè)結(jié)論:
4位字符和40000位字符的驗(yàn)證碼區(qū)別不大
純字母
不區(qū)分大小寫(xiě)。分類數(shù)為26
區(qū)分大小寫(xiě)。分類數(shù)為52
純數(shù)字。分類數(shù)為10
數(shù)字和區(qū)分大小寫(xiě)的字母組合。分類數(shù)為62
純數(shù)字?和?數(shù)字及字母組合?的驗(yàn)證碼區(qū)別不大
在沒(méi)有形成?指數(shù)級(jí)或者幾何級(jí)?的難度增加,而只是?線性有限級(jí)?增加計(jì)算量時(shí),意義不太大。
10???尺寸歸一
本文所選擇的研究對(duì)象本身尺寸就是統(tǒng)一狀態(tài):6*10的規(guī)格,所以此部分不需要額外處理。但是一些進(jìn)行了扭曲和縮放的驗(yàn)證碼,則此部分也會(huì)是一個(gè)圖像處理的難點(diǎn)。
11???模型訓(xùn)練步驟
在前面的環(huán)節(jié),已經(jīng)完成了對(duì)單個(gè)圖片的處理和分割了。后面就開(kāi)始進(jìn)行?識(shí)別模型?的訓(xùn)練了。
整個(gè)訓(xùn)練過(guò)程如下:
大量完成預(yù)處理并切割到原子級(jí)的圖片素材準(zhǔn)備
對(duì)素材圖片進(jìn)行人為分類,即:打標(biāo)簽
定義單張圖片的識(shí)別特征
使用SVM訓(xùn)練模型對(duì)打了標(biāo)簽的特征文件進(jìn)行訓(xùn)練,得到模型文件
12???素材準(zhǔn)備
本文在訓(xùn)練階段重新下載了同一模式的4數(shù)字的驗(yàn)證圖片總計(jì):3000張。然后對(duì)這3000張圖片進(jìn)行處理和切割,得到12000張?jiān)蛹?jí)圖片。
在這12000張圖片中刪除一些會(huì)影響訓(xùn)練和識(shí)別的強(qiáng)干擾的干擾素材,切割后的效果圖如下:
13???素材標(biāo)記
由于本文使用的這種識(shí)別方法中,機(jī)器在最開(kāi)始是不具備任何 數(shù)字的觀念的。所以需要人為的對(duì)素材進(jìn)行標(biāo)識(shí),告訴?機(jī)器什么樣的圖片的內(nèi)容是 1……。
這個(gè)過(guò)程叫做?“標(biāo)記”。
具體打標(biāo)簽的方法是:
為0~9每個(gè)數(shù)字建立一個(gè)目錄,目錄名稱為相應(yīng)數(shù)字(相當(dāng)于標(biāo)簽)
人為判定?圖片內(nèi)容,并將圖片拖到指定數(shù)字目錄中
每個(gè)目錄中存放100張左右的素材
一般情況下,標(biāo)記的素材越多,那么訓(xùn)練出的模型的分辨能力和預(yù)測(cè)能力越強(qiáng)。例如本文中,標(biāo)記素材為十多張的時(shí)候,對(duì)新的測(cè)試圖片識(shí)別率基本為零,但是到達(dá)100張時(shí),則可以達(dá)到近乎100%的識(shí)別率
14???特征選擇
對(duì)于切割后的單個(gè)字符圖片,像素級(jí)放大圖如下:
從宏觀上看,不同的數(shù)字圖片的本質(zhì)就是將黑色按照一定規(guī)則填充在相應(yīng)的像素點(diǎn)上,所以這些特征都是最后圍繞像素點(diǎn)進(jìn)行。
字符圖片?寬6個(gè)像素,高10個(gè)像素?,理論上可以最簡(jiǎn)單粗暴地可以定義出60個(gè)特征:60個(gè)像素點(diǎn)上面的像素值。但是顯然這樣高維度必然會(huì)造成過(guò)大的計(jì)算量,可以適當(dāng)?shù)慕稻S。
通過(guò)查閱相應(yīng)的文獻(xiàn)?[2],給出另外一種簡(jiǎn)單粗暴的特征定義:
每行上黑色像素的個(gè)數(shù),可以得到10個(gè)特征
每列上黑色像素的個(gè)數(shù),可以得到6個(gè)特征
最后得到16維的一組特征,實(shí)現(xiàn)代碼如下:
def get_feature(img): ? ?"""
獲取指定圖片的特征值,
1. 按照每排的像素點(diǎn),高度為10,則有10個(gè)維度,然后為6列,總共16個(gè)維度
:param img_path:
:return:一個(gè)維度為10(高度)的列表 ? ?"""
width, height = img.size
pixel_cnt_list = []
height = 10 ? ?for y in range(height):
?pix_cnt_x = 0 ? ? ? ?for x in range(width): ? ? ? ? ? ?if img.getpixel((x, y)) == 0: ?# 黑色點(diǎn)
? ? ? ? ?pix_cnt_x += 1
?pixel_cnt_list.append(pix_cnt_x) ? ?for x in range(width):
?pix_cnt_y = 0 ? ? ? ?for y in range(height): ? ? ? ? ? ?if img.getpixel((x, y)) == 0: ?# 黑色點(diǎn)
? ? ? ? ?pix_cnt_y += 1
?pixel_cnt_list.append(pix_cnt_y) ? ?return pixel_cnt_list
然后就將圖片素材特征化,按照?libSVM?指定的格式生成一組帶特征值和標(biāo)記值的向量文
area={"11":"北京","12":"天津","13":"河北","14":"山西","15":"內(nèi)蒙古","21":"遼寧","22":"吉林","23":"黑龍江","31":"上海","32":"江蘇","33":"浙江","34":"安徽","35":"福建","36":"江西","37":"山東","41":"河南","42":"湖北","43":"湖南","44":"廣東","45":"廣西","46":"海南","50":"重慶","51":"四川","52":"貴州","53":"云南","54":"西藏","61":"陜西","62":"甘肅","63":"青海","64":"寧夏","65":"新疆","71":"臺(tái)灣","81":"香港","82":"澳門","91":"國(guó)外"}
s=input('輸入身份證號(hào)碼:')
t=s[0]+s[1]
print(area[t])
最近工作中剛好要清洗一批客戶數(shù)據(jù),涉及到身份證號(hào)碼15位和18位的轉(zhuǎn)換,特意研究了下,在這里分享下。
身份證號(hào)碼的構(gòu)成
既然談到了身份證轉(zhuǎn)換,那就需要先了解下證件號(hào)碼的構(gòu)成。
公民身份號(hào)碼是特征組合碼,由 十七位數(shù)字本體碼 和 一位數(shù)字校驗(yàn)碼 組成;
排列順序從左至右依次為:六位數(shù)字地址碼,八位數(shù)字出生日期碼,三位數(shù)字順序碼和一位數(shù)字校驗(yàn)碼。
六位數(shù)字地址碼:表示編碼對(duì)象常住戶口所在縣(市、旗、區(qū))的行政區(qū)劃代碼,按GB/T 2260 的規(guī)定執(zhí)行。
八位數(shù)字出生日期碼:表示編碼對(duì)象出生的年、月、日,按 GB/T 7408 的規(guī)定執(zhí)行。年、月、日代碼之間不用分隔符。某人出生日期為 1995年08月12日,其出生日期碼為 19950812。
三位順序碼:表示在同一地址碼所標(biāo)識(shí)的區(qū)域范圍內(nèi),對(duì)同年、同月、同日出生的人編定的順序號(hào),順序碼的奇數(shù)分配給男性,偶數(shù)分配給女性。
一位校驗(yàn)碼:校驗(yàn)碼按照 ISO 7064:1983.MOD 11-2校驗(yàn)碼計(jì)算出來(lái)的檢驗(yàn)碼。
校驗(yàn)碼計(jì)算方法
1、將前面的身份證號(hào)碼17位數(shù)分別乘以不同的系數(shù)。從第一位到第十七位的系數(shù)分別為: 7 9 10 5 8 4 2 1 6 3 7 9 10 5 8 4 2 ;
2、將這17位數(shù)字和系數(shù)相乘的結(jié)果相加;
3、用加出來(lái)和除以11,看余數(shù)是多少;
4、余數(shù)只可能有0 1 2 3 4 5 6 7 8 9 10這11個(gè)數(shù)字。其分別對(duì)應(yīng)的最后一位身份證的號(hào)碼為 1 0 X 9 8 7 6 5 4 3 2 ;
5、通過(guò)上面得知如果余數(shù)是2,就會(huì)在身份證的第18位數(shù)字上出現(xiàn)羅馬數(shù)字的X。
解決思路
15位轉(zhuǎn)18位:即身份證號(hào)碼的前六位數(shù)字+ '19' + 身份證第六位以后的數(shù)字 + 校驗(yàn)碼
(不要問(wèn)我為什么加19這種白癡(′⊙ω⊙`) @?¥?的問(wèn)題,當(dāng)然是因?yàn)橹挥?9世紀(jì)的人才可能擁有15位的身份證號(hào)啦)
校驗(yàn)碼計(jì)算方法就更簡(jiǎn)單了,將這17位數(shù)字和系數(shù)相乘的結(jié)果相加除以11匹配余數(shù)對(duì)應(yīng)的號(hào)碼即可。
注意:代碼中我用了幾個(gè)變量,在這里拆解講解下。
Ai: 表示第i位置上的身份證號(hào)碼數(shù)字值 Wi: 表示第i位置上的加權(quán)因子 Wi: 7 9 10 5 8 4 2 1 6 3 7 9 10 5 8 4 2
十七位數(shù)字本體碼加權(quán)求和公式: S = Sum(AiWi), i = 0, … , 16 ,先對(duì)前17位數(shù)字的權(quán)求和
計(jì)算模 Y = mod(S, 11)
通過(guò)模得到對(duì)應(yīng)的校驗(yàn)碼
Y: 0 1 2 3 4 5 6 7 8 9 10
校驗(yàn)碼: 1 0 X 9 8 7 6 5 4 3 2
OK,分析的差不多了,直接看代碼。
怎么用代碼實(shí)現(xiàn)?
# encoding: utf-8
"""
CREATED ON 19-11-05
@AUTHOR: XUSL
"""
WI = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2, 1, ]
VI = [1, 0, 'X', 9, 8, 7, 6, 5, 4, 3, 2, ]
DEF GET_VERITY(EIGHTEEN_CARD):
"""
:PARAM EIGHTEEN_CARD:
:RETURN:
"""
AI = []
REMAINING = ''
IF LEN(EIGHTEEN_CARD) == 18:
EIGHTEEN_CARD = EIGHTEEN_CARD[0:-1]
IF LEN(EIGHTEEN_CARD) == 17:
S = 0
FOR I IN EIGHTEEN_CARD:
AI.APPEND(INT(I))
FOR I IN RANGE(17):
S = S + WI[I] * AI[I]
REMAINING = S % 11
RETURN 'X' IF REMAINING == 2 ELSE STR(VI[REMAINING])
DEF UP_TO_EIGHTEEN(FIFTEEN_CARD):
"""
15位轉(zhuǎn)18位
:PARAM FIFTEEN_CARD:
:RETURN:
"""
EIGHTEEN_CARD = FIFTEEN_CARD[0:6] + '19' + FIFTEEN_CARD[6:15]
RETURN EIGHTEEN_CARD + GET_VERITY(EIGHTEEN_CARD)
DEF DOWN_TO_FIFTEEN(EIGHTEEN_CARD):
"""
18位轉(zhuǎn)15位
:PARAM EIGHTEEN_CARD:
:RETURN:
"""
RETURN EIGHTEEN_CARD[0:6] + EIGHTEEN_CARD[8:17]
IF __NAME__ == '__MAIN__':
# 15位轉(zhuǎn)18位
CARD_1 = UP_TO_EIGHTEEN('632123820927051')
PRINT(CARD_1)
# 18位轉(zhuǎn)15位
CARD_2 = DOWN_TO_FIFTEEN('410125199908222000')
PRINT(CARD_2)
當(dāng)然,這只是個(gè)小功能,主要還是想分享下代碼,如果有同樣的處理可以直接用。
總結(jié)
以上所述是小編給大家介紹的使用Python完成15位18位身份證的互轉(zhuǎn)功能,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
#這個(gè)算法都給了,應(yīng)該比較簡(jiǎn)單吧。我也很菜,隨意寫(xiě)了一個(gè)。異常沒(méi)做,你可以自##己加一下。做一些驗(yàn)證過(guò)濾。不知道隨機(jī)是不是真是隨意隨機(jī),我沒(méi)有按照身份證規(guī)##則做隨機(jī)。是真的隨機(jī)了18位。。。?如果你有規(guī)則,也可以自己寫(xiě)一個(gè)。
import?random
yushu=[x?for?x?in?range(0,11)]?
ma=['1','0','X','9','8','7','6','5','4','3','2','1']
def?yanzheng(nid):
dicma=dict(zip(yushu,ma))
sum=0
for?x,y?in?enumerate(nid[:-1]):
sum+=((2**(18-x-1))%11)*int(y)?#17位對(duì)應(yīng)系數(shù)相乘的和
if?nid[-1]==dicma[sum%11]:?#校驗(yàn)碼對(duì)照
return?'%s?True'%nid
else:
return?'%s?False'%nid
def?readfile(fname):
f=open(fname,'rb')
for?line?in?f.readlines():
print??yanzheng(line.strip())
f.close()
def?randnum():
idstr=''
for?i?in?range(17):?#前17位隨機(jī)
idstr+=str(random.randint(0,9))
idstr+=random.choice(ma)?#最后一位從列表種隨意一個(gè),因?yàn)橛蠿
return?idstr
if?__name__=="__main__":
nid=raw_input('Please?enter?your?ID:?')?#用戶輸入ID,沒(méi)做任何驗(yàn)證
print?yanzheng(nid)??#驗(yàn)證身份證
readfile('id.txt')???#從文件讀出來(lái)?再驗(yàn)證
print?yanzheng(randnum())??#隨機(jī)一個(gè)?在驗(yàn)證
Python解釋器內(nèi)置了許多函數(shù),這意味著我們無(wú)需定義,始終可以它們。接下來(lái)和大家一起討論一個(gè)常用的內(nèi)建函數(shù)-input()和isinstance()。
input()
input()函數(shù)讀取用戶輸入,并轉(zhuǎn)換成字符串:
a?=?input()??#?將input()返回的值賦值給a
Python
a????????????#?查看a的值(為字符串'Python')
'Python'
input()函數(shù)可以提供一個(gè)參數(shù),用來(lái)提示用戶:
b?=?input('請(qǐng)輸入你最喜歡的水果:??')???#?給用戶必要的提示
請(qǐng)輸入你最喜歡的水果:??香蕉
b
'香蕉'
需要注意的是,input()函數(shù)返回的值總是字符串,當(dāng)用戶輸入的是數(shù)字也是這樣,所以當(dāng)使用它時(shí)一定要注意:
num?=?input('請(qǐng)輸入一個(gè)數(shù)字:?')
請(qǐng)輸入一個(gè)數(shù)字:?10
num?+?9????????????????????????????#?試圖把num和數(shù)字相加
Traceback?(most?recent?call?last):
File?"",?line?1,?in
TypeError:?must?be?str,?not?int
num
'10'
type(num)???????????????????????????#?查看num的數(shù)字類型
class?'str'
isinstance()
isinstance()函數(shù)用于檢查對(duì)象是否為指定類(或者說(shuō)數(shù)據(jù)類型)的實(shí)例。isintance()的第一個(gè)參數(shù)為一個(gè)對(duì)象,第二個(gè)參數(shù)為要檢查的數(shù)據(jù)類型。
舉個(gè)例子,比如有有一個(gè)變量,你想檢查它是否為數(shù)字類型,可以使用isinstance()函數(shù):
score?=?90
result?=?isinstance(score,?int)
if?result:
...?????print('score為int數(shù)據(jù)類型')
...?else:
...?????print('score不為int數(shù)據(jù)類型')
...
score為int數(shù)據(jù)類型
除了能檢查是否為int類型外,isintance()還能檢查其他數(shù)據(jù)類型(當(dāng)然了),下面是一個(gè)綜合示例:
pi?=?3.14
name?=?'Wang'
complex_num?=?1?+?2j
isinstance(pi,?float)??????????????#?3.14為浮點(diǎn)數(shù)類型
True
isinstance(name,?str)??????????????#?'Wang'為字符串類型
True
isinstance(complex_num,?complex)???#??1?+?2j為復(fù)數(shù)
True
isinstance()還可以驗(yàn)證某個(gè)對(duì)象是否為自定義的類型:
class?Developer:?????????????????????????????#?定義一個(gè)叫做Developer的類
...
...?????def?__init__(self,?name):????????????????#?__init__方法中,需要輸入名字
...?????????self.name?=?name
...?????def?display(self):???????????????????????#?定義了display()方法
...?????????print("Developer:",?self.name,?"-")
...
class?PythonDeveloper(Developer):????????????#?PythonDeveloper類,繼承了Developer類
...
...?????def?__init__(self,?name,?language):
...?????????self.name?=?name
...?????????self.language?=?language
...
...?????def?display(self):????????????????????????#?覆蓋了父類的display方法
...?????????print("Python?Developer:",?self.name,?"language:",?self.language,?"-")
...
dev?=?Developer('Zhang')?????????????????????#?創(chuàng)建一個(gè)Developer對(duì)象
dev.display()????????????????????????????????#?調(diào)用display()方法,以查看該對(duì)象
Developer:?Zhang?-
isinstance(dev,?Developer)???????????????????#?判斷dev是否為Developer類,答案是肯定的
True
isinstance(dev,?PythonDeveloper)?????????????#?判斷dev是否為PythonDeveloper類,當(dāng)然不是
False
python_dev?=?PythonDeveloper('Liu',?'Python')??#?創(chuàng)建一個(gè)PythonDeveloper對(duì)象,注意PythonDeveloper是Developer的子類
python_dev.display()??????????????????????????#?調(diào)用display方法
Python?Developer:?Liu?language:?Python?-
isinstance(python_dev,?Developer)?????????????#?判斷python_dev是否為Developer類,答案是肯定的
True
isinstance(python_dev,?PythonDeveloper)??????#?判斷python是否為PythonDeveloper類,答案也是肯定的
True
關(guān)于Python的基礎(chǔ)問(wèn)題可以看下這個(gè)網(wǎng)頁(yè)的視頻教程,網(wǎng)頁(yè)鏈接,希望我的回答能幫到你。
random.randint()
取的數(shù)的區(qū)間是前后封閉的。也就是可能會(huì)取到last_pos
如果不減1那么就會(huì)出錯(cuò)的。
all_chars[len(all_chars)]就出錯(cuò)了。
當(dāng)前名稱:python身份驗(yàn)證函數(shù) 身份證校驗(yàn)Python
轉(zhuǎn)載來(lái)于:http://www.chinadenli.net/article42/hgdsec.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供外貿(mào)網(wǎng)站建設(shè)、云服務(wù)器、企業(yè)網(wǎng)站制作、全網(wǎng)營(yíng)銷推廣、ChatGPT、搜索引擎優(yōu)化
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容