生成字符識(shí)別數(shù)據(jù)集

做文字OCR首先需要做的是生成字符圖片用于訓(xùn)練。沒(méi)有訓(xùn)練集,一切機(jī)器學(xué)習(xí)都免談了。因此,我們要做的第一件事情就是人工生成可用的數(shù)據(jù)集。

先引入我們需要的頭文件:

from PIL import Image, ImageDraw, ImageFont
from io import StringIO
import random
import os

制作數(shù)據(jù)集,首先要做的是把字符變成圖片。在網(wǎng)上找了很多資料,都是用Pygame生成字符圖片,再用PIL來(lái)讀入,進(jìn)行對(duì)應(yīng)的操作。因?yàn)镻ygame不能直接轉(zhuǎn)PIL,所以要存入磁盤,再用PIL讀出。為了提高速率,直接在內(nèi)存中進(jìn)行操作,網(wǎng)上的資料都是把Pygame生成的圖片讀入StringIO里面,再讀出。但是網(wǎng)上相關(guān)的博客,似乎都是復(fù)制了某個(gè)人的博客,而那份博客是用Python2寫的。當(dāng)我把其改成Python3的時(shí)候,StringIO的地方總是報(bào)錯(cuò),讓我很心煩。

后來(lái)我發(fā)現(xiàn),PIL可以直接生成字符圖片,并且博客上面所說(shuō)的問(wèn)題并沒(méi)有出現(xiàn),于是就可以毅然地拋棄Pygame了。

代碼很簡(jiǎn)單:

# Create Image with text
def addText(text, font):
    # 生成純白的50*50的圖片
    im = Image.new("RGB", (50, 50), (255, 255, 255))
    dr = ImageDraw.Draw(im)
    area = (random.randint(10, 15), 10)

    # 將字體畫入圖片
    dr.text(area, text, font=font, fill="#000000")
    return im

這里的area是字體的左上角的坐標(biāo)。因?yàn)槲覀兩傻淖煮w后面需要進(jìn)行一點(diǎn)的扭曲。因此在這里設(shè)置隨機(jī)數(shù)就可以進(jìn)行相應(yīng)的平移操作。

這里需要傳入font參數(shù)。font是這樣生成的:

font = ImageFont.truetype(os.path.join("ttf", font_path), random.randint(18, 20))

os.path.join("ttf", font_path)表示./ttf/font_path。其中ttf是我存字符文件的文件夾。因?yàn)樽詈笠S機(jī)字符,因此字符的路徑font_path是在字符文件中隨機(jī)抽取。后面是字體的大小。在這里使用隨機(jī)數(shù)就可以直接實(shí)現(xiàn)字體的縮放了。

接下來(lái)對(duì)生成的圖片進(jìn)行處理。處理有旋轉(zhuǎn)和扭曲。

需要注意的是,PIL的旋轉(zhuǎn)默認(rèn)黑色為底色,因此直接旋轉(zhuǎn)的結(jié)果,就是四個(gè)角留下了黑色。而且PIL并沒(méi)有提供對(duì)應(yīng)的功能可以選擇底色。因?yàn)槲覀兿氩涣粝潞谏乃慕?,就只能在旋轉(zhuǎn)之后把圖片的邊裁剪掉了。這就是為什么之前生成50*50的圖片了,這樣我們裁剪之后,就變成了30 * 30。

def imageProcess(image):
    # 圖像旋轉(zhuǎn)
    image = image.rotate(random.randint(-5, 5))

    # 圖像扭曲
    params = [1 - float(random.randint(1, 2)) / 100,
            0,
            0,
            0,
            1 - float(random.randint(1, 10)) / 100,
            float(random.randint(1, 2)) / 500,
            0.001,
            float(random.randint(1, 2)) / 500]

    image = image.transform((50, 50), Image.PERSPECTIVE, params)
    
    # 裁剪
    image = image.crop([10, 10, 40, 40])

    # 轉(zhuǎn)灰度圖
    image = image.convert('L')
    return image

圖像扭曲里面的params的參數(shù),我到現(xiàn)在都沒(méi)有找到文檔把它弄清楚。但是按這樣扭曲,出來(lái)的效果特別好。所以就不在意這些細(xì)節(jié)了。

如果我們需要把圖片變成其他尺寸,可以使用PIL中的resize,例如在我需要100*100。我就可以:

image = image.resize((100, 100), Image.ANTIALIAS)

后面的參數(shù)默認(rèn)是NEAREST。但是有NEAREST、BILINEAR、BICUBIC、ANTIALIAS。表示圖片縮放的四種算法。ANTIALIAS效果是比較好的。

最后,我們對(duì)圖片進(jìn)行二值化。這一步是我額外加的一步。因?yàn)樵谖沂褂谜鎸?shí)數(shù)據(jù)檢驗(yàn)我的機(jī)器學(xué)習(xí)的模型的時(shí)候,我會(huì)對(duì)真實(shí)數(shù)據(jù)進(jìn)行二值化。那么我在訓(xùn)練數(shù)據(jù)上面二值化,則它會(huì)更接近真實(shí)數(shù)據(jù)。

由于圖片比較簡(jiǎn)單,直接二值化,閾值設(shè)置為200效果就很好了。

def binarizing(image, threshold=200):
    pixdata = image.load()
    w, h = image.size
    for y in range(h):
        for x in range(w):
            if pixdata[x, y] < threshold:
                pixdata[x, y] = 0
            else:
                pixdata[x, y] = 255
    return image

一開(kāi)始,我以為機(jī)器學(xué)習(xí)的速度的瓶頸在IO操作,因?yàn)槲易隽艘患?。就是?500個(gè)漢字,先把30 * 30的圖片,一共2500張,組合成50 * 50的圖片,再保存。這樣讀入2500個(gè)漢字只需要一次IO操作了。代碼如下:

def saveImage(images, font_path):
    width = 30 * 40
    height = 30 * 50
    merge_image = Image.new('L', (width, height), 0xffffff)
    xPos, yPos = 0, 0
    for image in images:
        merge_image.paste(image, (xPos, yPos))
        xPos = xPos + 30
        if (xPos == width):
            xPos = 0
            yPos = yPos + 30
    imageName = font_path + '.png'
    merge_image.save(os.path.join('words', imageName))

也就是先生成一張純白的圖片,再一張張paste上去。但是后來(lái)發(fā)現(xiàn),其實(shí)更大的瓶頸在于把這么大的圖片分割。因?yàn)閳D片的操作,肯定會(huì)比直接IO慢嘛。所以我就放棄了這種方法。

但是生成的圖片乍一看還是很好看的。

生成的結(jié)果

參考資料:
Python 3 生成手寫體數(shù)字?jǐn)?shù)據(jù)集

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容