yolo v4 模型训练方法(节选自yolo v4 在github 上的文档原文)

How to train (to detect your custom objects)

(to train old Yolo v2 yolov2-voc.cfgyolov2-tiny-voc.cfgyolo-voc.cfgyolo-voc.2.0.cfg, … click by the link)

Training Yolo v4 (and v3):

  1. For training cfg/yolov4-custom.cfg download the pre-trained weights-file (162 MB): yolov4.conv.137 (Google drive mirror yolov4.conv.137 )
  2. Create file yolo-obj.cfg with the same content as in yolov4-custom.cfg (or copy yolov4-custom.cfg to yolo-obj.cfg) and:

So if classes=1 then should be filters=18. If classes=2 then write filters=21(Do not write in the cfg-file: filters=(classes + 5)x3)

(Generally filters depends on the classescoords and number of masks, i.e. filters=(classes + coords + 1)*<number of mask>, where mask is indices of anchors. If mask is absence, then filters=(classes + coords + 1)*num)

So for example, for 2 objects, your file yolo-obj.cfg should differ from yolov4-custom.cfg in such lines in each of 3 [yolo]-layers:

[convolutional]
filters=21

[region]
classes=2
  1. Create file obj.names in the directory build\darknet\x64\data\, with objects names – each in new line
  2. Create file obj.data in the directory build\darknet\x64\data\, containing (where classes = number of objects):
classes = 2
train  = data/train.txt
valid  = data/test.txt
names = data/obj.names
backup = backup/
  1. Put image-files (.jpg) of your objects in the directory build\darknet\x64\data\obj\
  2. You should label each object on images from your dataset. Use this visual GUI-software for marking bounded boxes of objects and generating annotation files for Yolo v2 & v3: https://github.com/AlexeyAB/Yolo_mark

It will create .txt-file for each .jpg-image-file – in the same directory and with the same name, but with .txt-extension, and put to file: object number and object coordinates on this image, for each object in new line:

<object-class> <x_center> <y_center> <width> <height>

Where:

  • <object-class> – integer object number from 0 to (classes-1)

  • <x_center> <y_center> <width> <height> – float values relative to width and height of image, it can be equal from (0.0 to 1.0]

  • for example: <x> = <absolute_x> / <image_width> or <height> = <absolute_height> / <image_height>

  • attention: <x_center> <y_center> – are center of rectangle (are not top-left corner)

    For example for img1.jpg you will be created img1.txt containing:

    1 0.716797 0.395833 0.216406 0.147222
    0 0.687109 0.379167 0.255469 0.158333
    1 0.420312 0.395833 0.140625 0.166667
    
  1. Create file train.txt in directory build\darknet\x64\data\, with filenames of your images, each filename in new line, with path relative to darknet.exe, for example containing:
data/obj/img1.jpg
data/obj/img2.jpg
data/obj/img3.jpg
  1. Download pre-trained weights for the convolutional layers and put to the directory build\darknet\x64

  2. Start training by using the command line: darknet.exe detector train data/obj.data yolo-obj.cfg yolov4.conv.137

    To train on Linux use command: ./darknet detector train data/obj.data yolo-obj.cfg yolov4.conv.137 (just use ./darknet instead of darknet.exe)

    • (file yolo-obj_last.weights will be saved to the build\darknet\x64\backup\ for each 100 iterations)
    • (file yolo-obj_xxxx.weights will be saved to the build\darknet\x64\backup\ for each 1000 iterations)
    • (to disable Loss-Window use darknet.exe detector train data/obj.data yolo-obj.cfg yolov4.conv.137 -dont_show, if you train on computer without monitor like a cloud Amazon EC2)
    • (to see the mAP & Loss-chart during training on remote server without GUI, use command darknet.exe detector train data/obj.data yolo-obj.cfg yolov4.conv.137 -dont_show -mjpeg_port 8090 -map then open URL http://ip-address:8090 in Chrome/Firefox browser)

8.1. For training with mAP (mean average precisions) calculation for each 4 Epochs (set valid=valid.txt or train.txt in obj.data file) and run: darknet.exe detector train data/obj.data yolo-obj.cfg yolov4.conv.137 -map

  1. After training is complete – get result yolo-obj_final.weights from path build\darknet\x64\backup\

    • After each 100 iterations you can stop and later start training from this point. For example, after 2000 iterations you can stop training, and later just start training using: darknet.exe detector train data/obj.data yolo-obj.cfg backup\yolo-obj_2000.weights

    (in the original repository https://github.com/pjreddie/darknet the weights-file is saved only once every 10 000 iterations if(iterations > 1000))

    • Also you can get result earlier than all 45000 iterations.

Note: If during training you see nan values for avg (loss) field – then training goes wrong, but if nan is in some other lines – then training goes well.

Note: If you changed width= or height= in your cfg-file, then new width and height must be divisible by 32.

Note: After training use such command for detection: darknet.exe detector test data/obj.data yolo-obj.cfg yolo-obj_8000.weights

Note: if error Out of memory occurs then in .cfg-file you should increase subdivisions=16, 32 or 64: link

tensorflow 深度学习训练代码

# coding: utf-8
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
import os

os.environ[‘TF_CPP_MIN_LOG_LEVEL’] = ‘2’

mnist = input_data.read_data_sets(‘mnist_data’, one_hot=True)

#初始化过滤器
def weight_variable(shape):
return tf.Variable(tf.truncated_normal(shape, stddev=0.1))

#初始化偏置,初始化时,所有值是0.1
def bias_variable(shape):
return tf.Variable(tf.constant(0.1, shape=shape))

#卷积运算,strides表示每一维度滑动的步长,一般strides[0]=strides[3]=1
#第四个参数可选”Same”或”VALID”,“Same”表示边距使用全0填充
def conv2d(x, W):
return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding=”SAME”)

#池化运算
def max_pool_2x2(x):
return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding=”SAME”)

#创建x占位符,用于临时存放MNIST图片的数据,
# [None, 784]中的None表示不限长度,而784则是一张图片的大小(28×28=784)
x = tf.placeholder(tf.float32, [None, 784], name=’input’)
#y_存的是实际图像的标签,即对应于每张输入图片实际的值
y_ = tf.placeholder(tf.float32, [None, 10])

#将图片从784维向量重新还原为28×28的矩阵图片,
# 原因参考卷积神经网络模型图,最后一个参数代表深度,
# 因为MNIST是黑白图片,所以深度为1,
# 第一个参数为-1,表示一维的长度不限定,这样就可以灵活设置每个batch的训练的个数了
x_image = tf.reshape(x, [-1, 28, 28, 1])

#第一层卷积
#将过滤器设置成5×5×1的矩阵,
#其中5×5表示过滤器大小,1表示深度,因为MNIST是黑白图片只有一层。所以深度为1
#32表示我们要创建32个大小5×5×1的过滤器,经过卷积后算出32个特征图(每个过滤器得到一个特征图),即输出深度为64
W_conv1 = weight_variable([5, 5, 1, 32])
#有多少个特征图就有多少个偏置
b_conv1 = bias_variable([32])
#使用conv2d函数进行卷积计算,然后再用ReLU作为激活函数
h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)
#卷积以后再经过池化操作
h_pool1 = max_pool_2x2(h_conv1)

#第二层卷积
#因为经过第一层卷积运算后,输出的深度为32,所以过滤器深度和下一层输出深度也做出改变
W_conv2 = weight_variable([5, 5, 32, 64])
b_conv2 = bias_variable([64])
h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)
h_pool2 = max_pool_2x2(h_conv2)

