【神經網路】寫了個自動批改小孩作業的程式碼

一、亮出效果

最近一些軟體的搜題、智慧批改類的功能要下線。

退1024步講,要不要自己做一個自動批改的功能啊?萬一哪天孩子要用呢!

昨晚我做了一個夢,夢見我實現了這個功能,如下圖所示:

【神經網路】寫了個自動批改小孩作業的程式碼

功能簡介

:作對了,能打對號;做錯了,能打叉號;沒做的,能補上答案。

醒來後,我環顧四周,趕緊再躺下,希望夢還能接上。

二、實現步驟

基本思路

其實,搞定兩點就成,第一是能識別數字,第二是能切分數字。

首先得能認識5是5,這是前提條件,其次是能找到5、6、7、8這些數字區域的位置。

前者是

影象識別

,後者是

影象切割

對於影象識別,一般的套路是下面這樣的(CNN卷積神經網路):

對於影象切割,一般的套路是下面的這樣(橫向縱向投影法):

【神經網路】寫了個自動批改小孩作業的程式碼

既然思路能走得通,那麼咱們先搞影象識別。

準備資料->訓練資料並儲存模型->使用訓練模型預測結果

2。1 準備資料

對於男友,找一個油嘴滑舌的花花公子,不如找一個悶葫蘆IT男,親手把他培養成你期望的樣子。

咱們不用什麼官方的mnist資料集,因為那是官方的,不是你的,你想要新增±×÷它也沒有。

有些通用的資料集,雖然很強大,很方便,但是一旦放到你的場景中,效果一點也不如你的願。

只有訓練自己手裡的資料,然後自己用起來才順手。更重要的是,我們享受創造的過程。

假設,我們只給口算做識別,那麼我們需要的圖片資料有如下幾類:

如果能識別這些,基本上能滿足整數的加減乘除運算了。

好了,圖片哪裡來?!

是啊,圖片哪裡來?

嚇得我差點從夢裡醒來,500萬都規劃好該怎麼花了,居然雙色球還沒有選號!

夢裡,一個老者跟我說,圖片要自己生成。我問他如何生成,他呵呵一笑,消失在迷霧中……

仔細一想,其實也不難,打字我們總會吧,生成數字無非就是用程式碼把字寫在圖片上。

字之所以能展示,主要是因為有字型的支撐。

如果你用的是windows系統,那麼開啟KaTeX parse error: Undefined control sequence: \Windows at position 3: C:\ W i n d o w s \Fonts這個資料夾,你會發現好多字型。

【神經網路】寫了個自動批改小孩作業的程式碼

我們寫程式碼呼叫這些字型,然後把它列印到一張圖片上,是不是就有資料了。

而且這些資料完全是由我們控制的,想多就多,想少就少,想數字、字母、漢字、符號都可以,今天你搞出來數字識別,也就相當於你同時擁有了所有識別!想想還有點小激動呢!

看看,這就是打工和創業的區別。你用別人的資料相當於打工,你是不用操心,但是他給你什麼你才有什麼。自己造資料就相當於創業,雖然前期辛苦,你可以完全自己把握節奏,需要就加上,沒用就去掉。

2。1。1 準備字型

建一個fonts資料夾,從字型庫裡拷一部分字型放進來,我這裡是複製了13種字型檔案。

【神經網路】寫了個自動批改小孩作業的程式碼

好的,準備工作做好了,肯定很累吧,休息休息休息,一會兒再搞!

2。1。2 生成圖片

程式碼如下,可以直接執行。

上面純程式碼不到30行,相信大家應該能看懂!看不懂不是我的讀者。

核心程式碼就是畫文字。

翻譯一下就是:使用某字型在黑底圖片的(x,y)位置寫白色的char符號。

核心邏輯就是三層迴圈。

【神經網路】寫了個自動批改小孩作業的程式碼

如果程式碼你執行的沒有問題,最終會生成如下結果:

【神經網路】寫了個自動批改小孩作業的程式碼

【神經網路】寫了個自動批改小孩作業的程式碼

