开发十年,就只剩下这套Java开发体系了 >>>
基于前面使用opencv实现舌头模型检测后,本次主要针对模型参数调优,提高图片识别率。
之前的模型精准率很高,但召回率不一定为1,有时候舌头图片并没有被圈出。所以我们需要调整参数让模型吧舌头都识别出来,再通过LeNet模型做后续识别,将不是舌头的剔除掉。
首先修改opencv训练后的模型参数,调低scaleFactor值,让模型更敏感,圈出更多的图片(舌头+额外的其他非舌头图片)
在默认参数(scaleFactor=1.38, minNeighbors=4, minSize=(20,20),)下,
Cascades模型的识别率偏低但精度高,
在该参数下,在数据规模为500的测试集中,一共切割出446张小图,覆盖了500张图片中的398张图片,剩余的48张小图为识别错误图片,或冗余图片。
修改为:(scaleFactor=1.002,, minNeighbors=3, minSize=(3,3),),
在该参数下,在数据规模为500的测试集中,一共切割出4823张小图,覆盖了500张图片中的499张图片,剩余的4324张小图为识别错误图片,或冗余图片。
对比发现,前者的精准率很高,但召回率低于后者。所以我们在加上LeNet模型对后者进行再次过来筛选。
收集整理数据集
在上一个步骤中,我们通过Cascades算法共获得4823切割后的小图,首先我们需要人工把这些图片分为正样本(是舌头)和图样本(非舌头)。
考虑到后期神经网络模型的计算量,在载入数据时,我们会将图片标化为200 * 200的灰度图片,分完后的效果如下:
正样本:
构建卷积神经网络并训练
为了提高模型的判别准确率,我们构建了一个深度为15层的卷积神经网络,
卷积网络的输入是以200 * 200 的灰度图片,输出是一个0-1之前的值,该值是一个概率值,
import cv2 import os import numpy as np import tensorflow as tf import keras.backend as K from keras.datasets import mnist from keras.layers import * from keras.models import * from keras.optimizers import * from keras.initializers import * from keras.callbacks import * from keras.utils.vis_utils import plot_model#显示层级图 from tqdm import tqdm def loadGrayImg(path, shape=(200, 200, 1)): """ 获取灰度值图片 :param path: :return: """ img = cv2.imread(path) img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) img = cv2.resize(img, (shape[0], shape[1])) return np.reshape(img, shape) def loadData(dir, shape=(200, 200, 1)): """ 加载数据集 :param dir: :return: """ imgs = [] for fn in os.listdir(dir): if fn.endswith('jpg'): imgs.append(loadGrayImg(os.path.join(dir, fn), shape=(200, 200, 1))) return np.array(imgs)#转换为numpy矩阵 def net(): """卷及网络模型""" inputs = Input(shape=(200, 200, 1)) model = Lambda(lambda x: (x - 127.5) / 127.5)(inputs)#将像素值变为(-1,-1)----灰度值是从0-255 #卷积层--16个特征图,关机过滤器(5*5),步长2*2,特征图大小:(200-5+2)/2=98 model = Conv2D(16, 5, strides=(2, 2))(model) #激活层--高级激活层Advanced Activation-----LeakyReLU层,LeakyRelU是修正线性单元(Rectified Linear Unit,ReLU)的特殊版本,当不激活时, # LeakyReLU仍然会有非零输出值,从而获得一个小梯度,避免ReLU可能出现的神经元“死亡”现象。即,f(x)=alpha * x for x < 0, f(x) = x for x>=0 # sigmoid和tanh在x趋于无穷的两侧,都出现导数为0的现象,成为软饱和激活函数。也就是造成梯度消失的情况,从而无法更新网络状态。 # relu的主要特点就是:单侧抑制,相对宽阔的兴奋边界,稀疏激活性。稀疏激活性,是指使得部分神经元输出为0,造成网络的稀疏性, #缓解过拟合现象。但是当稀疏过大的时候,出现大部分神经元死亡的状态,因此后面还有出现改进版的prelu.就是改进左侧的分布 model = LeakyReLU()(model) #池化层----输出49*49*16 model = MaxPooling2D(strides=2)(model) #卷积层---32个特征图,(49-5+2)/2=23-------输出23*23*32 model = Conv2D(32, 5, strides=(2, 2))(model) #激活层 model = LeakyReLU()(model) #池化层--输出11*11*32 model = MaxPooling2D(strides=2)(model) #卷积层--64个特征图feature map,输出(11-5+2)/2=4*4*64 model = Conv2D(64, 5, strides=(2, 2))(model) #激活层 model = LeakyReLU()(model) #池化层---输出2*2*64 model = MaxPooling2D(strides=2)(model) #展开层--输出256 model = Flatten()(model) #drop层,默认0.5最好 model = Dropout(0.2)(model) #全连接层,压缩为需要的维度128,如果本层的输入数据的维度大于2,则会先被压为与kernel相匹配的大小。 model = Dense(128)(model) # 全连接层,压缩为需要的维度128 model = Dense(units=1, activation='sigmoid')(model)#使用simgod输出0-1之间的值 ,二分类 #生成模型 model = Model(inputs=inputs, outputs=model) #运行模型,开始训练 model.compile(optimizer='nadam', loss='binary_crossentropy', metrics=['accuracy']) return model def train(echos=500, batch_size=128): """训练模型""" model = net() model.summary() plot_model(model, show_shapes=True, show_layer_names=True) positive = loadData('train/positive')#加载正数据 negtive = loadData('train/negtive')#加载负数据 #合并两个矩阵----相当于拼接到前面一个数组的后面 x = np.concatenate([positive, negtive]) y = np.zeros(len(x)) #赋值标签 y[0:len(positive)] = 1. y[len(positive):] = 0. #进度条 for i in tqdm(range(int(echos))): model.fit(x, y, batch_size=batch_size)#训练传入数据和标签 model.save('model/tongue_%d.model' % i) if __name__ == '__main__': train()
最后整合Cascades模型和卷积网络模型,做图像切割
在识别和切割舌头头图片时,主要用到了Cascades模型和卷积网络模型两种算法模型。
其中Cascades模型主完成于舌头的坐标定位;随后我们会根据这个鞋坐标切割出一组图像,而卷积网络模型则用于计算这一组图像中每一个图片属于舌头的概率,最终选取概率最高的一张作为输出。