理论基础

代码实践

代码下载

  1. git clone https://github.com/qqwweee/keras-yolo3.git

权重下载

  1. wget https://pjreddie.com/media/files/yolov3.weights

环境搭建

创建虚拟环境

  1. conda create -n keras_yolo python=3.5.2

依赖库安装

  1. conda install tensorflow-gpu==1.10
  2. conda install keras==2.1.5
  3. conda install pillow==4.2.1
  4. conda install matplotlib==2.0.2

权重格式转换

  1. python convert.py yolov3.cfg yolov3.weights model_data/yolo.h5
  1. ==================================================================================================
  2. Total params: 62,001,757
  3. Trainable params: 61,949,149
  4. Non-trainable params: 52,608
  5. __________________________________________________________________________________________________
  6. None
  7. Saved Keras model to model_data/yolo.h5
  8. Read 62001757 of 62001757.0 from Darknet weights.

测试

image.png
image.png

源码解读

convert.py

  1. import argparse
  2. import configparser
  3. import io
  4. import os
  5. from collections import defaultdict
  6. import numpy as np
  7. from keras import backend as K
  8. from keras.layers import (Conv2D, Input, ZeroPadding2D, Add,
  9. UpSampling2D, MaxPooling2D, Concatenate)
  10. from keras.layers.advanced_activations import LeakyReLU
  11. from keras.layers.normalization import BatchNormalization
  12. from keras.models import Model
  13. from keras.regularizers import l2
  14. from keras.utils.vis_utils import plot_model as plot
  15. # python convert.py yolov3.cfg yolov3.weights model_data/yolo.h5
  16. parser = argparse.ArgumentParser(description='Darknet To Keras Converter.')
  17. parser.add_argument('config_path', help='Path to Darknet cfg file.')
  18. parser.add_argument('weights_path', help='Path to Darknet weights file.')
  19. parser.add_argument('output_path', help='Path to output Keras model file.')
  20. parser.add_argument(
  21. '-p',
  22. '--plot_model',
  23. help='Plot generated Keras model and save as image.',
  24. action='store_true')
  25. parser.add_argument(
  26. '-w',
  27. '--weights_only',
  28. help='Save as Keras weights file instead of model file.',
  29. action='store_true')
  30. def unique_config_sections(config_file):
  31. """Convert all config sections to have unique names.
  32. Adds unique suffixes to config sections for compability with configparser.
  33. """
  34. # key不存在默认返回0
  35. section_counters = defaultdict(int)
  36. output_stream = io.StringIO()
  37. with open(config_file) as fin:
  38. for line in fin:
  39. if line.startswith('['):
  40. # 获取section的名字
  41. section = line.strip().strip('[]')
  42. # 添加section到字典生成独一无二的节点名
  43. _section = section + '_' + str(section_counters[section])
  44. # 字典值+1
  45. section_counters[section] += 1
  46. # 替换section名字
  47. line = line.replace(section, _section)
  48. # 将所有的内容读入到文件流中
  49. output_stream.write(line)
  50. output_stream.seek(0)
  51. return output_stream
  52. # %%
  53. def _main(args):
  54. # 展开路径缩写,用在Linux
  55. config_path = os.path.expanduser(args.config_path)
  56. weights_path = os.path.expanduser(args.weights_path)
  57. # 判断cfg文件后缀知否合法
  58. assert config_path.endswith('.cfg'), '{} is not a .cfg file'.format(config_path)
  59. # 判断weights文件后缀是否合法
  60. assert weights_path.endswith('.weights'), '{} is not a .weights file'.format(weights_path)
  61. # 获得输出路径
  62. output_path = os.path.expanduser(args.output_path)
  63. # 判断输出文件的格式是否合法
  64. assert output_path.endswith('.h5'), 'output path {} is not a .h5 file'.format(output_path)
  65. output_root = os.path.splitext(output_path)[0]
  66. # Load weights and config.
  67. print('Loading weights.')
  68. # 输出文件的文件句柄
  69. weights_file = open(weights_path, 'rb')
  70. # yolo权重文件的版本号
  71. major, minor, revision = np.ndarray(shape=(3,), dtype='int32', buffer=weights_file.read(12))
  72. if (major * 10 + minor) >= 2 and major < 1000 and minor < 1000:
  73. seen = np.ndarray(shape=(1,), dtype='int64', buffer=weights_file.read(8))
  74. else:
  75. seen = np.ndarray(shape=(1,), dtype='int32', buffer=weights_file.read(4))
  76. print('Weights Header: ', major, minor, revision, seen)
  77. print('Parsing Darknet config.')
  78. unique_config_file = unique_config_sections(config_path)
  79. cfg_parser = configparser.ConfigParser()
  80. cfg_parser.read_file(unique_config_file)
  81. # 定义输入层 3通道
  82. print('Creating Keras model.')
  83. input_layer = Input(shape=(None, None, 3))
  84. prev_layer = input_layer
  85. all_layers = []
  86. # 读取decay
  87. # 权重衰减正则项,防止过拟合.每一次学习的过程中,将学习后的参数按照固定比例进行降低,为了防止过拟合,decay参数越大对过拟合的抑制能力越强。
  88. weight_decay = float(cfg_parser['net_0']['decay']) if 'net_0' in cfg_parser.sections() else 5e-4
  89. count = 0
  90. out_index = []
  91. for section in cfg_parser.sections():
  92. print('Parsing section {}'.format(section))
  93. # 处理卷积层
  94. if section.startswith('convolutional'):
  95. # 输出特征图的个数
  96. filters = int(cfg_parser[section]['filters'])
  97. # 卷积核尺寸
  98. size = int(cfg_parser[section]['size'])
  99. # 做卷积运算的步长
  100. stride = int(cfg_parser[section]['stride'])
  101. # 如果pad0,padding padding参数指定;如果pad1padding大小为size/2padding应该是对输入图像左边缘拓展的像素数量
  102. pad = int(cfg_parser[section]['pad'])
  103. # 激活函数的类型
  104. activation = cfg_parser[section]['activation']
  105. # 是否做BN
  106. batch_normalize = 'batch_normalize' in cfg_parser[section]
  107. padding = 'same' if pad == 1 and stride == 1 else 'valid'
  108. # Setting weights.
  109. # Darknet serializes convolutional weights as:
  110. # [bias/beta, [gamma, mean, variance], conv_weights]
  111. # 获取上一层的shape
  112. prev_layer_shape = K.int_shape(prev_layer)
  113. # 获取权重尺寸
  114. weights_shape = (size, size, prev_layer_shape[-1], filters)
  115. # darknet_w_shape的尺寸
  116. darknet_w_shape = (filters, weights_shape[2], size, size)
  117. # 权重的参数个数
  118. weights_size = np.product(weights_shape)
  119. print('conv2d', 'bn' if batch_normalize else ' ', activation, weights_shape)
  120. # 定义偏置参数
  121. conv_bias = np.ndarray(
  122. shape=(filters,),
  123. dtype='float32',
  124. buffer=weights_file.read(filters * 4))
  125. count += filters
  126. if batch_normalize:
  127. bn_weights = np.ndarray(
  128. shape=(3, filters),
  129. dtype='float32',
  130. buffer=weights_file.read(filters * 12))
  131. count += 3 * filters
  132. bn_weight_list = [
  133. bn_weights[0], # scale gamma
  134. conv_bias, # shift beta
  135. bn_weights[1], # running mean
  136. bn_weights[2] # running var
  137. ]
  138. # 卷积权重
  139. conv_weights = np.ndarray(
  140. shape=darknet_w_shape,
  141. dtype='float32',
  142. buffer=weights_file.read(weights_size * 4))
  143. count += weights_size
  144. # DarkNet conv_weights are serialized Caffe-style:
  145. # (out_dim, in_dim, height, width)
  146. # We would like to set these to Tensorflow order:
  147. # (height, width, in_dim, out_dim)
  148. # Caffe-style 转为 Tensorflow order
  149. conv_weights = np.transpose(conv_weights, [2, 3, 1, 0])
  150. conv_weights = [conv_weights] if batch_normalize else [
  151. conv_weights, conv_bias
  152. ]
  153. # Handle activation.
  154. act_fn = None
  155. if activation == 'leaky':
  156. pass # Add advanced activation later.
  157. elif activation != 'linear':
  158. raise ValueError(
  159. 'Unknown activation function `{}` in section {}'.format(
  160. activation, section))
  161. # Create Conv2D layer
  162. if stride > 1:
  163. # Darknet uses left and top padding instead of 'same' mode
  164. prev_layer = ZeroPadding2D(((1, 0), (1, 0)))(prev_layer)
  165. conv_layer = (Conv2D(
  166. filters, (size, size),
  167. strides=(stride, stride),
  168. kernel_regularizer=l2(weight_decay),
  169. use_bias=not batch_normalize,
  170. weights=conv_weights,
  171. activation=act_fn,
  172. padding=padding))(prev_layer)
  173. if batch_normalize:
  174. conv_layer = (BatchNormalization(
  175. weights=bn_weight_list))(conv_layer)
  176. prev_layer = conv_layer
  177. if activation == 'linear':
  178. all_layers.append(prev_layer)
  179. elif activation == 'leaky':
  180. act_layer = LeakyReLU(alpha=0.1)(prev_layer)
  181. prev_layer = act_layer
  182. all_layers.append(act_layer)
  183. elif section.startswith('route'):
  184. ids = [int(i) for i in cfg_parser[section]['layers'].split(',')]
  185. layers = [all_layers[i] for i in ids]
  186. if len(layers) > 1:
  187. print('Concatenating route layers:', layers)
  188. concatenate_layer = Concatenate()(layers)
  189. all_layers.append(concatenate_layer)
  190. prev_layer = concatenate_layer
  191. else:
  192. skip_layer = layers[0] # only one layer to route
  193. all_layers.append(skip_layer)
  194. prev_layer = skip_layer
  195. elif section.startswith('maxpool'):
  196. size = int(cfg_parser[section]['size'])
  197. stride = int(cfg_parser[section]['stride'])
  198. all_layers.append(
  199. MaxPooling2D(
  200. pool_size=(size, size),
  201. strides=(stride, stride),
  202. padding='same')(prev_layer))
  203. prev_layer = all_layers[-1]
  204. elif section.startswith('shortcut'):
  205. index = int(cfg_parser[section]['from'])
  206. activation = cfg_parser[section]['activation']
  207. assert activation == 'linear', 'Only linear activation supported.'
  208. all_layers.append(Add()([all_layers[index], prev_layer]))
  209. prev_layer = all_layers[-1]
  210. elif section.startswith('upsample'):
  211. # 上采样
  212. stride = int(cfg_parser[section]['stride'])
  213. assert stride == 2, 'Only stride=2 supported.'
  214. all_layers.append(UpSampling2D(stride)(prev_layer))
  215. prev_layer = all_layers[-1]
  216. elif section.startswith('yolo'):
  217. out_index.append(len(all_layers) - 1)
  218. all_layers.append(None)
  219. prev_layer = all_layers[-1]
  220. elif section.startswith('net'):
  221. # 忽略net
  222. pass
  223. else:
  224. # 未知section抛出异常
  225. raise ValueError(
  226. 'Unsupported section header type: {}'.format(section))
  227. # Create and save model.
  228. if len(out_index) == 0:
  229. out_index.append(len(all_layers) - 1)
  230. # 新建Model模型
  231. model = Model(inputs=input_layer, outputs=[all_layers[i] for i in out_index])
  232. # 输出网络结构与超参数
  233. print(model.summary())
  234. # 保存参数
  235. if args.weights_only:
  236. model.save_weights('{}'.format(output_path))
  237. print('Saved Keras weights to {}'.format(output_path))
  238. else:
  239. model.save('{}'.format(output_path))
  240. print('Saved Keras model to {}'.format(output_path))
  241. # Check to see if all weights have been read.
  242. remaining_weights = len(weights_file.read()) / 4
  243. weights_file.close()
  244. print('Read {} of {} from Darknet weights.'.format(count, count + remaining_weights))
  245. if remaining_weights > 0:
  246. print('Warning: {} unused weights'.format(remaining_weights))
  247. # 网络模型输出图片
  248. if args.plot_model:
  249. plot(model, to_file='{}.png'.format(output_root), show_shapes=True)
  250. print('Saved model plot to {}.png'.format(output_root))
  251. if __name__ == '__main__':
  252. _main(parser.parse_args())

训练自己的数据集