Python徒手實現(xiàn)識別手寫數(shù)字—圖像的處理

寫在前面

在上一篇文章Python徒手實現(xiàn)手寫數(shù)字識別—大綱中杖狼,我們已經(jīng)講過了我們想要寫的全部思路,所以我們不再說全部的思路蝶涩。

我這一次將圖片的讀入與處理的代碼寫了一下絮识,和大綱寫的過程一樣子寓,這一段代碼分為以下幾個部分:

  • 讀入圖片;
  • 將圖片讀取為灰度值矩陣笋除;
  • 圖片背景去噪斜友;
  • 切割圖片垃它,得到手寫數(shù)字的最小矩陣烹看;
  • 拉伸/壓縮圖片洛史,得到標準大小為100x100大小矩陣也殖;
  • 將圖片拉為1x10000大小向量土思,存入訓練矩陣中己儒。

所以下面將會對這幾個函數(shù)進行詳解捆毫。

代碼分析

基礎(chǔ)內(nèi)容

首先我們現(xiàn)在最前面定義基礎(chǔ)變量

import os
from skimage import io
import numpy as np

##Essential vavriable 基礎(chǔ)變量
#Standard size 標準大小
N = 100
#Gray threshold 灰度閾值
color = 100/255

其中標準大小指的是我們在最后經(jīng)過切割、拉伸后得到的圖片的尺寸為NxN途样”舯铮灰度閾值指的是在某個點上的灰度超過閾值后則變?yōu)?.

接下來是這圖像處理的一部分的主函數(shù)

filenames = os.listdir(r"./num/")
pic = GetTrainPicture(filenames)

其中filenames得到在num目錄下所有文件的名稱組成的列表。pic則是通過函數(shù)GetTrainPicture得到所有訓練圖像向量的矩陣赖晶。這一篇文章主要就是圍繞這個函數(shù)進行講解辐烂。

GetTrainPicture函數(shù)

GetTrainPicture函數(shù)內(nèi)容如下

def GetTrainPicture(files):
    '''Read and save train picture 讀取訓練圖片并保存'''
    Picture = np.zeros([len(files), N**2+1])
    #loop all pictures 循環(huán)所有圖片文件
    for i, item in enumerate(files):
        #Read the picture and turn RGB to grey讀取這個圖片并轉(zhuǎn)為灰度值
        img = io.imread('./num/'+item, as_grey = True)
        #Clear the noise清除噪音
        img[img>color] = 1
        #Cut the picture and get the picture of handwritten number
        #將圖片進行切割纠修,得到有手寫數(shù)字的的圖像
        img = CutPicture(img)
        #Stretch the picture and get the standard size 100x100
        #將圖片進行拉伸,得到標準大小100x100
        img = StretchPicture(img).reshape(N**2)
        #Save the picture to the matrix 將圖片存入矩陣
        Picture[i, 0:N**2] = img
        #Save picture's name to the matrix 將圖片的名字存入矩陣
        Picture[i, N**2] = int(item[0])
    return Picture

可以看出這個函數(shù)的信息量非常大了牛,基本上今天做的所有步驟我都把封裝到一個個函數(shù)里面了辰妙,所以這里我們可以看到圖片處理的所有步驟都在這里。

提前準備

首先是創(chuàng)建了一個用來存放所有圖像向量的矩陣Picture蛙婴,大小為fx10001尔破,其中f代表我們擁有的訓練圖片的數(shù)目浇衬,10001的前10000位代表圖片展開后的向量長度餐济,最后一維代表這一個向量的類別絮姆,比如說時2就代表這個圖片上面寫的數(shù)字是2.

接下來用的是一個for循環(huán),將files里面每一個圖片進行一次迭代篙悯,計算出向量后存入picture辕近。

在循環(huán)中的內(nèi)容就是對每一張圖片進行的操作匿垄。

讀入圖片并清除背景噪音

首先是io.imread函數(shù),這個函數(shù)是將圖片導出成為灰度值的矩陣漏峰,每一個像素點是矩陣上的一個元素届榄。

接下來是img[img>color]=1這一句。這一句運用了邏輯運算的技巧靖苇,我們可以將其分為兩部分

point = img > color
img[point] = 1