好了,資料準備好了。總共15個資料夾,每個資料夾下對應的各種字型各種傾斜角的字元圖片3900個(字元15類×字型13種×角度20個),圖片的大小是

24×24

畫素。

有了

資料

,我們就可以再進行下一步了,下一步是

訓練

使用

資料。

2。2 訓練資料

2。2。1 構建模型

你先看程式碼,外行感覺好深奧,內行偷偷地笑。

這個模型的序列是下面這樣的,作用是輸入一個圖片資料,經過各個層揉搓,最終預測出這個圖片屬於哪個分類。

【神經網路】寫了個自動批改小孩作業的程式碼

這麼多層都是幹什麼的,有什麼用?和衣服一樣,肯定是有用的,內衣、襯衣、毛衣、棉衣各有各的用處。

2。2。2 卷積層 Conv2D

各個職能部門的調查員,蒐集和整理某單位區域內的特定資料。我們輸入的是一個影象,它是由畫素組成的,這就是R e s c a l i n g ( 1。 / 255 , i n p u t s h a p e = ( 24 , 24 , 1 ) ) Rescaling(1。/255, input_shape=(24, 24, 1))Rescaling(1。/255,input shape=(24,24,1))中,input_shape輸入形狀是24*24畫素1個通道(彩色是RGB 3個通道)的影象。

【神經網路】寫了個自動批改小孩作業的程式碼

卷積層程式碼中的定義是Conv2D(24,3),意思是用3*3畫素的卷積核,去提取24個特徵。

我把圖轉到地圖上來,你就能理解了。以我大濟南的市中區為例子。

【神經網路】寫了個自動批改小孩作業的程式碼

卷積的作用就相當於從地圖的某級單位區域中收集多組特定資訊。比如以小區為單位去提取住宅數量、車位數量、學校數量、人口數、年收入、學歷、年齡等等24個維度的資訊。小區相當於卷積核。

提取完成之後是這樣的。

【神經網路】寫了個自動批改小孩作業的程式碼

第一次卷積之後,我們從市中區得到N個小區的資料。

卷積是可以進行多次的。

比如在小區卷積之後,我們還可在小區的基礎上再來一次卷積,在卷積就是街道了。

【神經網路】寫了個自動批改小孩作業的程式碼

透過再次以街道為單位卷積小區,我們就從市中區得到了N個街道的資料。

這就是卷積的作用。

透過一次次卷積,就把一張大圖,透過特定的方法捲起來,最終留下來的是固定幾組有目的資料,以此方便後續的評選決策。這是評選一個區的資料,要是評選濟南市,甚至山東省,也是這麼卷積。這和現實生活中評選文明城市、經濟強省也是一個道理。

2。2。3 池化層 MaxPooling2D

說白了就是四捨五入。

計算機的計算能力是強大的,比你我快,但也不是不用考慮成本。我們當然希望它越快越好,如果一個方法能省一半的時間,我們肯定願意用這種方法。

池化層乾的就是這個事情。池化的程式碼定義是這樣的M a x P o o l i n g 2 D ( ( 2 , 2 ) ) MaxPooling2D((2,2))MaxPooling2D((2,2)),這裡是最大值池化。其中(2,2)是池化層的大小,其實就是在2*2的區域內,我們認為這一片可以合成一個單位。

再以地圖舉個例子,比如下面的16個格子裡的資料,是16個街道的學校數量。

【神經網路】寫了個自動批改小孩作業的程式碼

為了進一步提高計算效率,少計算一些資料,我們用2*2的池化層進行池化。

【神經網路】寫了個自動批改小孩作業的程式碼

池化的方格是4個街道合成1個,新單位學校數量取成員中學校數量最大(也有取最小,取平均多種池化)的那一個。池化之後,16個格子就變為了4個格子,從而減少了資料。

這就是池化層的作用。

2。2。4 全連線層 Dense

弱水三千,只取一瓢。

在這裡,它其實是一個分類器。

我們構建它時,程式碼是這樣的D e n s e ( 15 ) Dense(15)Dense(15)。

