yolov5可以用灰度图像进行训练吗,从0开始yolov5灰度图训练和检测
1 预演
通常我们采用RGB图像做目标检测,考虑多路视频流同时做目标检测时,消耗显卡算力和推理时间较多。现通过实验对比对灰度图(GRAY)和RGB图做训练和测试对比。
本次使用yolov5_6.2尝试灰度图做目标检测的训练的目标检测的测试。
直接将彩色图通过opencv算法转换为灰度图,运行’python train.py’ 训练。
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
在train.py运行的时候,无报错,但是打印的模型结构的前两行如下所示:
【表1-1 模型结构截取】
from | n | params | module | arguments | |
---|---|---|---|---|---|
0 | -1 | 1 | 3520 | models.common.Conv | [3, 32, 6, 2, 2] |
1 | -1 | 1 | 18560 | models.common.Conv | [32, 64, 3, 2] |
通过上图打印可知模型的输入是固定3通道的,那么意味着即使训练的数据集是灰度图像,在读取图像时仍会对图像进行处理。
观察源码,在utils/dataloaders.py文件第248行
245 else:
246 # Read image
247 self.count += 1
248 img0 = cv2.imread(path) # BGR
249 assert img0 is not None, f'Image Not Found {path}'
250 s = f'image {self.count}/{self.nf} {path}: '
如下opencv的imread方法简介
img = cv2.imread(path, flag) # flag: the way in which img should be read
# flag: default value is cv2.IMREAD_COLOR
# cv2.IMREAD_COLOR(1): BGR
# cv2.IMREAD_GRAYSCALE(0): GRAY
# cv2.IMREAD_UNCHANGED(-1):
opencv在默认情况下,'cv2.imread(path)'会读取3个通道(BGR)的图像,如果是灰度图,会将图层复制三次(BGR缺省,BGR),因此读出来的图片是三通道。
2 修改源码使可以灰度训练
源码改变的位置,后加注释’Hlj2308’。
2.1 修改读取图片模式
在utils/dataloaders.py文件第248行,原
img0 = cv2.imread(path) # BGR
改为
img0 = cv2.imread(path, 0) # Hlj2308_GRAY
2.2 修改源码传参中的通道数
2.2.1 在train.py文件第122行、130行中的参数 ch=3 改为 ch=1。
model = Model(cfg or ckpt['model'].yaml, ch=1, nc=nc, anchors=hyp.get('anchors')).to(device) # Hlj2308
model = Model(cfg, ch=1, nc=nc, anchors=hyp.get('anchors')).to(device) # Hlj2308 create
2.2.2 在models/yolo.py文件第151行ch=3改为 ch=1。
149 class DetectionModel(BaseModel):
150 # YOLOv5 detection model
151 def __init__(self, cfg='yolov5s.yaml', ch=1, nc=None, anchors=None):
152 super().__init__()
153 if isinstance(cfg, dict):
154 self.yaml = cfg # model dict
2.3 运行train.py
在运行 train.py 的过程中,反正就是一路报错,一路改错调试。最终实现可正常训练和测试的目的。
【报错1】
File "....packages\torch\nn\modules\conv.py", line 442, in _conv_forward
return F.conv2d(input, weight, bias, self.stride,
RuntimeError: Given groups=1, weight of size [32, 1, 6, 6], expected input[8, 3, 640, 640] to have 1 channels, but got 3 channels instead
数据读入的通道数不对,应该是 1 通道,但是这里读入的是 3 通道。
可能是PIL模块’Image.open()'打开图像时的’mode’问题。修改models/common.py文件中 612 行
im, f = Image.open(requests.get(im, stream=True).raw if str(im).startswith('http') else im), im
后面添加如下两行代码
if im.mode != 'L':
im = im.convert('L')
仍存在【报错1】
2.4 修改utils/general.py中的源码
修改utils/general.py中的源码1031-1032行如下
def imread(path, flags=cv2.IMREAD_COLOR):
return cv2.imdecode(np.fromfile(path, np.uint8), flags)
改为
def imread(path, flags=cv2.IMREAD_GRAYSCALE):
return cv2.imdecode(np.fromfile(path, np.uint8), cv2.IMREAD_GRAYSCALE)
仍存在【报错1】
2.5 修改dataloaders.py中所有cv2读取图片的flags
在utils/dataloaders.py文件第677、691、881、1042、1119、1122、1125行,原
cv2.imread(f)
改为如下,可通过Ctr+F搜索替换
cv2.imread(f, 0)
此时,运行train.py,【报错1】将不存在
【报错2】如下
File "..../yolov5_6.2_gray/utils/dataloaders.py", line 706, in load_mosaic
img4 = np.full((s * 2, s * 2, img.shape[2]), 114, dtype=np.uint8) # base image with 4 tiles
IndexError: tuple index out of range
解决:原因是img.shape[2]索引不存在,因为图片是单通道的,所以只有二维,而非三维数组。这里将源码\utils\dataloaders.py的706行源码
img4 = np.full((s * 2, s * 2, img.shape[2]), 114, dtype=np.uint8)
改为
img4 = np.full((s * 2, s * 2, img.shape[2] if len(img.shape)==3 else 1), 114, dtype=np.uint8)
【报错3】如下,是由于【报错2】的修改方式不对
File "..../yolov5_6.2_gray/utils/dataloaders.py", line 721, in load_mosaic
img4[y1a:y2a, x1a:x2a] = img[y1b:y2b, x1b:x2b] # img4[ymin:ymax, xmin:xmax]
ValueError: could not broadcast input array from shape (360,640) into shape (360,640,1)
解决:打印报错对应 img4 和 img 的 shape,有’img4.shape, img.shape = (1280, 1280, 1) (360, 640)',显然是图片尺寸不匹配导致的。将【报错2】重新修改为如下
img4 = np.full((s * 2, s * 2), 114, dtype=np.uint8) # base image with 4 tiles
【报错4】如下
File "D:\yolov5train\yolov5_6.2_grayTrain\utils\dataloaders.py", line 642, in __getitem__
augment_hsv(img, hgain=hyp['hsv_h'], sgain=hyp['hsv_s'], vgain=hyp['hsv_v'])
File "D:\yolov5train\yolov5_6.2_grayTrain\utils\augmentations.py", line 69, in augment_hsv
hue, sat, val = cv2.split(cv2.cvtColor(im, cv2.COLOR_BGR2HSV))
cv2.error: OpenCV(4.2.0) c:\projects\opencv-python\opencv\modules\imgproc\src\color.simd_helpers.hpp:92: error: (-2:Unspecified error) in function '__cdecl cv::impl::`anonymous-namespace'::CvtHelper<struct cv::impl::`anonymous namespace'::Set<3,4,-1>,struct cv::impl::A0x3b52564f::Set<3,-1,-1>,struct cv::impl::A0x3b52564f::Set<0,5,-1>,2>::CvtHelper(const class cv::_InputArray &,const class cv::_OutputArray &,int)'
> Invalid number of channels in input image:
> 'VScn::contains(scn)'
> where
> 'scn' is 1
解决:上述问题是由于hsv通道拆分导致的,此处不需要,将\utils\dataloaders.py中的 line 642对应代码注释掉,如下
# augment_hsv(img, hgain=hyp['hsv_h'], sgain=hyp['hsv_s'], vgain=hyp['hsv_v'])
【报错5】如下
File "D:\yolov5train\yolov5_6.2_grayTrain\utils\dataloaders.py", line 665, in __getitem__
img = img.transpose((2, 0, 1))[::-1] # HWC to CHW, BGR to RGB
ValueError: axes don't match array
解决:上述问题是猜测是由于BGR to RGB转换导致的,将\utils\dataloaders.py的665行源码
img = img.transpose((2, 0, 1))[::-1] # HWC to CHW, BGR to RGB
改为如下是不对的
img = img.transpose((2, 0, 1)) # HWC to CHW
opencv通过HWC加载图片,而Pytorch需要CHW,故需要将图片通过 'transpose((2, 0, 1)) ’ 转为CHW。但此时需要进行 ‘transpose’ 操作的 'img’的尺寸是(640, 640),无法直接进行 ‘transpose’ 操作。故改为如下【报错5】可避免。
img = img.reshape(1, img.shape[0], img.shape[1])
2.6 此时运行 ’train.py‘ 文件可正常epoch跑起来。
此时未涉及修改模型cfg文件。其中train.py中的参数设置如下
'--weights', type=str, default='',
'--cfg', type=str, default='./models/yolov5s.yaml',
'--data', type=str, default= './data/my_yolo5.yaml',
'--epochs', type=int, default=50
'--batch-size', type=int, default=8,
'--imgsz', '--img', '--img-size', type=int, default= 640,
'./models/yolov5s.yaml’参数包含如下
# Parameters
nc: 11 # number of classes
depth_multiple: 0.33 # model depth multiple
width_multiple: 0.50 # layer channel multiple
'./data/my_yolo5.yaml’参数包含如下
train: ../datasTrain4_LQTest_gray/images/train/
val: ../datasTrain4_LQTest_gray/images/val/
# number of classes
nc: 11
# class names
names: [ 'pedes', 'car', 'bus', 'truck', 'bike', 'elec', 'tricycle','coni', 'warm', 'tralight', 'speVeh']
3 模型测试
本次 RGB 和 GRAY 均采用yolov5_6.2无预训练weight训练50epochs。数据集是同一组图像的RGB和GRAY格式,标签文件一模一样。–> 因为数据集较少,2491(train)、360(val),且没有使用预训练模型,epoch=50,所以P、R、mAP相对较低。
3.1 RGB图训练的模型测试
训练对比
一个epoch的train的时间在 4:24-4:34 不等,一个epoch的val的时间在 0:13-0:15 不等。
准确率的对比
trian结束,all:P(0.869) R(0.653) mAP(0.717)@.5 mAP(0.48)@.5:.95
val: all:P(0.712) R(0.651) mAP(0.71)@.5 mAP(0.512)@.5:.95
val推理时间的对比
Speed: 0.3ms pre-process, 7.1ms inference, 1.8ms NMS per image at shape (8, 3, 640, 640)
3.2 GRAY图训练的模型检测测试
训练对比
一个epoch的train的时间在 2:48-2:54 不等,一个epoch的val的时间在 0:08-0:09 不等。
准确率的对比
trian结束,all:P(0.905) R(0.617) mAP(0.705)@.5 mAP(0.464)@.5:.95
val: all:P(0.728) R(0.619) mAP(0.696)@.5 mAP(0.497)@.5:.95
val推理时间的对比
Speed: 0.1ms pre-process, 4ms inference, 2.3ms NMS per image at shape (8, 1, 640, 640)
上述运行val.py过程中,
【报错6】如下
...
File "D:/yolov5train/yolov5_6.2_grayTrain/val.py", line 169, in run
model.warmup(imgsz=(1 if pt else batch_size, 3, imgsz, imgsz)) # warmup
...
File "D:\AppData\anaconda3.8\envs\yolov5t\lib\site-packages\torch\nn\modules\conv.py", line 442, in _conv_forward
return F.conv2d(input, weight, bias, self.stride,
RuntimeError: Given groups=1, weight of size [32, 1, 6, 6], expected input[1, 3, 640, 640] to have 1 channels, but got 3 channels instead
解决:将val.py源码中169行代码
model.warmup(imgsz=(1 if pt else batch_size, 3, imgsz, imgsz)) # warmup
改为如下,【报错6】得以解决。
model.warmup(imgsz=(1 if pt else batch_size, 1, imgsz, imgsz)) # warmup
3.3 RGB和GRAY训练测试对比
本次 RGB 和 GRAY 均采用yolov5_6.2无预训练weight训练50epochs。数据集是同一组图像的RGB和GRAY格式,标签文件一模一样。 因为数据集分配:2491(train)、360(val)。对比结果如下:
【表3-1 RGB和GRAY模型结论对比】
4 结论
综合表3-1可得结论如下:
(1)通过采用RGB和GRAY对图像的对比,采用灰度图像训练和推理的时间确有减少。我们关注的推理时间从7.1降到4.1,减少了3 / 7.1 = 42%的时间。
(2)测试的P、R、mAP@.5值存在差异,但50轮epochs的差异并不大。由于在无预训练模型的状态下,训练50轮。
5 测试RGB转Gray时间
通过 ‘detect.py’ 测试。将模型传入的参数 ch=3 改为 1 。 ‘–source’ 参数取文件夹中均为 RGB 的图像。发现直接检测完会按照gray格式保存在了 ‘runs/val’ 目录下。原来是自己在 ‘utils/dataloaders.py’ 源码中 ‘class LoadImages:’ 类里的方法 ‘def next(self):’ 中,已经改为了 ‘img0 = cv2.imread(path, 0)’ 。于是,自己测了一下 ‘cv2.imread()’ 方法的耗时,如下:
读取RGB图的耗时(s): 0.02003192901611328
读取灰度图的耗时(s): 0.011004924774169922
其实,‘cv2.imread()’ 方法的耗时对比是完全没有必要的,我们通常通过rtsp流或者相机SDK取流,拿到的摄相机数据流为yuv格式,而y通道的数据就是gray数据。反而要获取RGB数据需要yuv转RGB公式的计算。
有必要研究一下,① 目标检测在部署的时候,是怎么来读取相机中的视频的。② 相机SDK给出来的是什么样的数据形式。③ 目标检测的前处理当中,是对rgb图做处理吗,还是视频流过来的其它格式(或形式的数据),是否需要转为rgb的格式后再做其它的前处理操作。考虑包含相机的编解码操作在内的原理。
【上段落中提到的有必要研究的3点,有没有好一点的资源推荐呢,感谢~】
如下为图像所有像素点的 R、G、B 分量与 Y、U、V 分量之间相互转换的公式。
{
Y
=
0.299
∗
R
+
0.587
∗
G
+
.
0114
∗
B
U
=
−
0.147
∗
R
−
0.289
∗
G
+
0.436
∗
B
V
=
0.615
∗
R
−
0.515
∗
G
−
0.100
∗
B
\begin{cases} Y = 0.299*R + 0.587*G + .0114*B\\ U = -0.147*R -0.289*G + 0.436*B\\ V = 0.615*R -0.515*G -0.100*B \end{cases}
⎩
⎨
⎧Y=0.299∗R+0.587∗G+.0114∗BU=−0.147∗R−0.289∗G+0.436∗BV=0.615∗R−0.515∗G−0.100∗B
{ R = Y + 1.14 ∗ V G = Y − 0.39 ∗ U − 0.58 ∗ V B = Y + 2.03 ∗ U \begin{cases} R = Y + 1.14*V\\ G = Y - 0.39*U - 0.58*V\\ B = Y + 2.03*U \end{cases} ⎩ ⎨ ⎧R=Y+1.14∗VG=Y−0.39∗U−0.58∗VB=Y+2.03∗U
附1 未修改任何源码,直接使用灰度图像+yolov6_6.2训练
模型参数如图1
图1 也能跑起来,根据理解,也是3通道跑的训练,并且三个通道都是GRAY通道的值。按照yuv理解的话,y通道认为是灰度通道。即相当于3个通道都是y对应的灰度的值。
附2 训练时数据集标签如下
表1 目标标签详情
ID | label | 描述 | 备注 |
---|---|---|---|
0 | pedes | 行人(骑着平衡车 平板车也划到此类) | |
1 | car | 轿车(包括SUV、 MPV(皮卡)、 VAN(面包车)) | |
2 | bus | 大巴车、公交车 | |
3 | truck | 卡车、货车 | |
4 | bike | 自行车 | |
5 | elec | 摩托车(电摩托车) | |
6 | tricycle | 三轮车(电三轮车、油三轮车) | |
7 | coni | 锥桶 | |
8 | warm | 警示柱 | |
9 | tralight | 交通信号灯 | |
10 | speVeh | 紧急或特殊车辆 (救护车、消防车、工程车辆如吊车挖掘机渣土车等 |