基于paddlex、yolov3的行人距离检测学习项目

项目效果基于paddlex、yolov3的行人距离检测学习项目

 配置项目运行环境

!pip install paddlepaddle-gpu==1.8.0 -i https://mirror.baidu.com/pypi/simple #环境使用 paddlepaddle-gpu 1.8.0版本
!pip install paddlex==1.3.7 -i https://mirror.baidu.com/pypi/simple #下载paddlex1.3.7版本模型包

解压数据集

!unzip /home/aistudio/data/data21544/PascalVOC2007.zip -d /home/aistudio/work/  #解压数据集到工作目录work

导入依赖并设置工作环境

import matplotlib 
matplotlib.use('Agg') #修改渲染器使其只生成图像,不显示图像
import os
os.environ['CUDA_VISIBLE_DEVICES'] = '0' #设置程序的运行环境为gpu 此命令必须再导入paddle和paddlex之前执行
import paddlex as pdx
os.chdir('/home/aistudio/work/') #将工作目录切换到work目录

进行数据的读取和准备

base = '/home/aistudio/work/PascalVOC2007/VOC2007_test' #设置数据集目录

imgs = os.listdir(os.path.join(base, 'JPEGImages')) #利用join()函数合成图片所在目录,并获取文件的路径以list的形式返回
print('total:', len(imgs))

with open(os.path.join(base, 'train_list.txt'), 'w') as f: #以读取的方法打开 train_list.txt 若没有则新建此文本
    for im in imgs[:-200]: 
        info = 'JPEGImages/'+im+' '
        info += 'Annotations/'+im[:-4]+'.xml\n'
        f.write(info)                                      #第一个至倒数第200个为训练集 每一数据为图片路径 第二部分为其对应的xml信息

with open(os.path.join(base, 'val_list.txt'), 'w') as f:
    for im in imgs[-200:]:
        info = 'JPEGImages/'+im+' '
        info += 'Annotations/'+im[:-4]+'.xml\n'
        f.write(info)                                      #倒数两百个为测试集

CLASSES = ['aeroplane', 'bicycle', 'bird', 'boat', 'bottle', 'bus',

           'car', 'cat', 'chair', 'cow', 'diningtable', 'dog', 'horse',

           'motorbike', 'person', 'pottedplant', 'sheep', 'sofa',

           'train', 'tvmonitor']  # 记录图像识别中的物体种类

with open('labels.txt', 'w') as f:
    for v in CLASSES:
        f.write(v+'\n')           #写入到work目录下的labels.txt

进行图像增强和标准化

from paddlex.det import transforms #从 paddlex模块中导入图像预处理
train_transforms = transforms.Compose([
    transforms.MixupImage(mixup_epoch=250), #对图像进行mixup操作
    transforms.RandomDistort(), #以一定的概率对图像进行随机像素内容变换
    transforms.RandomExpand(), #随机扩张
    transforms.RandomCrop(), #随机裁剪
    transforms.Resize(target_size=512, interp='RANDOM'), #将图片长宽设置为[target_size, target_size],差值方式为随机
    transforms.RandomHorizontalFlip(), #以一定的概率对图像进行随机水平翻转
    transforms.Normalize(), #图片标准化
])

eval_transforms = transforms.Compose([
    transforms.Resize(target_size=512, interp='CUBIC'), #将图片长宽设置为[target_size, target_size],差值方式为‘CUBIC’
    transforms.Normalize(), #图片标准化
])

模型设置与训练

train_dataset = pdx.datasets.VOCDetection(
    data_dir=base,                                  #设置数据集所在的目录路径
    file_list=os.path.join(base, 'train_list.txt'), #数据集图片文件和对应标注文件的文件路径
    label_list='labels.txt',                        #数据集包含的类别信息文件路径
    transforms=train_transforms,                    #数据集中每个样本的预处理/增强算子
    shuffle=True)                                   #对数据集中样本打乱顺序

eval_dataset = pdx.datasets.VOCDetection(
    data_dir=base,                                  #设置数据集所在的目录路径
    file_list=os.path.join(base, 'val_list.txt'),   #数据集图片文件和对应标注文件的文件路径
    label_list='labels.txt',                        #数据集包含的类别信息文件路径
    transforms=eval_transforms)                     #数据集中每个样本的预处理/增强算子

num_classes = len(train_dataset.labels) + 1 #数据集包含的类别数
model = pdx.det.YOLOv3(                     #采用paddlex中的YOLOv3模型
    num_classes=num_classes,                #设置数据集类别数
    backbone='DarkNet53'                    #主干网络为'DarkNet53'
)                                          
model.train(
    num_epochs=50,                        #训练迭代轮数
    train_dataset=train_dataset,          #设置训练数据集
    train_batch_size=8,                   #训练数据batch的大小
    eval_dataset=eval_dataset,            #设置训练测试集
    learning_rate=0.00025,                #设置优化器的学习率
    lr_decay_epochs=[10, 15],             #优化器的学习率衰减轮数
    save_interval_epochs=10,              #模型保存间隔
    log_interval_steps=100,               #训练日志输出间隔
    save_dir='./YOLOv3',                  #模型保存路径
    pretrain_weights='IMAGENET',          #自动下载在ImageNet图像数据上预训练的模型权重
    use_vdl=True)                         #使用VisualDL进行可视化