#全连接层
#经过两层卷积后,图片的大小为7×7(第一层池化后输出为(28/2)×(28/2),
#第二层池化后输出为(14/2)×(14/2)),深度为64,
#我们在这里加入一个有1024个神经元的全连接层,所以权重W的尺寸为[7 * 7 * 64, 1024]
W_fc1 = weight_variable([7 * 7 * 64, 1024])
#偏置的个数和权重的个数一致
b_fc1 = bias_variable([1024])
#这里将第二层池化后的张量(长:7 宽:7 深度:64) 变成向量(跟上一节的Softmax模型的输入一样了)
h_pool2_flat = tf.reshape(h_pool2, [-1, 7 * 7 * 64])
#使用ReLU激活函数
h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)

#dropout
#为了减少过拟合,我们在输出层之前加入dropout
keep_prob = tf.placeholder(tf.float32)
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)

#输出层
#全连接层输入的大小为1024,而我们要得到的结果的大小是10(0~9),
# 所以这里权重W的尺寸为[1024, 10]
W_fc2 = weight_variable([1024, 10])
b_fc2 = bias_variable([10])
#最后都要经过Softmax函数将输出转化为概率问题
y_conv = tf.nn.softmax(tf.matmul(h_fc1_drop, W_fc2) + b_fc2, name=’output’)

#损失函数和损失优化
cross_entropy = tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(y_conv)))
train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)