它所做的事情,不管你前面是怎麼樣,有多少維度,到我這裡我要強行轉化為固定的通道。

比如識別字母a~z,我有500個神經元參與判斷,但是最終輸出結果就是26個通道(a,b,c,……,y,z)。

我們這裡總共有15類字元,所以是15個通道。給定一個輸入後,輸出為每個分類的機率。

【神經網路】寫了個自動批改小孩作業的程式碼

注意:上面都是二維的輸入,比如24×24,但是全連線層是一維的,所以程式碼中使用了l a y e r s 。 F l a t t e n ( ) layers。Flatten()layers。Flatten()將二維資料拉平為一維資料([[11,12],[21,22]]->[11,12,21,22])。

對於總體的模型,呼叫m o d e l 。 s u m m a r y ( ) model。summary()model。summary()列印序列的網路結構如下:

我們看到conv2d_5 (Conv2D) (None, 9, 9, 64) 經過2*2的池化之後變為max_pooling2d_5 (MaxPooling2 (None, 4, 4, 64)。(None, 4, 4, 64) 再經過F l a t t e n FlattenFlatten拉成一維之後變為(None, 1024),經過全連線變為(None, 128)再一次全連線變為(None, 15),15就是我們的最終分類。這一切都是我們設計的。

2。2。5 訓練資料

執行就完了。

執行之後會輸出如下資訊:

我們看到,第3遍時候,準確率達到100%了。最後結束的時候,我們發現資料夾checkpoint下多了幾個檔案:

上面那幾個檔案是訓練結果,訓練儲存之後就不用動了。後面可以直接用這些資料進行預測。

2。3 預測資料

終於到了享受成果的時候了。

我們找兩張圖片img1。png,img2。png,一張是數字6,一張是數字8,兩張圖放到程式碼同級目錄下,驗證一下識別效果如何。

圖片要透過cv2。imread(‘img1。png’,0) 轉化為二維陣列結構,0引數是灰度圖片。經過處理後,圖片轉成的陣列是如下所示(24,24)的結構:

【神經網路】寫了個自動批改小孩作業的程式碼

我們要同時驗證兩張圖,所以把兩張圖再組成imgs放到一起,imgs的結構是(2,24,24)。

下面是構建模型,然後載入權重。透過呼叫predicts = model。predict(imgs)將imgs傳遞給模型進行預測得出predicts。

predicts的結構是(2,15),數值如下面所示:

[[ 16。134243 -12。10675 -1。1994154 -27。766754 -43。4324 -9。633694 -12。214878 1。6287893 2。562174 3。2222707 13。834648 28。254173 -6。102874 16。76582 7。2586184] [ 5。022571 -8。762314 -6。7466817 -23。494259 -30。170597 2。4392672 -14。676962 5。8255725 8。855118 -2。0998626 6。820853 7。6578817 1。5132296 24。4664 2。4192357]]

意思是有2個預測結果,每一個圖片的預測結果有15種可能。

然後根據 index = np。argmax(predict) 找出最大可能的索引。

根據索引找到字元的數值結果是[‘6’, ‘8’]。

下面是資料在記憶體中的監控:

可見,我們的預測是準確的。

下面,我們將要把圖片中數字切割出來,進行識別了。

之前我們準備了資料,訓練了資料,並且拿圖片進行了識別,識別結果正確。

到目前為止,看來問題不大……沒有大問題,有問題也大不了。

下面就是把圖片進行切割識別了。

下面這張大圖片,怎麼把它搞一搞,搞成單個小數字的圖片。

【神經網路】寫了個自動批改小孩作業的程式碼

2。4 切割影象

上帝說要有光,就有了光。

於是,當光投過來時,物體的背後就有了影。

我們就知道了,有影的地方就有東西,沒影的地方是空白。

【神經網路】寫了個自動批改小孩作業的程式碼

這就是投影。

這個簡單的道理放在影象切割上也很實用。

我們把文字的畫素做個投影,這樣我們就知道某個區間有沒有文字,並且知道這個區間文字是否集中。

下面是示意圖:

【神經網路】寫了個自動批改小孩作業的程式碼

2。4。1 投影大法

最有效的方法,往往都是用迴圈實現的。

要計算投影,就得一個畫素一個畫素地數,檢視有幾個畫素,然後記錄下這一行有N個畫素點。如此迴圈。

首先匯入包:

比如說要看垂直方向的投影,程式碼如下:

最終得到是這樣的結構:[0, 79, 67, 50, 50, 50, 109, 137, 145, 136, 125, 117, 123, 124, 134, 71, 62, 68, 104, 102, 83, 14, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, ……38, 44, 56, 106, 97, 83, 0, 0, 0, 0, 0, 0, 0]表示第幾行總共有多少個畫素點,第1行是0,表示是空白的白紙,第2行有79個畫素點。

如果我們想要從視覺呈現出來怎麼處理呢?那可以把它立起來拉直畫出來。

【神經網路】寫了個自動批改小孩作業的程式碼

我們來試驗一下效果:

我們將上面的原圖片命名為question。jpg放到程式碼同級目錄。

二值化並反色後的變化如下所示:

【神經網路】寫了個自動批改小孩作業的程式碼

上面的操作很有作用,透過二值化,過濾掉雜色,透過反色將黑白對調,原來白紙區域都是255,現在黑色都是0,更利於計算。

計算投影並展示的程式碼:

下面的圖是上面圖在Y軸上的投影

【神經網路】寫了個自動批改小孩作業的程式碼

從視覺上看,基本上能區分出來哪一行是哪一行。

2。4。2 根據投影找區域

最有效的方法,往往還得用迴圈來實現。

上面投影那張圖,你如何計算哪裡到哪裡是一行,雖然肉眼可見,但是計算機需要規則和演算法。

透過投影,計算哪些區域在一定範圍內是連續的,如果連續了很長時間,我們就認為是同一區域,如果斷開了很長一段時間,我們就認為是另一個區域。

【神經網路】寫了個自動批改小孩作業的程式碼

透過這項操作,我們就可以獲得Y軸上某一行的上下兩個邊界點的座標,再結合圖片寬度,其實我們也就知道了一行圖片的四個頂點的座標了mark_boxs存下的是[坐,上,右,下]。

【神經網路】寫了個自動批改小孩作業的程式碼

如果呼叫如下程式碼:

我們獲取到的是所有識別出來每行圖片的座標,格式是這樣的:[[0, 26, 596, 52], [0, 76, 596, 103], [0, 130, 596, 155], [0, 178, 596, 207], [0, 233, 596, 259], [0, 282, 596, 311], [0, 335, 596, 363], [0, 390, 596, 415]]

2。4。3 根據區域切圖片

最有效的方法,最終也得用迴圈來實現。這也是計算機體現它強大的地方。

這一步驟是拿著方框,從大圖上用小刀劃下小圖,核心程式碼是img_org[box[1]:box[3], box[0]:box[2]]圖片裁剪,引數是陣列的[上:下,左:右],獲取的資料還是二維的陣列。

如果儲存下來:

圖片是下面這樣的:

【神經網路】寫了個自動批改小孩作業的程式碼

2。4。4 迴圈可去油膩

還是迴圈。橫著行我們掌握了,那麼針對每一行圖片,我們豎著切成三塊是不是也會了,一個道理。

需要注意的是,橫豎是稍微有區別的,下面是上圖的x軸投影。

橫著的時候,字與字之間本來就是有空隙的,然後塊與塊也有空隙,這個空隙的度需要掌握好,以便更好地區分出來是字的間距還是算式塊的間距。

幸好,有種方法叫膨脹。

膨脹對人來說不積極,但是對於技術來說,不管是膨脹(dilate),還是腐蝕(erode),只要能達到目的,都是好的。

膨脹之後再投影,就很好地區分出了塊。

根據投影裁剪之後如下圖所示:

同理,不膨脹可擷取單個字元。

這樣,這是一塊區域的字元。

一行的,一頁的,透過迴圈,都可以截取出來。

有了圖片,就可以識別了。有了位置,就可以判斷識別結果的關係了。

下面提供一些程式碼,這些程式碼不全,有些函式你可能找不到,但是思路可以參考,詳細的程式碼可以去我的github去看。

最後返回的值是3個,all_mark_boxs是標記的字元位置的座標集合。[左,上,右,下]是指某個字元在一張大圖裡的座標,列印一下是這樣的:

[[[[19, 26, 34, 53], [36, 26, 53, 53], [54, 26, 65, 53], [66, 26, 82, 53], [84, 26, 101, 53], [102, 26, 120, 53], [120, 26, 139, 53]], [[213, 26, 229, 53], [231, 26, 248, 53], [249, 26, 268, 53], [268, 26, 285, 53]], [[408, 26, 426, 53], [427, 26, 437, 53], [438, 26, 456, 53], [456, 26, 474, 53], [475, 26, 492, 53]]], [[[20, 76, 36, 102], [38, 76, 48, 102], [50, 76, 66, 102], [67, 76, 85, 102], [85, 76, 104, 102]], [[214, 76, 233, 102], [233, 76, 250, 102], [252, 76, 268, 102], [270, 76, 287, 102]], [[411, 76, 426, 102], [428, 76, 445, 102], [446, 76, 457, 102], [458, 76, 474, 102], [476, 76, 493, 102], [495, 76, 511, 102]]]]

它是有結構的。它的結構是:

【神經網路】寫了個自動批改小孩作業的程式碼

all_char_imgs這個返回值,裡面是上面座標結構對應位置的圖片。img_o就是原圖了。

2。5 識別

迴圈,迴圈,還是TM迴圈!

對於識別,2。3 預測資料已經講過了,那次是對於2張獨立圖片的識別,現在我們要對整張大圖切分後的小圖集合進行識別,這就又用到了迴圈。

翠花,上程式碼!

上面程式碼做的就是以塊為單位,傳遞給神經網路進行預測,然後返回識別結果。

針對這張圖,我們來進行裁剪和識別。

【神經網路】寫了個自動批改小孩作業的程式碼

看底部的最後一行

結果是索引,不是真實的字元,我們根據字典10: ‘=’, 11: ‘+’, 12: ‘-’, 13: ‘×’, 14: ‘÷’轉換過來之後結果是:

和圖片是對應的:

2。6 計算並反饋

迴圈……

我們獲取到了10-2=、8-6=2,也獲取到了他們在原圖的位置座標[左,上,右,下],那麼怎麼把結果反饋到原圖上呢?

往往到這裡就剩最後一步了。

再來溫習一遍需求:作對了,能打對號;做錯了,能打叉號;沒做的,能補上答案。

實現分兩步走:計算(是作對做錯還是沒錯)和反饋(把預期結果寫到原圖上)。

2。6。1 計算 python有個函式很強大,就是eval函式,能計算字串算式,比如直接計算eval(“5+3-2”)。

所以,一切都靠它了。

執行之後獲得的結果是:

2。6。2 反饋

有了結果之後,把結果寫到圖片上,這是最後一步,也是最簡單的一步。

但是實現起來,居然很繁瑣。

得找座標吧,得計算結果呈現的位置吧,我們還想標記不同的顏色,比如對了是綠色,錯了是紅色,補齊答案是灰色。

下面程式碼是在一個圖img上,把文字內容text畫到(left,top)位置,以特定顏色和大小。

結合著切圖的資訊、計算的資訊,下面程式碼提供思路參考:

結果是下面這樣的:

【神經網路】寫了個自動批改小孩作業的程式碼

注意

同級新建fonts資料夾裡複製一些字型檔案,從這裡找C:\Windows\Fonts,幾十個就行。

get_character_pic。py 生成字型

cnn。py 訓練資料

main。py 裁剪指定圖片並識別,素材圖片新建imgs資料夾,在imgs/question。png下,結果檔案儲存在imgs/result。png。

注意如果識別不成功,很可能是question。png的字型你沒有訓練(這幅圖的字型是方正書宋簡體,但是你只訓練了楷體),這時候可以使用楷體自己編一個算式圖。