model = pdx.load_model('./YOLOv3/best_model') #载入训练出来的最优模型
model.evaluate(eval_dataset, batch_size=1, epoch_id=None, metric=None, return_details=False) #在测试集上进行模型评估

测试模型在图片上的识别效果

import cv2                                  
import time
import numpy as np
import matplotlib.pyplot as plt             #导入图像处理相关的包
%matplotlib inline                          
                                            #ipython魔法函数,可以直接在python console中生成图像

image_name = './test.jpg'                   #设置待处理的图像名称
start = time.time()                         #记录识别开始时间
result = model.predict(image_name, eval_transforms)                         #对图像进行识别
print('infer time:{:.6f}s'.format(time.time()-start))                       #输出图像识别所花时间
print('detected num:', len(result))                                         #输出识别到的目标数

im = cv2.imread(image_name)                                                 #cv2读取图片
font = cv2.FONT_HERSHEY_SIMPLEX                                             #设置添加文字的字体
threshold = 0.0                                                             #设置过滤边界框的阈值,此处为不过滤

for value in result:
    xmin, ymin, w, h = np.array(value['bbox']).astype(np.int)               #获取识别到边界框的坐标、宽度、高度
    cls = value['category']                                                 #获取识别到的物体类别
    score = value['score']                                                  #获取识别的置信度
    if score < threshold:                                                   #如果低于设置的置信阈值就将其过滤
        continue
    cv2.rectangle(im, (xmin, ymin), (xmin+w, ymin+h), (0, 255, 0), 4)       #绘制识别到的边界框,颜色为绿,粗细为4
    cv2.putText(im, '{:s} {:.3f}'.format(cls, score),
                    (xmin, ymin), font, 0.5, (255, 0, 0), thickness=2)      #在左上角显示识别种类和置信度

cv2.imwrite('result.jpg', im)               #将结果图像保存
plt.figure(figsize=(15,12))                 #创建一个画板
plt.imshow(im[:, :, [2,1,0]])               #
plt.show()                                  #显示结果图片

定义基于图像识别的距离检测类

