├── .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 = '