首先是img>color班缰,img是一個矩陣,color是一個數(shù)脾拆。意義就是對img中所有元素進行判斷是否大于color這個數(shù)莹妒,并輸出一個與img同等大小的矩陣,對應(yīng)元素上是該值與color判斷后的結(jié)果渠驼,有False與True鉴腻。如果大于這個數(shù),那么就是Ture谋梭,否則是False。下面舉個例子盹舞,不再贅述隘庄。

a = np.array([1, 2, 3, 4])
print(a>2)

#以下為輸出結(jié)果
[False False True True]

之后的img[point] = 1說明將所有True的值等于1丑掺。舉個例子

a = np.array([1, 2, 3, 4])
p = a > 2
a[p] = 0
print(a)

#以下為輸出結(jié)果
[1 2 0 0]

因此我通過這樣的方法來清除掉了與數(shù)字顏色差別太大的背景噪音。

切割圖像

首先切割圖像的函數(shù)我寫的是CutPicture兼丰。我們來說一下這個切割圖像的意思唆缴。比如說有一個人寫字寫的特別小,另一個人寫字寫的特別大艳丛。就像是下圖所示趟紊,所以我們進行這樣的操作。沿著圖片的邊進行切割眶蕉,得到了下面切割后的圖片唧躲,讓數(shù)字占滿整個圖片,從而具有可比性饭入。

所以下面貼出代碼谐丢,詳細解釋一下我是怎么做的。

def CutPicture(img):
    '''Cut the Picture 切割圖象'''
    #初始化新大小
    size = []
    #圖片的行數(shù)
    length = len(img)
    #圖片的列數(shù)
    width = len(img[0,:])
    #計算新大小
    size.append(JudgeEdge(img, length, 0, [-1, -1]))
    size.append(JudgeEdge(img, width, 1, [-1, -1]))
    size = np.array(size).reshape(4)
    return img[size[0]:size[1]+1, size[2]:size[3]+1]

首先函數(shù)導入過來的的參數(shù)只有一個原img讥珍。

我第一步做的是把新的大小初始化一下窄瘟,size一共會放入四個值,第一個值代表原圖片上的手寫數(shù)字圖案的最高行氏义,第二個值代表的是最低行图云,第三個值代表數(shù)字圖案的最左列,克婶,第四個只代表最右列**丹泉。這個還看不明白的話就看上面的圖示,就是沿著圖片切割一下就好了紫岩。

接下來的length和width分別代表著原圖片的行數(shù)與列數(shù)睬塌,作用在下面揩晴。我又創(chuàng)建了一個JudgeEdge函數(shù)贪磺,這個函數(shù)是輸出它的行數(shù)或者列數(shù)的兩位數(shù)字。第一個append是給size列表放入了兩個行序號(最高行和最低行)劫映,第二個append是給size放進兩個列序號(最左列和最右列)刹前。所以接下來就看JudgeEdge函數(shù)是干什么的。

def JudgeEdge(img, length, flag, size):
    '''Judge the Edge of Picture判斷圖片切割的邊界'''
    for i in range(length):
        #Row or Column 判斷是行是列
        if flag == 0:
            #Positive sequence 正序判斷該行是否有手寫數(shù)字
            line1 = img[i, img[i,:]<color]
            #Negative sequence 倒序判斷該行是否有手寫數(shù)字
            line2 = img[length-1-i, img[length-1-i,:]<color]
        else:
            line1 = img[img[:,i]<color, i]
            line2 = img[img[:,length-1-i]<color,length-1-i]
        #If edge, recode serial number 若有手寫數(shù)字祖今,即到達邊界千诬,記錄下行
        if len(line1)>=1 and size[0]==-1:
            size[0] = i
        if len(line2)>=1 and size[1]==-1:
            size[1] = length-1-i
        #If get the both of edge, break 若上下邊界都得到,則跳出
        if size[0]!=-1 and size[1]!=-1:
            break
    return size

JudgeEdge函數(shù)的參數(shù)flag就是用來判斷是行還是列邪驮,當flag=0時泵三,說明以行為基礎(chǔ)開始循環(huán);當flag=1時說明以列為基礎(chǔ)進行循環(huán)俺抽。所以參數(shù)length傳遞的時候就是行數(shù)和列數(shù)较曼。接下來的for循環(huán)就是根據(jù)length的大小看似是循環(huán)。