class PersonDistanceDetector(object):                                           

    def __init__(self):                                                 #定义类的构造函数

        self.model = model = pdx.load_model('./YOLOv3/best_model')      #导入训练的识别模型

        self.boxes = []                                                 #定义list用来存放识别到的边界框

        self.threshold = 0.3                                            #定义边界框置信度过滤阈值

        self.process_this_frame = False                                 #设置标志位来记录此图片是否在被处理

    def feedCap(self, frame):

        self.process_this_frame = not self.process_this_frame           #将标志位改为true

        if self.process_this_frame:
            result = self.model.predict(frame)                          #对该帧图像进行识别
            self.boxes = [v['bbox'] for v in result if v['score'] > self.threshold]#将边界框大于置信值的边框加入对象的boxes list中

        circles = []
        for i in range(len(self.boxes)):
            x, y = int(self.boxes[i][0]), int(self.boxes[i][1])         #获取边界框的坐标
            w, h = int(self.boxes[i][2]), int(self.boxes[i][3])         #获取边界框的宽高
            cx, cy = x + w // 2, y + h                                  #定义边界框的中点为底部中点
            frame = cv2.line(frame, (cx, cy), (cx, cy - h // 2), (0, 255, 0), 2)#画一条从中点到边界框几何中点的红线
            frame = cv2.circle(frame, (cx, cy - h // 2), 5, (255, 20, 200), -1) #在边界框的几何中点画一个实心小圆
            circles.append([cx, cy - h // 2, h])                        #将边界框底部的圆加入圆的list
                     
        indexes = []
        #依次判断各个圆之间的距离
        for i in range(len(circles)):   
            x1, y1, r1 = circles[i]     #获取标记圆的坐标和半径
            for j in range(i + 1, len(circles)):    # i之前的都已经判断过,直接从i+1开始
                x2, y2, r2 = circles[j] #获取待比较圆的坐标和半径
                if (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) <= (r1 + r2) * (r1 + r2) and abs(y1 - y2) < r1 // 4: 
                    indexes.append(i)
                    indexes.append(j)               #如果两圆相交且圆心 y 方向的距离小于标记圆半径的四分之一,则记录它们编号
                    cv2.line(frame, (x1, y1), (x2, y2), (0, 0, 255), 2)     #在两圆之间画一条红色的线  

            for i in range(len(circles)):
                x, y, r = circles[i]        #依次对图像中的每个圆进行处理    

                if i in indexes: 
                    transparentOverlay1(frame, dst_circle, (x, y - 5), scale=(r) / 100, color=(0, 0, 255), alphaVal=110)    
                else:
                    transparentOverlay1(frame, dst_circle, (x, y - 5), scale=(r) / 100, color=(0, 200, 0),alphaVal=110)     #对检测距离标记的圆分别采用不同颜色#
            rows, cols, _ = frame.shape              #获取图片的大小信息     
            cv2.putText(frame,
                        "Total Persons : " + str(len(self.boxes)), #显示的内容
                        (20, rows - 40),             #显示的位置
                        fontFace=cv2.QT_FONT_NORMAL, #显示的字体
                        fontScale=1,                 #字体的大小
                        color=(215, 220, 245))       #字体的颜色

        return frame    #返回处理完成的图像

M3 = np.array([
    [0.8092, -0.2960, 11],
    [0.0131, 0.0910, 30],
    [0.0001, -0.0052, 1.0]
])

circle_img = cv2.circle(np.zeros((100, 100, 3)), center=(50, 50), radius=40, color=(0, 240, 0), thickness=4) #在黑色背景中心绘制一个半径为40的圆
dst_circle = cv2.warpPerspective(circle_img, M3, (100, 100)) #将圆变换成椭圆效果的圆(即平行四边形化)

def transparentOverlay1(src, overlay, pos=(0, 0), scale=1, color=(0, 200, 100), alphaVal=255):

    h, w, _ = overlay.shape # 标记圆圈的信息

    rows, cols, _ = src.shape  # 识别图像的信息
    x, y = pos[0], pos[1]  # 原来圆心的位置

    x -= w // 2 #将x设置为圆最左边的点

    background = src[y:min(y + h, rows), x:min(x + w, cols)]  #获取在待画圆范围的原图像
    b_h, b_w, _ = background.shape      #获取识别圆的信息
    if b_h <= 0 or b_w <= 0:
        return src                      #若有一部分不在原图像内则直接返回原图
    foreground = overlay[0:b_h, 0:b_w]  #设置在图片范围内的识别圆
    alpha = foreground[:, :, 1].astype(float) #将图片里的像素信息转化为浮点数形式进行处理
    alpha[alpha > 235] = alphaVal             #去除图像中的黑色部分
    alpha = cv2.merge([alpha, alpha, alpha])  #根据alpha的合成图像
    alpha = alpha / 255.0                     #统一到0-1处理

    foreground = foreground.astype(float)     
    background = background.astype(float)     #变换为浮点数处理

    foreground = np.zeros_like(foreground) + color  #生成和背景图片一样大小的空数组,并设置指定颜色

    foreground = cv2.multiply(alpha, foreground[:, :, :3])
    background = cv2.multiply(1.0 - alpha, background) 
    outImage = cv2.add(foreground, background).astype("uint8") #用待显示的圆替代原图本来位置的图像

    src[y:y + b_h, x:x + b_w] = outImage #实现替换
    return src

安装imutils

!pip install imutils

视频处理

import cv2 #强大的图像处理包
import imutils #一个依赖 Numpy cv2 mat matplotlib 的包,可以更方便的实现图片的旋转放大等操作
from tqdm import tqdm #python 提供的进度条模块

if __name__ == '__main__':
    path = 'p.mp4'  #待处理图像的路径
    det = PersonDistanceDetector() #实例化化一个行人检测对象
    cap = cv2.VideoCapture(path) #cv2.VideoCapture()视频读取函数 默认为电脑‘0’摄像头,也可以是图像的路径
    fps = int(cap.get(5))        #获取图像的帧率
    frames_num = int(cap.get(7)) #获取视频的总帧数

    size = None
    for i in tqdm(range(frames_num)): #对视频的每一帧图像进行识别检测,并以进度条显示执行进度

            flag, im = cap.read() #按帧读取图像,第一个返回值为布尔型 表示是否正确读取,第二个为读取的图像帧

            if not flag: 
                break     #如果未读取到图片就停止循环

            result = det.feedCap(im)
            result = imutils.resize(result, height=500)
            if size is None:
                size = (result.shape[1], result.shape[0])
                fourcc = cv2.VideoWriter_fourcc(
                    'm', 'p', '4', 'v')  # opencv3.0
                videoWriter = cv2.VideoWriter(
                    'result.mp4', fourcc, fps, (result.shape[1],result.shape[0]))
            videoWriter.write(result) #将处理结果result写成视频
            cv2.waitKey(1) #等待键盘输入一毫秒若无输入返回-1,用来更新图片输出

    cap.release()
    videoWriter.release()   #释放线程所占有的资源
    cv2.destroyAllWindows() #删除所有的窗口
    print('done')           #输出工作已经完成

项目参考,在原作者的基础上进行了相关算法和代码的优化【PaddleX助力疫情防护】基于PaddleX的行人社交安全距离检测 - 飞桨AI Studio - 人工智能学习与实训社区

上一篇:module ‘cv2.cv2‘ has no attribute ‘xfeatures2d‘ 错误解决


下一篇:两点截取线【shapely】【clipline】