从0开始 yolov5可以用灰度图像进行训练和检测吗

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、[email protected]值存在差异,但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.299R+0.587G+.0114BU=0.147R0.289G+0.436BV=0.615R0.515G0.100B

{ 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.14VG=Y0.39U0.58VB=Y+2.03U


附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 紧急或特殊车辆 (救护车、消防车、工程车辆如吊车挖掘机渣土车等

猜你喜欢

转载自blog.csdn.net/qq_42835363/article/details/132693597