當是行時弛饭,我這里用line1和line2起到的是一個指針的作用萍歉,即在第i行時,line1的內(nèi)容就是這一行擁有非白色底(數(shù)值為1)的像素的個數(shù)憔晒;line2的作用則是反序的蔑舞,也就是他計算的是倒數(shù)i行非白色像素個數(shù)攻询,這樣做的目的是能夠快一點,讓上下同時開始進行尋找低零,而不用line1把整個圖片循環(huán)一遍拯杠,line2把整個圖片循環(huán)一遍,大大節(jié)省了時間气堕。

尋找這一行擁有非白色底的像素的個數(shù)這一個語句同樣的運用了邏輯判斷,和上文中去噪的原理一模一樣揖膜。

當line里面有數(shù)的時候梅桩,說明已經(jīng)到達了手寫數(shù)字的邊緣。這時候就記錄下來宿百,然后就不再改變。當兩個line都不等于初始值-1時雀费,說明已經(jīng)找到了兩個邊緣痊焊,這時候就可以跳出循環(huán)并且return了薄啥。

這個函數(shù)就是這樣。所以說切割圖像的完整代碼就是這樣子刁愿,下面就要把切割的大小不一的圖像給拉伸成標準大小100x100了到逊。

拉伸圖像

因為切割以后的圖像有大有小,一張圖片的大小可能是21x39(比如說數(shù)字2)枷踏,另一張可能是4x40(比如說數(shù)字1)掰曾。所以為了能夠讓他們統(tǒng)一大小稱為100x100旷坦,我們就要把他們拉伸一下佑稠。

大概就是像圖上的一樣。實際情況的圖案可能會更復雜捆蜀,所以我們下面展示一下代碼

def StretchPicture(img):
    '''Stretch the Picture拉伸圖像'''
    newImg1 = np.ones(N*len(img)).reshape(len(img), N)
    newImg2 = np.ones(N**2).reshape(N, N)
    #對每一行進行拉伸/壓縮
    #每一行拉伸/壓縮的步長
    temp1 = len(img[0])/100
    #每一列拉伸/壓縮的步長
    temp2 = len(img)/100
    #對每一行進行操作
    for i in range(len(img)):
        for j in range(N):
            newImg1[i, j] = img[i, int(np.floor(j*temp1))]
    #對每一列進行操作
    for i in range(N):
        for j in range(N):
            newImg2[i, j] = newImg1[int(np.floor(j*temp2)), j]
    return newImg2

首先初始化一個新的圖片矩陣辆它,這個大小就是標準大小100x100。接下來才是重頭戲呢蔫。我這里用的方法是比較簡單基礎(chǔ)的方法飒筑,但是可能依舊比較難。

首先定義兩個步長step1和step2俏脊,分別代表拉伸/壓縮行與列時的步長肤晓。這里的原理就是把原來的長度給他平均分成100份材原,然后將這100個像素點分別對應(yīng)上原本的像素點。

如下圖所示卷胯,圖像1我們假設(shè)為原圖像威酒,圖像2我們假設(shè)為標準圖像,我們需要把圖像1轉(zhuǎn)化為圖像2担钮,其中每一個點代表一個像素點尤仍,也就是圖像1有五個像素點,圖像2有四個像素點宰啦。

所以我的思想就是直接讓圖像2的像素點的值等于距離它最近的圖像1的像素點赡模。

我們?yōu)榱朔奖闫鹨姡谶@里定義一個語法:圖像2第三個數(shù)據(jù)點我們可以寫為2_3.

所以2_1對應(yīng)的就是1_1教硫,2_2對應(yīng)的就是1_2,2_3對應(yīng)的是1_4挤安,2_4對應(yīng)的是1_5丧鸯。就這樣我們就能夠得到了圖像2所有的數(shù)據(jù)點丛肢。

利用數(shù)學的形式表現(xiàn)出來,就是假設(shè)圖像1長度為l_1穆刻,圖像2長度為l_2杠步,所以令圖像2的步長為l_1/l_2,也就是說當圖像2的第一個像素點對應(yīng)圖像1第一個像素點朵锣,圖像2的最后一個像素點對應(yīng)圖像1最后一個像素點甸私。然后圖像2第二個像素點位置就是2l_1/l_2皇型,對應(yīng)圖像1第floor(2l_1/l_2)個像素點。以此類推就行绞吁。因此再回頭看一下那一段代碼唬格,這一段是不是就好理解了?

