├── .gitattributes ├── .idea └── vcs.xml ├── README.md ├── VGG16_model └── vgg16_weights_tf_dim_ordering_tf_kernels.h5 ├── main ├── MedicalLargeClassification.py ├── MedicalLargeFine_tuning.py └── MedicalSegmentFine_tuning.py ├── picture └── craw_picture.py ├── src ├── 1.png ├── 2.png ├── 3.png ├── 4.png └── 5.png └── testCase ├── 10室内.jpg ├── 11人物.jpg ├── 12人物.jpg ├── 13人物.jpg ├── 14人物.jpg ├── 15人物.jpg ├── 16动物.JPEG ├── 17动物.JPEG ├── 18动物.JPEG ├── 19动物.JPEG ├── 1交通.jpg ├── 20动物.JPEG ├── 21医学胸部.png ├── 22医学胸部.png ├── 23医学胸部.png ├── 24医学胸部.png ├── 25医学胸部.png ├── 26医学四肢.png ├── 27医学四肢.png ├── 28医学四肢.png ├── 29医学四肢.png ├── 2交通.jpg ├── 30医学四肢.png ├── 31医学头部.png ├── 32医学头部.png ├── 33医学头部.png ├── 34医学头部.png ├── 35医学头部.png ├── 3交通.jpg ├── 4交通.jpg ├── 5交通.jpg ├── 6室内.jpg ├── 7室内.jpg ├── 8室内.jpg └── 9室内.jpg /.gitattributes: -------------------------------------------------------------------------------- 1 | *.h5 filter=lfs diff=lfs merge=lfs -text 2 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 图像的类别理解及应用 2 | - 此库为2017-2018年度工程实践项目,主要目的是能够识别图像类别,尤其是医学类,然后在医学类中再进行更为细致的类别识别,以达到医学影像这一垂直领域的应用目的。 3 | - blog: https://blog.csdn.net/zpalyq110/article/details/80432827 4 | 5 | # 环境 6 | - 操作系统:Windows10 7 | - 编程语言:Python3 8 | - 模型框架:Keras 9 | - GPU:GTX 1060 10 | - GUI:Tkinter 11 | 12 | # 文件结构及意义 13 | - VGG16_model:存放训练好的VGG16模型——vgg16_weights_tf_dim_ordering_tf_kernels.h5 14 | - main:主文件 15 |  - MedicalLargeClassification.py——图像识别GUI搭建——`运行此文件即可启动程序` 16 | - MedicalLargeFine_tuning.py——图像大类识别模型搭建 17 | - MedicalSegmentFine_tuning.py——医学小类识别模型搭建 18 | - MedicalLargeClassificationModel_weights_15.h5——训练好的图像大类分类模型 19 | - MedicalSegmentClassificationModel_weights_15.h5——训练好的医学小类分类模型 20 | - picture: 21 | - craw_picture.py——爬虫系统构建 22 | - testCase:测试样本 23 | - 注:`由于.h5单文件超过了GitHub 100M的限制,项目总大小超过1个G,所以利用LFS进行git push` 24 | 25 | # 数据源 26 | - ImageNet开源数据集中的VOC2012一部分,进行类别合并,筛选出人物、动物、室内、交通四大类 27 | - 从国外开源医疗图像网站www.openi.org上爬取图片,进行修剪,最终得到医学类图像 28 | - 其中医学类又细分为了胸部、头部、四肢三类 29 | - 数据规模:训练集1700张,验证集450张,测试集35张 30 | 31 | # 模型 32 | - 模型借鉴了迁移学习的思想,利用基于ImageNet数据集训练好的VGG16模型,释放最后一个卷积核的参数并且pop最后三层,再add三个Dense层。 33 | - 其实这一步花费了很长时间,因为模型的迁移涉及到两个部分,一个是模型的框架,另一个是模型的参数。 34 | - 先说官方文档,众所周知,keras的模型结构有两种:Sequential、Model。阅读VGG16的源码可以发现,VGG16是Model结构,而官网文档给的例子是用Sequential结构搭建模型后,将vgg16_weights_tf_dim_ordering_tf_kernels.h5的权重加载进模型,但是实际运行会报错——两种结构并不兼容 35 | - 再说说博客,几乎所有的blog都和我的想法一致,尝试自己用Model结构搭建模型,但是在Flatten层都会报错,尝试各种写法都报错误 36 | - 最后我决定不动Flatten层,利用Model的pop()将最后三层Dense删除,再增加合适尺寸的Dense层,问题解决 37 | - 注:`想要利用训练好的VGG16,最好自己下载,然后改VGG16源码里面的载入地址(因为Keras需要去国外下载,及其慢,本库存放在VGG16_model中)` 38 | 39 | # 训练 40 | - 图像大类分类模型训练:人物、动物、室内、交通、医学 41 | 42 | 43 | - 医学小类分类模型训练: 头部。胸部、四肢 44 | 45 | 46 | 47 | # GUI 48 | - 利用python的tkinter搭建交互界面 49 | - 将大类识别和医学小类识别串联起来,形成应用。 50 | 51 | 52 | 53 | 54 | # 测试 55 | - 测试样本:testCase 56 | 57 | 58 | - 测试截图:红线框标注的为分类错误 59 | 60 | 61 | -------------------------------------------------------------------------------- /VGG16_model/vgg16_weights_tf_dim_ordering_tf_kernels.h5: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:8b81f25be4126c5ec088f19901b2b34e9a40e3d46246c9d208a3d727462b4f5c 3 | size 553467096 4 | -------------------------------------------------------------------------------- /main/MedicalLargeClassification.py: -------------------------------------------------------------------------------- 1 | """ 2 | Created on :2018/5/21 3 | @author: Freeman 4 | """ 5 | import numpy as np 6 | from tkinter import * 7 | import tkinter.filedialog 8 | from keras import backend as K 9 | from keras.models import load_model 10 | from keras.applications.vgg16 import preprocess_input 11 | from keras.preprocessing.image import load_img, img_to_array 12 | 13 | 14 | def getLargeFileNames(): 15 | filenames = tkinter.filedialog.askopenfilenames() 16 | if len(filenames) != 0: 17 | rightCategroy = "" 18 | string_filename = "" 19 | count = 0 20 | for i in range(0, len(filenames)): 21 | count += 1 22 | string_filename += str(filenames[i]) + "\n" 23 | temp = str(filenames[i]).split('/')[-1].split('.')[0] 24 | if count % 5 == 0: 25 | rightCategroy += temp + "\n" 26 | else: 27 | rightCategroy += temp + " " 28 | LargePictureNames.set(string_filename) 29 | lb1.config(text="图片真实类别:\n" + rightCategroy) 30 | else: 31 | LargePictureNames.set('') 32 | lb1.config(text="您没有选择任何图片") 33 | 34 | 35 | def Large_model_predict(): 36 | if LargePictureNames.get() != '': 37 | picList = LargePictureNames.get().split('\n')[:-1] 38 | data = [] 39 | for i in picList: 40 | img = load_img(i, target_size=(img_width, img_height)) 41 | x = img_to_array(img) 42 | data.append(x) 43 | data = np.stack(data,axis=0) 44 | data = preprocess_input(data) # 对样本执行-逐样本均值消减-的归一化 45 | model1 = load_model('MedicalLargeClassificationModel_weights_15.h5') 46 | result1 = model1.predict(data) 47 | K.clear_session() 48 | 49 | model2 = load_model('MedicalSegmentClassificationModel_weights_15.h5') 50 | result2 = model2.predict(data) 51 | K.clear_session() 52 | 53 | number = result1.shape[0] 54 | strs = '' 55 | count = 0 56 | for i in range(number): 57 | count += 1 58 | res1 = np.max(result1[i]) 59 | categroy = categroyDict1[np.where(result1[i] == res1)[0][0]] 60 | if count % 5 == 0: 61 | if categroy == '医学': 62 | res2 = np.max(result2[i]) 63 | segment = categroyDict2[np.where(result2[i] == res2)[0][0]] 64 | strs += str(categroy) + '-' + str(segment) + "\n" 65 | else: 66 | strs += str(categroy)+ "\n" 67 | 68 | else: 69 | if categroy == '医学': 70 | res2 = np.max(result2[i]) 71 | segment = categroyDict2[np.where(result2[i] == res2)[0][0]] 72 | strs += str(categroy) + '-' + str(segment) + " " 73 | else: 74 | strs += str(categroy) + " " 75 | lb3.config(text="预测类别:\n" + strs) 76 | else: 77 | lb3.config(text="图片选择有误!") 78 | 79 | 80 | if __name__ == "__main__": 81 | 82 | # 参数初始化 83 | img_width, img_height = 224, 224 84 | 85 | # 类别字典 86 | categroyDict1 ={0:' 动物 ',1:' 室内 ',2:'医学',3:' 人物 ',4:' 交通 '} 87 | categroyDict2 ={0:'胸部',1:'头部',2:'四肢'} 88 | 89 | # GUI 90 | root = tkinter.Tk() 91 | root.geometry('700x550+300+50') 92 | 93 | LargePictureNames = StringVar() 94 | SegmentPictureNames = StringVar() 95 | 96 | root.title('Image recognition system Copyright © 2018 by Freeman') 97 | root['bg'] = '#a9ed96' 98 | root.attributes("-alpha",0.97) 99 | 100 | 101 | lb0 = Label(root, text='图像识别系统', width=20, font = 'Helvetica -20 bold',bg='#a9ed96',height=3) 102 | lb0.grid(row=0, column=0, stick=W, pady=1,) 103 | 104 | lb5 = Label(root, text='注:若为医学类图像暂时仅支持胸部、头部、四肢识别',bg='#a9ed96', fg='red') 105 | lb5.grid(row=0, column=1, stick=W) 106 | 107 | btn1 = Button(root, text="选择需要预测的图片", width=20, command=getLargeFileNames) 108 | btn1.grid(row=1, column=0, stick=W, pady=10, padx=50) 109 | 110 | lb1 = Label(root, text='', width=60) 111 | lb1.grid(row=1, column=1, pady=20) 112 | 113 | btn3 = Button(root, text="预 测", width=20, bg='SkyBlue',command=Large_model_predict) 114 | btn3.grid(row=4, column=0, stick=W, pady=10, padx=50) 115 | 116 | lb3 = Label(root, text='', width=60) 117 | lb3.grid(row=4, column=1, pady=30) 118 | 119 | 120 | root.mainloop() -------------------------------------------------------------------------------- /main/MedicalLargeFine_tuning.py: -------------------------------------------------------------------------------- 1 | """ 2 | Created on :2018/1/26 3 | @author: Freeman 4 | """ 5 | from keras.callbacks import Callback 6 | from keras.applications import VGG16 7 | from keras.preprocessing.image import ImageDataGenerator 8 | from keras.models import Model 9 | from keras.layers import Dense 10 | from keras.optimizers import SGD 11 | import matplotlib.pyplot as plt 12 | 13 | 14 | class LossHistory(Callback): 15 | def on_train_begin(self, logs={}): 16 | self.losses = {'batch':[], 'epoch':[]} 17 | self.accuracy = {'batch':[], 'epoch':[]} 18 | self.val_loss = {'batch':[], 'epoch':[]} 19 | self.val_acc = {'batch':[], 'epoch':[]} 20 | 21 | def on_batch_end(self, batch, logs={}): 22 | self.losses['batch'].append(logs.get('loss')) 23 | self.accuracy['batch'].append(logs.get('acc')) 24 | self.val_loss['batch'].append(logs.get('val_loss')) 25 | self.val_acc['batch'].append(logs.get('val_acc')) 26 | 27 | def on_epoch_end(self, batch, logs={}): 28 | self.losses['epoch'].append(logs.get('loss')) 29 | self.accuracy['epoch'].append(logs.get('acc')) 30 | self.val_loss['epoch'].append(logs.get('val_loss')) 31 | self.val_acc['epoch'].append(logs.get('val_acc')) 32 | 33 | def loss_plot(self, loss_type): 34 | iters = range(len(self.losses[loss_type])) 35 | plt.figure() 36 | # acc 37 | plt.plot(iters, self.accuracy[loss_type], 'r', label='train acc') 38 | # loss 39 | plt.plot(iters, self.losses[loss_type], 'g', label='train loss') 40 | if loss_type == 'epoch': 41 | # val_acc 42 | plt.plot(iters, self.val_acc[loss_type], 'b', label='val acc') 43 | # val_loss 44 | plt.plot(iters, self.val_loss[loss_type], 'k', label='val loss') 45 | plt.grid(True) 46 | plt.xlabel(loss_type) 47 | plt.ylabel('acc-loss') 48 | plt.legend(loc="upper right") 49 | plt.savefig("loss-acc/epochs_15.png") 50 | plt.show() 51 | 52 | 53 | # build the VGG16 network 54 | def vgg16_model(img_rows, img_cols, num_classes=5): 55 | model = VGG16(weights='imagenet', include_top=True) 56 | # for i in model.layers: 57 | # print(i.name) 58 | # print(i.get_weights()) 59 | # print(model.get_layer('block4_conv3').get_weights()) 60 | # model.summary() 61 | model.layers.pop() 62 | model.layers.pop() 63 | model.layers.pop() 64 | model.outputs = [model.layers[-1].output] 65 | x=Dense(1024, activation='relu')(model.layers[-1].output) 66 | x=Dense(128, activation='relu')(x) 67 | x=Dense(num_classes, activation='softmax')(x) 68 | model=Model(model.input,x) 69 | for layer in model.layers[:15]: 70 | layer.trainable = False 71 | sgd = SGD(lr=1e-3, decay=1e-6, momentum=0.9, nesterov=True) 72 | model.compile(optimizer=sgd, loss='categorical_crossentropy', metrics=['accuracy']) 73 | model.summary() 74 | return model 75 | 76 | def train_save_model(): 77 | # 图片生成器:用以生成一个batch的图像数据, 78 | # 支持实时数据提升。训练时该函数会无限生成数据,直到达到规定的epoch次数为止。 79 | train_datagen = ImageDataGenerator( 80 | rescale=1. / 255, 81 | shear_range=0.2, 82 | zoom_range=0.2, 83 | horizontal_flip=True) 84 | 85 | test_datagen = ImageDataGenerator(rescale=1. / 255) 86 | 87 | train_generator = train_datagen.flow_from_directory( 88 | train_data_dir, 89 | target_size=(img_height, img_width), 90 | batch_size=batch_size, 91 | class_mode='categorical') 92 | 93 | validation_generator = test_datagen.flow_from_directory( 94 | validation_data_dir, 95 | target_size=(img_height, img_width), 96 | batch_size=batch_size, 97 | class_mode='categorical') 98 | 99 | # fine-tune the model 100 | model = vgg16_model(224, 224, 5) 101 | 102 | # loss-history 103 | history = LossHistory() 104 | model.fit_generator( 105 | train_generator, 106 | steps_per_epoch=16, 107 | epochs=epochs, 108 | validation_data=validation_generator, 109 | validation_steps=16, 110 | callbacks=[history] 111 | ) 112 | 113 | loss, acc = model.evaluate_generator( 114 | validation_generator, 115 | steps=8) 116 | 117 | print("loss:", loss, "acc:", acc) 118 | history.loss_plot('epoch') 119 | # 保存model 120 | model.save('MedicalLargeClassificationModel_weights_15.h5') 121 | 122 | 123 | if __name__ == "__main__": 124 | # 参数初始化 125 | img_width, img_height = 224, 224 126 | train_data_dir = '../data/train' 127 | validation_data_dir = '../data/validation' 128 | epochs = 15 129 | batch_size = 16 # 每次训练样本数 130 | 131 | # 训练模型 132 | train_save_model() -------------------------------------------------------------------------------- /main/MedicalSegmentFine_tuning.py: -------------------------------------------------------------------------------- 1 | """ 2 | Created on :2018/5/21 3 | @author: Freeman 4 | """ 5 | from keras.callbacks import Callback 6 | from keras.applications import VGG16 7 | from keras.preprocessing.image import ImageDataGenerator 8 | from keras.models import Model 9 | from keras.layers import Dense 10 | from keras.optimizers import SGD 11 | import matplotlib.pyplot as plt 12 | 13 | 14 | class LossHistory(Callback): 15 | def on_train_begin(self, logs={}): 16 | self.losses = {'batch':[], 'epoch':[]} 17 | self.accuracy = {'batch':[], 'epoch':[]} 18 | self.val_loss = {'batch':[], 'epoch':[]} 19 | self.val_acc = {'batch':[], 'epoch':[]} 20 | 21 | def on_batch_end(self, batch, logs={}): 22 | self.losses['batch'].append(logs.get('loss')) 23 | self.accuracy['batch'].append(logs.get('acc')) 24 | self.val_loss['batch'].append(logs.get('val_loss')) 25 | self.val_acc['batch'].append(logs.get('val_acc')) 26 | 27 | def on_epoch_end(self, batch, logs={}): 28 | self.losses['epoch'].append(logs.get('loss')) 29 | self.accuracy['epoch'].append(logs.get('acc')) 30 | self.val_loss['epoch'].append(logs.get('val_loss')) 31 | self.val_acc['epoch'].append(logs.get('val_acc')) 32 | 33 | def loss_plot(self, loss_type): 34 | iters = range(len(self.losses[loss_type])) 35 | plt.figure() 36 | # acc 37 | plt.plot(iters, self.accuracy[loss_type], 'r', label='train acc') 38 | # loss 39 | plt.plot(iters, self.losses[loss_type], 'g', label='train loss') 40 | if loss_type == 'epoch': 41 | # val_acc 42 | plt.plot(iters, self.val_acc[loss_type], 'b', label='val acc') 43 | # val_loss 44 | plt.plot(iters, self.val_loss[loss_type], 'k', label='val loss') 45 | plt.grid(True) 46 | plt.xlabel(loss_type) 47 | plt.ylabel('acc-loss') 48 | plt.legend(loc="upper right") 49 | # plt.savefig("loss-acc/epochs_10.png") 50 | plt.show() 51 | 52 | 53 | # build the VGG16 network 54 | def vgg16_model(img_rows, img_cols, num_classes=5): 55 | model = VGG16(weights='imagenet', include_top=True) 56 | # for i in model.layers: 57 | # print(i.name) 58 | # print(i.get_weights()) 59 | # print(model.get_layer('block4_conv3').get_weights()) 60 | # model.summary() 61 | model.layers.pop() 62 | model.layers.pop() 63 | model.layers.pop() 64 | model.outputs = [model.layers[-1].output] 65 | x=Dense(1024, activation='relu')(model.layers[-1].output) 66 | x=Dense(128, activation='relu')(x) 67 | x=Dense(num_classes, activation='softmax')(x) 68 | model=Model(model.input,x) 69 | for layer in model.layers[:15]: 70 | layer.trainable = False 71 | sgd = SGD(lr=1e-3, decay=1e-6, momentum=0.9, nesterov=True) 72 | model.compile(optimizer=sgd, loss='categorical_crossentropy', metrics=['accuracy']) 73 | model.summary() 74 | return model 75 | 76 | 77 | def train_save_model(): 78 | # 图片生成器:用以生成一个batch的图像数据, 79 | # 支持实时数据提升。训练时该函数会无限生成数据,直到达到规定的epoch次数为止。 80 | train_datagen = ImageDataGenerator( 81 | rescale=1. / 255, 82 | shear_range=0.2, 83 | zoom_range=0.2, 84 | horizontal_flip=True) 85 | 86 | test_datagen = ImageDataGenerator(rescale=1. / 255) 87 | 88 | train_generator = train_datagen.flow_from_directory( 89 | train_data_dir, 90 | target_size=(img_height, img_width), 91 | batch_size=batch_size, 92 | class_mode='categorical') 93 | 94 | validation_generator = test_datagen.flow_from_directory( 95 | validation_data_dir, 96 | target_size=(img_height, img_width), 97 | batch_size=batch_size, 98 | class_mode='categorical') 99 | 100 | # fine-tune the model 101 | model = vgg16_model(224, 224, 3) 102 | 103 | # loss-history 104 | history = LossHistory() 105 | model.fit_generator( 106 | train_generator, 107 | steps_per_epoch=16, 108 | epochs=epochs, 109 | validation_data=validation_generator, 110 | validation_steps=16, 111 | callbacks=[history] 112 | ) 113 | 114 | loss, acc = model.evaluate_generator( 115 | validation_generator, 116 | steps=8) 117 | 118 | print("loss:", loss, "acc:", acc) 119 | history.loss_plot('epoch') 120 | # 保存model 121 | # model.save('MedicalSegmentClassificationModel_weights_10.h5') 122 | 123 | 124 | if __name__ == "__main__": 125 | # 参数初始化 126 | img_width, img_height = 224, 224 127 | train_data_dir = '../dataSegment/train' 128 | validation_data_dir = '../dataSegment/validation' 129 | epochs = 15 130 | batch_size = 16 # 每次训练样本数 131 | 132 | # 训练模型 133 | train_save_model() -------------------------------------------------------------------------------- /picture/craw_picture.py: -------------------------------------------------------------------------------- 1 | import urllib.request 2 | import re 3 | import time 4 | import socket 5 | 6 | 7 | socket.setdefaulttimeout(20) 8 | urlCT = 'https://openi.nlm.nih.gov/gridquery.php?q=&it=c' 9 | urlMRI = 'https://openi.nlm.nih.gov/gridquery.php?q=&it=m' 10 | urlUltrasound = 'https://openi.nlm.nih.gov/gridquery.php?q=&it=u' 11 | urlX = 'https://openi.nlm.nih.gov/gridquery.php?q=&it=x' 12 | urlList = [urlCT, urlMRI, urlUltrasound, urlX] 13 | urlDir = ['CT/', 'MRI/', 'Ultrasound/', 'Xss/'] 14 | 15 | pngHead ='https://openi.nlm.nih.gov' 16 | 17 | Header = { 18 | "User-Agent": 19 | 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36', 20 | } 21 | 22 | # suolue = 'https://openi.nlm.nih.gov/imgs/150/204/1006/MPX1006_synpic52425.png' 23 | # wanzheng = 'https://openi.nlm.nih.gov/imgs/512/204/1006/MPX1006_synpic52425.png' 24 | 25 | def craw(url, pages, dir): 26 | for i in range(0, pages): 27 | print(i) 28 | 29 | a = i * 100 + 1 30 | b = a + 99 31 | url = url + '&m=' + str(a) + '&n=' + str(b) 32 | request = urllib.request.Request(url, headers=Header) 33 | try: 34 | response = urllib.request.urlopen(request) 35 | html = response.read() 36 | html = str(html) 37 | response.close() 38 | pat1 = '