机器学习入门(二)--CASIA-HWDB(tensorflow)微调efficientNetB0实现手写字体的识别

对数据集做一个resize后会导致某些图片失真,可以采用不失真的resize方法,能提高准确率。

1.准备数据集

CASIA-HWDB2.x(offline)数据集下载地址:http://www.nlpr.ia.ac.cn/databases/handwriting/Download.html
将官网下载的HWDB数据进行解压,文件夹名称作为label。代码如下:
注意分开test和train

import struct
import os
from PIL import Image
DATA_PATH="HWDB1.1tst_gnt" #gnt数据文件路径
IMG_PATH="test"#解析后的图片存放路径
files=os.listdir(DATA_PATH) #os.listdir() 方法用于返回指定的文件夹包含的文件或文件夹的名字的列表。
num=0
for file in files:
    tag = []
    img_bytes = []
    img_wid = []
    img_hei = []
    f=open(DATA_PATH+"/"+file,"rb")
    while f.read(4):
        tag_code=f.read(2)
        tag.append(tag_code)
        width=struct.unpack(', bytes(f.read(2)))
        height=struct.unpack(',bytes(f.read(2)))
        img_hei.append(height[0])
        img_wid.append(width[0])
        data=f.read(width[0]*height[0])
        img_bytes.append(data)
    f.close()
    for k in range(0, len(tag)):
        im = Image.frombytes('L', (img_wid[k], img_hei[k]), img_bytes[k])
        if os.path.exists(IMG_PATH + "/" + tag[k].decode('gbk')):
            im.save(IMG_PATH + "/" + tag[k].decode('gbk') + "/" + str(num) + ".jpg")
        else:
            os.mkdir(IMG_PATH + "/" + tag[k].decode('gbk'))
            im.save(IMG_PATH + "/" + tag[k].decode('gbk') + "/" + str(num) + ".jpg")
    num = num + 1
print(tag.__len__())

files=os.listdir(IMG_PATH)
n=0
f=open("label.txt","w") #创建用于训练的标签文件
for file in files:
    files_d=os.listdir(IMG_PATH+"/"+file)
    for file1 in files_d:
        f.write(file+"/"+file1+" "+str(n)+"\n")
    n=n+1

解压后可以得到
机器学习入门(二)--CASIA-HWDB(tensorflow)微调efficientNetB0实现手写字体的识别_第1张图片
机器学习入门(二)--CASIA-HWDB(tensorflow)微调efficientNetB0实现手写字体的识别_第2张图片

2.微调efficientNetB0网络

导入

from tensorflow.keras.applications import EfficientNetB0

进行微调

如果没有BN层就取消冻结权重参数可以有效解决 训练后进行单张预测时BN层归一化的值无法接近训练时的归一化值,以此达到单张预测也可达到评估时的准确率。

	model = EfficientNetB0(include_top=False, weights='imagenet', input_tensor=x, input_shape=input_shape)
    '''
    include_top:是否在网络顶部包含完全连接的层
    weights:无(随机初始化)、“imagenet”(imagenet上的预训练)或要加载的权重文件路径之一
    input_tensor:可选Keras张量(即layers.Input()的输出),用作模型的图像输入。即图像增广后作为输入
    input_shape:输出
    '''
    model.trainable = False #冻结
    #施加全局平均值池化 GAP
    x = layers.GlobalAveragePooling2D()(model.output)
    #批量化 BN
    x = layers.BatchNormalization()(x)
    #防止过拟合
    x = layers.Dropout(0.2)(x)
    #全连接层 激活函数softmax
    outputs = layers.Dense(num_classes, activation='softmax')(x)

    #封装自己的输入输出进去model
    model = tf.keras.Model(inputs, outputs)

    #对某些层进行修改(如果没有BN层就取消冻结权重参数)
    for layer in model.layers[-20:]:
        if not isinstance(layer, layers.BatchNormalization):
            layer.trainable = True
    model.compile(
        optimizer=tf.keras.optimizers.Adam(learning_rate=0.0001),
        loss='sparse_categorical_crossentropy',
        metrics=['sparse_categorical_accuracy']
    )
    return model

3.训练

对于输入的图片做一个图片增强(旋转,修改对比度等)

img_augmentation = Sequential([ #创建一个Sequential模型
        # 随机旋转每个图像 默认情况下,仅在训练期间应用随机旋转。
        preprocessing.RandomRotation(factor=0.15),
        # 在训练期间随机翻译每个图像。
        preprocessing.RandomTranslation(height_factor=0.1, width_factor=0.1),
        # 随机水平和垂直翻转每个图像。
        preprocessing.RandomFlip(),
        # 通过随机因子调整一个或多个图像的对比度
        preprocessing.RandomContrast(factor=0.1),
    ])

完整代码如下:

import json
import datetime
from pathlib import Path

import tensorflow as tf
import matplotlib.pyplot as plt
from tensorflow.keras import layers
from tensorflow.keras.models import Sequential
from tensorflow.keras.applications import EfficientNetB0
from tensorflow.keras.layers.experimental import preprocessing
from tensorflow.keras.preprocessing import image_dataset_from_directory

# 数据集
img_size = 112 #输入尺寸
batch_size = 32 #批次
image_size = (112, 112) #图片大小
input_shape = image_size + (3,) #(112, 112, 3) 3通道

train_dir = 'train'  # 训练集目录
validation_dir = 'test'  # 验证集目录

train_dataset = image_dataset_from_directory(train_dir, batch_size=batch_size, image_size=image_size)#读取数据和标签
'''
tf.keras.preprocessing.image_dataset_from_directory:从目录中的图像文件生成一个 tf.data.Dataset
labels='inferred'(默认):标签从目录结构生成
'''
validation_dataset = image_dataset_from_directory(validation_dir, batch_size=batch_size, image_size=image_size)
AUTOTUNE = tf.data.experimental.AUTOTUNE
'''
多线程并行化
tf.data模块运行时,使用多线程进行数据通道处理,从而实现并行,这种操作几乎是透明的。
tf.data.experimental.AUTOTUNE,则根据可用的CPU动态设置并行调用的数量
'''
train_data = train_dataset.prefetch(AUTOTUNE)
validation_data = validation_dataset.prefetch(AUTOTUNE)#Dataset.prefetch() 开启预加载数据,使得在 GPU 训练的同时 CPU 可以准备数据


class_names = train_dataset.class_names  # 类别自动根据目录命名
json.dump(class_names, open('class_names.json', mode='w'))  # 保存分类信息
num_classes = len(class_names)
print('共{}类'.format(num_classes))#多少类


def build_model(num_classes):
    """创建并编译模型"""
    inputs = layers.Input(shape=input_shape) #构建网络的第一层——输入层 告诉尺寸多少
    img_augmentation = Sequential([ #创建一个Sequential模型
        # 随机旋转每个图像 默认情况下,仅在训练期间应用随机旋转。
        preprocessing.RandomRotation(factor=0.15),
        # 在训练期间随机翻译每个图像。
        preprocessing.RandomTranslation(height_factor=0.1, width_factor=0.1),
        # 随机水平和垂直翻转每个图像。
        preprocessing.RandomFlip(),
        # 通过随机因子调整一个或多个图像的对比度
        preprocessing.RandomContrast(factor=0.1),
    ])
    x = img_augmentation(inputs) #图像增广
    #实例化EfficientNetB0体系结构
    model = EfficientNetB0(include_top=False, weights='imagenet', input_tensor=x, input_shape=input_shape)
    '''
    include_top:是否在网络顶部包含完全连接的层
    weights:无(随机初始化)、“imagenet”(imagenet上的预训练)或要加载的权重文件路径之一
    input_tensor:可选Keras张量(即layers.Input()的输出),用作模型的图像输入。即图像增广后作为输入
    input_shape:输出
    '''
    model.trainable = False #冻结
    #施加全局平均值池化 GAP
    x = layers.GlobalAveragePooling2D()(model.output)
    #批量化 BN
    x = layers.BatchNormalization()(x)
    #防止过拟合
    x = layers.Dropout(0.2)(x)
    #全连接层 激活函数softmax
    outputs = layers.Dense(num_classes, activation='softmax')(x)

    #封装自己的输入输出进去model
    model = tf.keras.Model(inputs, outputs)

    #对某些层进行修改
    for layer in model.layers[-20:]:
        if not isinstance(layer, layers.BatchNormalization):
            layer.trainable = True
    model.compile(
        optimizer=tf.keras.optimizers.Adam(learning_rate=0.0001),
        loss='sparse_categorical_crossentropy',
        metrics=['sparse_categorical_accuracy']
    )
    return model

# 回调函数
Path('models').mkdir(parents=True, exist_ok=True)
filepath = 'models/model-B0.h5'
callbacks = [
    tf.keras.callbacks.ModelCheckpoint(filepath=filepath, monitor='val_loss', verbose=1, save_best_only=True),  # 保存模型
    tf.keras.callbacks.ReduceLROnPlateau(monitor='val_loss', factor=0.1, patience=3, verbose=1),  # 训练多次没有提升就降低学习率
    tf.keras.callbacks.EarlyStopping(monitor='val_loss', patience=25, verbose=1),  # 训练多次没有提升就停止
]
model = tf.keras.models.load_model('models/model-B0.h5')
#model = build_model(num_classes)
history = model.fit(train_data, epochs=100, validation_data=validation_data, callbacks=callbacks)
print(filepath)

# 绘制训练曲线
# plt.plot(history.history['sparse_categorical_accuracy'])
# plt.plot(history.history['val_sparse_categorical_accuracy'])
# plt.title('model accuracy')
# plt.ylabel('accuracy')
# plt.xlabel('epoch')
# plt.legend(['train', 'validation'], loc='upper left')
# plt.figure()
# plt.plot(history.history['loss'])
# plt.plot(history.history['val_loss'])
# plt.title('model loss')
# plt.ylabel('loss')
# plt.xlabel('epoch')
# plt.legend(['train', 'validation'], loc='upper left')
# plt.show()

训练后的模型保存在models下(需要手动创建文件夹)
我只训练了30批次左右,准确率达到百分之80多。

4.进行预测

1.在test文件夹中随机抽取图片进行预测并使用plt可视化(使用爬虫对识别的汉字进行翻译)

翻译功能如下:

# -*- coding:utf-8 -*-
import requests
def google_translate(content):
    data = {
    'doctype': 'json',
    'type': 'AUTO',
    'i':content
    }
    url = "http://fanyi.youdao.com/translate"
    r = requests.get(url,params=data)
    result = r.json()
    translate_result = result['translateResult'][0][0]["tgt"]
    return translate_result

机器学习入门(二)--CASIA-HWDB(tensorflow)微调efficientNetB0实现手写字体的识别_第3张图片
预测代码:

import time
import json
import random
import pathlib
from translate import google_translate
import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt
from tensorflow.python.keras.preprocessing import image

# 加载类名
class_names = json.load(open('class_names.json'))
print(class_names)

# 加载模型
model = tf.keras.models.load_model('model-B0.h5')

# 预测
image_size = 112  # 图像大小
paths = list(pathlib.Path('test').rglob('*'))  # 测试集所有文件
correct = 0
sum = 0
while True:
    path = random.choice(paths)
    x = image.load_img(path=path, target_size=(image_size, image_size))
    plt.imshow(x)
    plt.ion()
    x = image.img_to_array(x)
    plt.rcParams['font.sans-serif'] = 'SimHei'  # 设置字体显示中文
    plt.rcParams['axes.unicode_minus'] = False  # 正常显示字符(x轴)
    x = np.expand_dims(x, axis=0)
    y = model.predict(x)  # 预测

    # 置信度
    for i in np.argsort(y[0])[::-1]:
        j= 0
        #print('{}: {:.2f}%'.format(class_names[i], y[0][i] * 100), end=' ')
        plt.title("{}:{} {}".format(path.parent.name, class_names[i],google_translate(class_names[i])))
        print("{}:{} {}".format(path.parent.name, class_names[i],google_translate(class_names[i])))

        plt.pause(2)
        j+=1
        plt.show()
        plt.close()
        if path.parent.name == class_names[i]:
            correct+=1
        if j == 1:
            sum +=1
            break
    print("预测个数:{},正确个数:{},准确率:{:.2f}%".format(sum,correct,(correct/sum)*100))
    # plt.title("{}:{}".format(path.parent.name,class_names[y[0][0]]))
    # plt.show()
    # q = input('回车继续,q退出')
    # if q == 'q':
    #     break

机器学习入门(二)--CASIA-HWDB(tensorflow)微调efficientNetB0实现手写字体的识别_第4张图片

2.对画板的汉字进行预测(带翻译)

import json
import tensorflow as tf
from tensorflow.python.keras.preprocessing import image
from translate import google_translate
import matplotlib.pyplot as plt
import numpy as np
class_names = json.load(open('class_names.json'))
model = tf.keras.models.load_model('model-B0.h5')

image_size = 112  # 图像大小
path = '2.png'
x = image.load_img(path=path, target_size=(image_size, image_size))
plt.imshow(x)
x = image.img_to_array(x)
plt.rcParams['font.sans-serif'] = 'SimHei'  # 设置字体显示中文
plt.rcParams['axes.unicode_minus'] = False  # 正常显示字符(x轴)
x = np.expand_dims(x, axis=0)
y = model.predict(x)
for i in np.argsort(y[0])[::-1]:
    j = 0
    plt.title("{} {}".format(class_names[i],google_translate(class_names[i])))
    print('{}: {}'.format(class_names[i],google_translate(class_names[i])), end=' ')
    #print(class_names[i],y[0][i] * 100)
    j += 1
    plt.show()
    if j == 1:
        break

机器学习入门(二)--CASIA-HWDB(tensorflow)微调efficientNetB0实现手写字体的识别_第5张图片
机器学习入门(二)--CASIA-HWDB(tensorflow)微调efficientNetB0实现手写字体的识别_第6张图片
机器学习入门(二)--CASIA-HWDB(tensorflow)微调efficientNetB0实现手写字体的识别_第7张图片

3.对长文字进行分割(投影分割法)后逐个识别并全部可视化(带翻译)

将长文字图片使用投影分割成单个文字后存放于 saveImages = 'process/' 文件夹下后,对文件夹内所有图片进行预测

import cv2
import json
import tensorflow as tf
from tensorflow.python.keras.preprocessing import image
from handwrite_tensorflow.translate import google_translate
import matplotlib.pyplot as plt
import numpy as np
import os
import tkinter as tk
from tkinter import filedialog
import shutil
import sys   #导入sys模块
sys.path.append("..")
#显示图片
def cv_show(name, mat):
    cv2.imshow(name, mat)
    cv2.waitKey(0)

#获取图像的在垂直方向上的投影
def getH(img):
    hpro = np.zeros(img.shape, np.uint8)    #用于存放投影图像
    (h, w) = img.shape                      #获取整张图像的高和宽
    h_ = [0]*h                              #长度与图像高度一致的数组,存放每一行白色像素的总数

    #循环统计图像每一行白色像素的个数
    for y in range(h):
        for x in range(w):
            if img[y, x] == 255:
                h_[y] += 1
    #遍历垂直像素点绘制出垂直投影的图像
    for y in range(h):
        for x in range(h_[y]):
            hpro[y, x] = 255

    #cv_show('hpro', hpro)
    #cv2.imwrite('{}h.jpg'.format(saveImages), hpro)
    return h_

#获取图像的在水平方向上的投影
def getW(img):
    wpro = np.zeros(img.shape, np.uint8)    #用于存放投影图像
    (h, w) = img.shape                      #获取图像的高和宽
    w_ = [0]*w                              #长度与图像宽度一致的数组

    #循环统计图像每一列白色像素的个数
    for x in range(w):
        for y in range(h):
            if img[y, x] == 255:
                w_[x] += 1
    #遍历水平像素点绘制出水平投影的图像
    for x in range(w):
        for y in range(h-w_[x],h):      #w_[x]表示当前列的白色像素点总数
            wpro[y, x] = 255

    #cv_show('wpro', wpro)
    #cv2.imwrite('{}w.jpg'.format(saveImages), wpro)

    return w_

#切割
def cut(loadImages):
    #t_img  是读进来的原始图片
    #img    是进行膨胀和二值化后的图片
    k=0 #文件保存命名变量
    t_img = cv2.imread(loadImages)
    plt.imshow(t_img)
    #灰度化、二值化
    img = cv2.cvtColor(t_img, cv2.COLOR_BGR2GRAY)
    retval, img = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY_INV)
    #滤波
    img = cv2.blur(img, (3,3))
    #膨胀
    kernel = np.ones((3, 3), np.uint8)
    img = cv2.dilate(img, kernel, iterations=10)

    #获取图片高度和宽度
    (h, w) = img.shape

    #获取垂直投影的高度
    H = getH(img)

    hstart = 0
    H_Start = []                            #每个字在垂直方向上的起始像素点
    H_End = []                              #每个字在垂直方向上的结束像素点
    for i in range(len(H)):
        #字开始
        if H[i]>50 and hstart==0:
            hstart=1
            H_Start.append(i)
        #字结束
        if H[i]<=50 and hstart==1:
            hstart=0
            H_End.append(i)

    #遍历每一行
    for i in range(len(H_Start)):
        #截取出一行
        cropImg = img[H_Start[i]:H_End[i], 0:w]
        #获取这一行水平投影的宽度
        W = getW(cropImg)

        wstart = 0
        wend   = 0
        W_Start = 0                        #当前字在水平方向上的起始像素点
        W_End = 0                          #当前字在水平方向上的结束像素点

        for j in range(len(W)):
            # 字开始
            if W[j] > 10 and wstart == 0:
                wstart = 1
                W_Start =j
                wend = 0
            # 字结束
            if W[j] <= 10 and wstart == 1:
                wstart = 0
                W_End =j
                wend = 1
            if wend == 1:
                wstart = 0
                wend = 0
                k+=1
                h1 = H_Start[i]
                h2 = H_End[i]
                w1 = W_Start
                w2 = W_End
                #截取出当前字并保存
                temp = t_img[h1:h2, w1:w2]
                #cv_show('1', temp)
                cv2.imwrite('{}{}.jpg'.format(saveImages, k), temp)

#识别文字
def pre(path):
    image_size = 112  # 图像大小
    imagelist = os.listdir(path)
    imagelist.sort()
    #print(imagelist)
    str = ''
    #读取文件夹下所有图片
    for imgname in imagelist:
        if (imgname.endswith(".jpg")):
            x = image.load_img(path=(path+imgname), target_size=(image_size, image_size))
            x = image.img_to_array(x)
            plt.rcParams['font.sans-serif'] = 'SimHei'  # 设置字体显示中文
            plt.rcParams['axes.unicode_minus'] = False  # 正常显示字符(x轴)
            x = np.expand_dims(x, axis=0)
            y = model.predict(x)
            for i in np.argsort(y[0])[::-1]:
                j = 0
                str+= class_names[i]
                #plt.title("{}".format(class_names[i]))
                #print('{}: {:.2f}%'.format(class_names[i], y[0][i] * 100), end=' ')
                # print(class_names[i],y[0][i] * 100)
                j += 1
                #plt.show()
                if j == 1:
                    break
    print('{} {}'.format(str,google_translate(str)))
    plt.title('{} {}'.format(str,google_translate(str)))
    plt.show()

if __name__ == "__main__":
    class_names = json.load(open('../class_names.json'))
    model = tf.keras.models.load_model('../model-B0.h5')
    saveImages = 'process/'
    #初始化文件夹
    shutil.rmtree(saveImages)
    os.mkdir(saveImages)
    while True:#死循环 点击取消退出
        '''打开选择文件夹对话框'''
        root = tk.Tk()
        root.withdraw()
        loadImages = filedialog.askopenfilename()  # 获得选择好的文件路径
        list(loadImages)  # 不加读取不了 会报错
        cut(loadImages)
        pre(saveImages)

机器学习入门(二)--CASIA-HWDB(tensorflow)微调efficientNetB0实现手写字体的识别_第8张图片
机器学习入门(二)--CASIA-HWDB(tensorflow)微调efficientNetB0实现手写字体的识别_第9张图片

你可能感兴趣的:(机器学习+opencv,tensorflow,深度学习,神经网络)