之后對行與列分別進行這個操作员舵,所以就可以得到標準的圖片大小藕畔。然后再返回到GetTrainPicture即可注服。

再GetTrainPicture函數(shù)中,我用了reshape函數(shù)把原本100x100大小的圖片拉伸成為1x10000大小的向量女淑,然后存入矩陣當中辜御,并將這一張圖片的類別存入矩陣最后一個擒权。

以上就是圖片處理的所有內(nèi)容。

小結(jié)

以上就是把一張圖片經(jīng)過處理后存入矩陣的內(nèi)容愉老。

本文中的所有算法剖效、代碼均是我自己構(gòu)思的,所以可能存在一些不足之處咒林,我沒有系統(tǒng)的學習過圖像的相關(guān)知識爷光,也并不是計算機專業(yè)瞎颗,因此可能在理論上有一些不合乎情況,所以如果有錯誤的話歡迎一起討論引有,謝謝倦逐。

目前所有源代碼都在我的GitHub中更新哦。

如果喜歡的話曾我,麻煩點一個喜歡哦抒巢,謝謝秉犹!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市型诚,隨后出現(xiàn)的幾起案子狰贯,更是在濱河造成了極大的恐慌,老刑警劉巖还绘,帶你破解...
    沈念sama閱讀 222,378評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拍顷,死亡現(xiàn)場離奇詭異塘幅,居然都是意外死亡电媳,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,970評論 3 399
  • 文/潘曉璐 我一進店門捞稿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來娱局,“玉大人咧七,你說我怎么就攤上這事〕芴危” “怎么了瘟檩?”我有些...
    開封第一講書人閱讀 168,983評論 0 362
  • 文/不壞的土叔 我叫張陵墨辛,是天一觀的道長背蟆。 經(jīng)常有香客問我哮幢,道長志珍,這世上最難降的妖魔是什么伦糯? 我笑而不...
    開封第一講書人閱讀 59,938評論 1 299
  • 正文 為了忘掉前任敛纲,我火速辦了婚禮剂癌,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘旁壮。我一直安慰自己抡谐,他們只是感情好桐猬,可當我...
    茶點故事閱讀 68,955評論 6 398
  • 文/花漫 我一把揭開白布溃肪。 她就那樣靜靜地躺著,像睡著了一般杜秸。 火紅的嫁衣襯著肌膚如雪润绎。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,549評論 1 312
  • 那天,我揣著相機與錄音棍郎,去河邊找鬼涂佃。 笑死蜈敢,一個胖子當著我的面吹牛抓狭,可吹牛的內(nèi)容都是我干的造烁。 我是一名探鬼主播,決...
    沈念sama閱讀 41,063評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼苗桂,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了告组?” 一聲冷哼從身側(cè)響起煤伟,我...
    開封第一講書人閱讀 39,991評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎木缝,沒想到半個月后便锨,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,522評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡氨肌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,604評論 3 342
  • 正文 我和宋清朗相戀三年鸿秆,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片怎囚。...
    茶點故事閱讀 40,742評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖恳守,靈堂內(nèi)的尸體忽然破棺而出考婴,到底是詐尸還是另有隱情,我是刑警寧澤催烘,帶...
    沈念sama閱讀 36,413評論 5 351
  • 正文 年R本政府宣布沥阱,位于F島的核電站,受9級特大地震影響伊群,放射性物質(zhì)發(fā)生泄漏考杉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,094評論 3 335
  • 文/蒙蒙 一舰始、第九天 我趴在偏房一處隱蔽的房頂上張望崇棠。 院中可真熱鬧,春花似錦丸卷、人聲如沸枕稀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,572評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽萎坷。三九已至凹联,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間哆档,已是汗流浹背蔽挠。 一陣腳步聲響...
    開封第一講書人閱讀 33,671評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留虐呻,地道東北人象泵。 一個月前我還...
    沈念sama閱讀 49,159評論 3 378
  • 正文 我出身青樓寞秃,卻偏偏與公主長得像斟叼,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子春寿,可洞房花燭夜當晚...
    茶點故事閱讀 45,747評論 2 361

推薦閱讀更多精彩內(nèi)容