#测试准确率,跟Softmax回归模型的一样
correct_prediction = tf.equal(tf.argmax(y_conv, 1), tf.argmax(y_, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

#将训练结果保存,如果不保存我们这次训练结束后的结果也随着程序运行结束而释放了
savePath = ‘./mnist_conv/’
saveFile = savePath + ‘mnist_conv.ckpt’
if os.path.exists(savePath) == False:
os.mkdir(savePath)

saver = tf.train.Saver()

#开始训练
with tf.Session() as sess:
#初始化所有变量
sess.run(tf.global_variables_initializer())
#训练两万次
for i in range(20000):
#每次获取50张图片数据和对应的标签
batch = mnist.train.next_batch(50)
#每训练100次,我们打印一次训练的准确率
if i % 100 == 0:
train_accuracy =sess.run(accuracy, feed_dict={x:batch[0], y_:batch[1], keep_prob:1.0})
print(“step %d, training accuracy %g” % (i, train_accuracy))
#这里是真的训练,将数据传入
sess.run(train_step, feed_dict={x:batch[0], y_:batch[1], keep_prob:0.5})

print (“end train, start testing…”)

mean_value = 0.0
for i in range(mnist.test.labels.shape[0]):
batch = mnist.test.next_batch(50)
train_accuracy = sess.run(accuracy, feed_dict={x: batch[0], y_: batch[1], keep_prob: 1.0})
mean_value += train_accuracy

print(“test accuracy %g” % (mean_value / mnist.test.labels.shape[0]))
# #训练结束后,我们使用mnist.test在测试最后的准确率
# print(“test accuracy %g” % sess.run(accuracy, feed_dict={x:mnist.test.images, y_:mnist.test.labels, keep_prob:1.0}))

# 最后,将会话保存下来
saver.save(sess, saveFile)

当有多个GPU时,调用tensorflow 出现的问题和解决方法

使用PYTHON 调用tensorflow的GPU的一般方法如下:

physical_devices = tf.config.experimental.list_physical_devices(‘GPU’)
tf.config.experimental.set_memory_growth(physical_devices[0], True)

但这样有个问题,当主机有多个GPU时,会抛出一个错误,大致意思是不能使用不同GPU的显存。遇到这个问题,在百度上找到大多数的解决方法是,在这段代码之前加入如下:

os.environ[‘CUDA_VISIBLE_DEVICES’]=’0′

这样,代码是能正常运行了,但这样会有另一个问题,就是主机上明明有两个甚至更多的GPU,但程序只能使用其中一个运行,这大大浪费了主机的机能。之后,阅读了外国的社区和网站,看到遇到同样问题的技术人员的回复和解决方法,终于找到了最好的解决方法。修改GPU调用的代码如下:

例如有两个GPU
os.environ[‘CUDA_VISIBLE_DEVICES’]=’0,1′
physical_devices = tf.config.experimental.list_physical_devices(‘GPU’)
for gpu_instance in physical_devices:
tf.config.experimental.set_memory_growth(gpu_instance,True)

经测试,这样就可以同一个程序同时使用多个GPU处理了。

使用C++调用海康SDK取摄像头视频,使用SWIG 封装成PYTHON

先说明一下这么干的背景。因为项目需要,视频需要转成可以直接在浏览器上播放的流格式,或者直接在浏览器显示的格式。浏览器使用的是FIREFOX 或者CHROME 之类的浏览器。另外,还需要极低的视频延迟,最好少于1秒的延迟。一开始走了不少弯路,海康摄像头本身支持RTSP 流协议,但经测试,使用这个协议取流的话,本身就有大约0.5秒到1秒的延迟,如果再对流作进一步的处理的话,就会产生约2秒或以上的延迟。最后,只有使用海康SDK的私有协议来取流。经测试,SDK私有协议取流是几乎没有延迟的,但海康SDK取流只有C++,所以,就有了C++调用SDK取视频流,再封装接口给PYTHON输出到浏览器这个做法。

C++核心部分的代码:

int cam_midware_cc::InitCam(char* camId,char* userName,char* userPwd)
{
//—————————————
// 初始化
NET_DVR_Init();
//设置连接时间与重连时间
NET_DVR_SetConnectTime(2000, 1);
NET_DVR_SetReconnect(10000, true);
//—————————————
// 注册设备
NET_DVR_DEVICEINFO_V30 struDeviceInfo;
GUserID = NET_DVR_Login_V30((char*)camId, 8000, (char*)userName, (char*)userPwd, &struDeviceInfo);
if (GUserID < 0)
{
printf(“Login error, %d\n”, NET_DVR_GetLastError());
NET_DVR_Cleanup();
}
//—————————————
//设置异常消息回调函数
NET_DVR_SetExceptionCallBack_V30(0, NULL, G_ExceptionCallBack, NULL);
    returnGUserID;
}
int cam_midware_cc::StartVideo(int userId)
{
LONG lRealPlayHandle;
NET_DVR_PREVIEWINFO struPlayInfo = { 0 };
struPlayInfo.hPlayWnd = 0; //需要SDK解码时句柄设为有效值,仅取流不解码时可设为空
struPlayInfo.lChannel = 1; //预览通道号
struPlayInfo.dwStreamType = 1; //0-主码流,1-子码流,2-码流3,3-码流4,以此类推
struPlayInfo.dwLinkMode = 0; //0- TCP方式,1- UDP方式,2- 多播方式,3- RTP方式,4-RTP/RTSP,5-RSTP/HTTP
lRealPlayHandle = NET_DVR_RealPlay_V40(userId, &struPlayInfo, G_RealDataCallBack, NULL);
if (lRealPlayHandle < 0)
{
printf(“NET_DVR_RealPlay_V40 error\n”);
printf(“%d\n”, NET_DVR_GetLastError());
NET_DVR_Logout(userId);
NET_DVR_Cleanup();
return -1;
}
return0;
}
//解码回调 视频为YUV数据(YV12),音频为PCM数据
void CALLBACK G_DecCBFun(int nPort, char * pBuf, int nSize, FRAME_INFO * pFrameInfo, void* nReserved1, int nReserved2)
{
if (G_gbHandling)
{
G_gbHandling–;
return;
}
long lFrameType = pFrameInfo->nType;
if (lFrameType == T_YV12)
{
Mat pImg(pFrameInfo->nHeight, pFrameInfo->nWidth, CV_8UC3);
Mat src(pFrameInfo->nHeight + pFrameInfo->nHeight / 2, pFrameInfo->nWidth, CV_8UC1, pBuf);
cvtColor(src, pImg, CV_YUV2BGR_YV12);
outMat=pImg;
G_video_index++;
}
G_gbHandling = 3;
}
///实时流回调
void CALLBACK G_RealDataCallBack(LONG lRealHandle, DWORD dwDataType, BYTE *pBuffer, DWORD dwBufSize, void *pUser)
{
switch (dwDataType)
{
caseNET_DVR_SYSHEAD: //系统头
if (!PlayM4_GetPort(&G_nPort)) //获取播放库未使用的通道号
{
break;
}
//m_iPort = lPort; //第一次回调的是系统头,将获取的播放库port号赋值给全局port,下次回调数据时即使用此port号播放
if (dwBufSize > 0)
{
if (!PlayM4_SetStreamOpenMode(G_nPort, STREAME_REALTIME)) //设置实时流播放模式
{
break;
}
if (!PlayM4_OpenStream(G_nPort, pBuffer, dwBufSize, 10 * 1024 * 1024)) //打开流接口
{
break;
}
if (!PlayM4_Play(G_nPort, NULL)) //播放开始
{
break;
}
if (!PlayM4_SetDecCallBack(G_nPort, G_DecCBFun))
{
break;
}
}
break;
case NET_DVR_STREAMDATA: //码流数据
if (dwBufSize > 0 && G_nPort != -1)
{
if (!PlayM4_InputData(G_nPort, pBuffer, dwBufSize))
{
cout<<“error”<<PlayM4_GetLastError(G_nPort) <<endl;
break;
}
}
break;
default: //其他数据
if (dwBufSize > 0 && G_nPort != -1)
{
if (!PlayM4_InputData(G_nPort, pBuffer, dwBufSize))
{
break;
}
}
break;
}
}
void CALLBACK G_ExceptionCallBack(DWORD dwType, LONG lUserID, LONG lHandle, void *pUser)
{
char tempbuf[256] = { 0 };
switch (dwType)
{
case EXCEPTION_RECONNECT: //预览时重连
printf(“———-reconnect——–%d\n”, time(NULL));
break;
default:
break;
}
}
Mat cam_midware_cc::getVideoFrame()
{
return outMat;
}
这里使用了opencv 用来,所,需要下载opencv-swig 用于把C++的代码封装成SO或者DLL库
opencv-swig下载地址:
swig下载地址:
swig 在linux 上需要编译,直接 make make install 就可以了
最后还需要编写一个用来让SWIG生成对应的中间代码的 .i 文件
我写的.i 文件如下:

%module cam_midware_cc

%include <opencv.i>
%cv_instantiate_all_defaults
%{
#include “cam_midware_cc.h”
%}
%include “cam_midware_cc.h”

SWIG 的文件弄好后使用SWIG 编译,我使用的是LINUX的方法,命令如下:
swig -c++ -python cam_medware_cc.i
然后再把SWIG 生成的 .cxx 文件连同自己编写的CPP 文件编译一下就可以生成需要的.SO 或 .DLL 文件。
PYTHON 调用的核心代码如下:
import cam_midware_cc
import cv2
import numpy as np
import time
camApi=cam_midware_cc.cam_midware_cc()
testStr=camApi.testLib()
testInitCam=camApi.InitCam(IP,USERNAME,PASSWORD)
print(testStr)
print(testInitCam)
if testInitCam>-1 :
testStartVideo=camApi.StartVideo(testInitCam)
print(testStartVideo)
time.sleep(5)
whileTrue:
    cv_img=camApi.getVideoFrame()
    img_cv2=cv2.blur(np.asarray(cv_img),(1,1))
    cv2.imshow(‘aa’,img_cv2)
    c= cv2.waitKey(30) & 0xff

一个编译opencv+contrib+cuda+cudnn 的命令与参数

在ubuntu 下使用cmake 编译opencv +contrib+cuda+cudnn

cmake -D CMAKE_BUILD_TYPE=Release -D CMAKE_INSTALL_PREFIX=/usr/local -D OPENCV_EXTRA_MODULES_PATH=/root/opencv_contrib-4.5.1/modules -D BUILD_OPENCV_PYTHON2=OFF -D BUILD_opencv_python3=ON -D PYTHON_DEFAULT_EXECUTABLE=/usr/bin/python3 -D PYTHON_INCLUDE_DIR=/usr/include/python3.8 -D PYTHON_LIBRARY=/usr/lib/x86_64-index-gnu/python3.8 -D WITH_TBB=ON -D WITH_CUDA=ON -D OPENCV_DNN_CUDA=ON -D CUDA_ARCH_BIN=6.1 -D OPENCV_PC_FILE_NAME=opencv.pc ..

编译opencv + cuda + cudnn 的注意事项

linux 版,需要使用ubuntu 发行版,避免使用centos 或其他RED HAT 系列的发行版,RED HAT 系列的发行版包不太好找,必须注意需要从nvidia 上下载官方的显卡或运算卡驱动,不要使用第三方的驱动。建议下载直接运行的后缀 .run  的包不要使用tar.gz  需要编译的包,这样可以更省事。另外需要注意的是.run  的包必须在命令行下运行,并且不要运行x window。如有x window  的进程在运行,需使用 systemctl stop gdm  关闭。安装显卡驱动后,下载并安装对应版本的nvidia  官方cuda tool kit。 下载 .run 版本即可。 需要注意的是,在安装过程中不要安装tool kit 包里的 显卡驱动,否则会报错。下载并解压 nvidia 官方的 cudnn 库,需要注意的是,新版本需要把include 和 lib  全部复制到 cuda  对应的目录,这里大多数网上文档都说只把cudnn.h 和 某几个头文件复制过去,这是错的,这是个坑,新版本这么干的话在编译OPENCV时会报找不到 cudnn 。最后,需要注意的是在编译OPENCV时,他会查找系统里的PYTHON JAVA等其他平台的相关库和运行环境并编译对应的库,LINUX上他默认找到并编译的是PYTHON2.7  对于现在流行的PYTHON 3.X 他默认是不查找的,需要在cmake  命令上加上对应的参数,让他查找PYTHON3.X 并且强制关闭PYTHON2 ,才能编译PYTHON3的库。编译后,需要在opencv build 目录下找到并运行python setup.py install 安装编译生成的OPENCV到python3环境,如果用的是VENV 的话,就要先activate VENV 然后再用VENV 的python 执行安装

Tensorflow 环境搭建

$ python3 -m venv _pml_

$ source _pml_/bin/activate

$ python -m pip install –upgrade pip

$ python -m pip install –upgrade setuptools

$ pip list

# 安装库

$ pip install tensorflow

$ pip install matplotlib seaborn

$ pip install Pillow opencv-python opencv-contrib-python

$ pip install scikit-learn

# linux 上需要的

$ sudo apt install python3-tk

springboot 打包外置配置文件

以springboot 2.2.1为例,在系统启动方法如 WebApplication 里添加注解

@PropertySource(value = {"file:${spring.profiles.path}/schedule_settings.properties"})

并在对应的配置文件实体添加同样的注解,如:

@PropertySource(value = {"file:${spring.profiles.path}/schedule_settings.properties"})
@ConfigurationProperties(prefix = "schedule")

在application.yml 或 application.properties 或 application-xxx.yml里添加spring.profiles.path 如:

profiles:
  path: ~/xxxxx/xxxxxxx/scheduletask/target

打包后,把自定义的配置文件如schedule_settings.properties 放在jar 包的同一个目录 或使用如下启动参数启动:

java -jar xxxxxxx.jar –spring.profiles.path=/xxxx/xxxxx/target