基於Paddle的車輛逆行違章檢測
作者:樑瑛平
QQ:1691608003
博客地址:https://blog.csdn.net/weixin_44936889
項目地址:https://aistudio.baidu.com/aistudio/projectdetail/453620
(有問題歡迎討論~)
簡介:
該項目使用Paddle框架開發,在 AI Studio 平臺運行,
屬於整體項目的一個功能模塊(逆行違章檢測模塊)。
該模型使用YOLOv3算法進行車輛的檢測;
使用ResNet18進行車輛狀態識別(包括朝向,顏色和類型);
該項目使用了PaddleDetection工具和X2Paddle工具進行開發。
其中:
- PaddleDetection 提供了很好的應用接口和預訓練模型,實現了快速的車輛檢測;
- X2Paddle 則解決了不同深度學習框架的模型權重文件轉換的問題;
- AI Studio提供了模型訓練和測試的平臺,並無需我們自己配置環境,提高了開發效率;
最終效果如圖:
緒論:
對於目前絕大多數的交通監控而言,依舊是沿用傳統的管理模式。
對着國民經濟的日益發展和交通產業的需求日益增長,智能交通視覺系統的發展將趨向用機器視覺下的圖像處理技術進行實時監測。
基於深度學習的計算機視覺技術的發展,使得智能監控的精度不斷提升。
一些模型的性能已經能夠達到應用水平。
並且近年來提出的許多壓縮模型和優化模型的方法,使得模型參數和計算量進一步下降。
從而使得深度學習模型能夠在智能監控、嵌入式設備等計算資源受限的平臺使用。
PaddleDetection 簡介:
源碼地址:https://github.com/PaddlePaddle/PaddleDetection
官方文檔:https://paddledetection.readthedocs.io/
PaddleDetection 創立的目的是爲工業界和學術界提供豐富、易用的目標檢測模型。不僅性能優越、易於部署,而且能夠靈活的滿足算法研究的需求。
簡而言之就是,該工具使用百度開源的 Paddle 框架,集成了多種圖像識別和目標檢測框架,並且提供了相應的訓練、推理和部署工具,使得用戶可以自己 DIY 數據集和模型細節,實現深度學習落地應用的快速部署。
特點:
- 易部署:PaddleDetection的模型中使用的核心算子均通過C++或CUDA實現,同時基於PaddlePaddle的高性能推理引擎可以方便地部署在多種硬件平臺上。
- 高靈活度:PaddleDetection通過模塊化設計來解耦各個組件,基於配置文件可以輕鬆地搭建各種檢測模型。
- 高性能:基於PaddlePaddle框架的高性能內核,在模型訓練速度、顯存佔用上有一定的優勢。例如,YOLOv3的訓練速度快於其他框架,在Tesla V100 16GB環境下,Mask-RCNN(ResNet50)可以單卡Batch Size可以達到4 (甚至到5)。
支持的主流模型包括:
並且支持多種拓展特性:
使得開發者只需修改相應的 yml 格式參數文件,即可一鍵 DIY 並訓練自己的模型:
ResNet簡介:
2.1 論文地址:
《Deep Residual Learning for Image Recognition》
2.2 核心思想:
將本來回歸的目標函數H(x)轉化爲F(x)+x,即F(x) = H(x) - x,稱之爲殘差。
2.3 網絡結構:
2.3.1 殘差單元:
ResNet的基本的殘差單元如圖所示:
基本結構如圖,假設每個單元輸入的特徵層爲x,經過兩個卷積層獲得輸出y,將x與y求和即得到了這個單元的輸出;
在訓練時,我們將該單元目標映射(即要趨近的最優解)假設爲F(x) + x,而輸出爲y+x,那麼訓練的目標就變成了使y趨近於F(x)。即去掉映射前後相同的主體部分x,從而突出微小的變化(殘差)。
用數學表達式表示爲:
其中:
- x是殘差單元的輸入;
- y是殘差單元的輸出;
- F(x)是目標映射;
- {Wi}是殘差單元中的卷積層;
- Ws是一個1x1卷積核大小的卷積,作用是給x降維或升維,從而與輸出y大小一致(因爲需要求和);
2.3.2 改進單元:
同時也可以進一步拓展殘差結構:
原論文中則以VGG爲例:
從VGG的19層,拓展到了34層。
可見使用了殘差單元可以大大加深卷積神經網絡的深度,而且不會影響性能和訓練速度.
YOLOv3簡介:
(一)論文地址:
https://arxiv.org/abs/1804.02767
(二)核心思想:
‘Sometimes you just kinda phone it in for a year, you know?’
作者說他一年大部分時間去刷 Twitter 了,然後玩了(play around)一陣子 GAN,正好剩下一點時間,就改進了一下 YOLO 算法,提出了 YOLO v3;
(作者有點皮呀)
(三)DarkNet-53:
作者在 DarkNet-19 的基礎上加入了殘差結構,使得它在 ImageNet 上的表現跟 ResNet-101 相差無幾,但是處理速度卻快得多;
(四)Class Prediction:
這裏由於 YOLO 使用了多層 label,因此使用了 Logistic 迴歸和 binary cross-entropy 損失函數;
(五)Predictions Across Scales:
這裏每個區域使用了 3 個不同大小的 Anchor,其餘設置跟 YOLO v2 相同;
並且作者使用了跟 FPN 相似的特徵金字塔進行預測,共選出了 3 個特徵層去針對不同大小的物體,這三個特徵層大小分別爲:13,26,52;
(六)實驗結果:
(非常快😀)
添加工作環境路徑
這一步導入os、sys庫
設置工作環境爲:’/home/aistudio/work’
並添加掃描路徑:’/home/aistudio/external-libraries’
# 同時添加如下代碼, 這樣每次環境(kernel)啓動的時候只要運行下方代碼即可:
# Also add the following code, so that every time the environment (kernel) starts, just run the following code:
import os
import sys
os.chdir('/home/aistudio/work')
sys.path.append('/home/aistudio/external-libraries')
解壓權重文件:
這一步使用命令行命令解壓權重文件:
# !unzip /home/aistudio/data/data33477/clsmodel.zip -d /home/aistudio/work
# !unzip /home/aistudio/data/data33477/darknet.zip -d /home/aistudio/work
導入需要的包
import cv2
from paddle_model_cls.model_with_code.model import x2paddle_net
import argparse
import functools
import numpy as np
import paddle.fluid as fluid
import matplotlib.pyplot as plt
定義圖像預處理函數:
這裏主要是將圖像進行標準化
然後將圖像放縮到224×224大小
def process_img(img, image_shape=[3, 224, 224]):
mean = [0.485, 0.456, 0.406]
std = [0.229, 0.224, 0.225]
img = cv2.resize(img, (image_shape[1], image_shape[2]))
#img = cv2.resize(img,(256,256))
#img = crop_image(img, image_shape[1], True)
# RBG img [224,224,3]->[3,224,224]
img = img[:, :, ::-1].astype('float32').transpose((2, 0, 1)) / 255
#img = img.astype('float32').transpose((2, 0, 1)) / 255
img_mean = np.array(mean).reshape((3, 1, 1))
img_std = np.array(std).reshape((3, 1, 1))
img -= img_mean
img /= img_std
img = img.astype('float32')
img = np.expand_dims(img, axis=0)
return img
定義預測函數:
功能是將one hot表示的標籤轉換爲顏色、朝向、類別的索引
def get_predict(output):
output = np.squeeze(output)
pred_color = output[:9]
pred_direction = output[9:11]
pred_type = output[11:]
color_idx = np.argmax(pred_color)
direction_idx = np.argmax(pred_direction)
type_idx = np.argmax(pred_type)
return color_idx, direction_idx, type_idx
定義分類器:
這裏定義了分類器的一個類,用於獲取圖像輸入,並輸出預測;
注意CPU環境中use_gpu需要改成False
use_gpu = False
class CarClassifier(object):
def __init__(self):
self.color_attrs = ['Black', 'Blue', 'Brown',
'Gray', 'Green', 'Pink',
'Red', 'White', 'Yellow'] # 車體顏色
self.direction_attrs = ['Front', 'Rear'] # 拍攝位置
self.type_attrs = ['passengerCar', 'saloonCar',
'shopTruck', 'suv', 'trailer', 'truck', 'van', 'waggon'] # 車輛類型
self.init_params()
def inference(self, img):
fetch_list = [self.out.name]
output = self.exe.run(self.eval_program,
fetch_list=fetch_list,
feed={'image': img})
color_idx, direction_idx, type_idx = get_predict(np.array(output))
color_name = self.color_attrs[color_idx]
direction_name = self.direction_attrs[direction_idx]
type_name = self.type_attrs[type_idx]
return color_name, direction_name, type_name
def init_params(self):
# Attack graph
adv_program = fluid.Program()
# 完成初始化
with fluid.program_guard(adv_program):
input_layer = fluid.layers.data(
name='image', shape=[3, 224, 224], dtype='float32')
# 設置爲可以計算梯度
input_layer.stop_gradient = False
# model definition
_, out_logits = x2paddle_net(inputs=input_layer)
self.out = fluid.layers.softmax(out_logits[0])
place = fluid.CUDAPlace(0) if use_gpu else fluid.CPUPlace()
self.exe = fluid.Executor(place)
self.exe.run(fluid.default_startup_program())
# 記載模型參數
fluid.io.load_persistables(
self.exe, './paddle_model_cls/model_with_code/')
# 創建測試用評估模式
self.eval_program = adv_program.clone(for_test=True)
def predict(self, im):
im_input = process_img(im)
color_name, direction_name, type_name = self.inference(im_input)
label = '顏色:{}\n朝向:{}\n類型:{}'.format(
color_name, direction_name, type_name)
return label
定義並測試分類器
結果如圖:
%matplotlib inline
classifier = CarClassifier()
im = cv2.imread('a.png')
result = classifier.predict(im)
print(result)
plt.imshow(im[:, :, [2, 1, 0]])
plt.show()
顏色:Yellow
朝向:Front
類型:suv
導入其他包
主要是一些繪圖工具(PIL)和PaddleDetection工具(ppdet)
import ppdet.utils.checkpoint as checkpoint
from ppdet.utils.cli import ArgsParser
from ppdet.utils.eval_utils import parse_fetches
from ppdet.core.workspace import load_config, create
from paddle import fluid
import os
import cv2
import glob
from ppdet.utils.coco_eval import bbox2out, mask2out, get_category_info
import numpy as np
from PIL import Image
from PIL import ImageFont, ImageDraw
定義繪圖函數
使用PIL庫解決opencv不支持中文的問題
font_path = r'./simsun.ttc' # 新宋體
font = ImageFont.truetype(font_path, 16)
def putText(img, text, x, y, color=(0, 0, 255)):
img_pil = Image.fromarray(img)
draw = ImageDraw.Draw(img_pil)
b, g, r = color
a = 0
draw.text((x, y), text, font=font, fill=(b, g, r, a))
img = np.array(img_pil)
return img
定義目標檢測類
class VehicleDetector(object):
def __init__(self):
self.size = 608
self.draw_threshold = 0.1
self.cfg = load_config('./configs/vehicle_yolov3_darknet.yml')
self.place = fluid.CUDAPlace(
0) if use_gpu else fluid.CPUPlace()
# self.place = fluid.CPUPlace()
self.exe = fluid.Executor(self.place)
self.model = create(self.cfg.architecture)
self.classifier = classifier
self.init_params()
def draw_bbox(self, image, catid2name, bboxes, threshold):
raw = image.copy()
for dt in np.array(bboxes):
catid, bbox, score = dt['category_id'], dt['bbox'], dt['score']
if score < threshold or catid == 6:
# if score < threshold:
continue
xmin, ymin, w, h = bbox
xmin = int(xmin)
ymin = int(ymin)
xmax = int(xmin + w)
ymax = int(ymin + h)
roi = raw[ymin:ymax, xmin:xmax].copy()
label = self.classifier.predict(roi)
cv2.rectangle(image, (xmin, ymin), (xmax, ymax), (255, 0, 0), 4)
image = putText(image, label, 0, 10, color=(255, 0, 0))
print(label)
print()
return image
def init_params(self):
startup_prog = fluid.Program()
infer_prog = fluid.Program()
with fluid.program_guard(infer_prog, startup_prog):
with fluid.unique_name.guard():
inputs_def = self.cfg['TestReader']['inputs_def']
inputs_def['iterable'] = True
feed_vars, loader = self.model.build_inputs(**inputs_def)
test_fetches = self.model.test(feed_vars)
infer_prog = infer_prog.clone(True)
self.exe.run(startup_prog)
if self.cfg.weights:
checkpoint.load_params(self.exe, infer_prog, self.cfg.weights)
extra_keys = ['im_info', 'im_id', 'im_shape']
self.keys, self.values, _ = parse_fetches(
test_fetches, infer_prog, extra_keys)
dataset = self.cfg.TestReader['dataset']
anno_file = dataset.get_anno()
with_background = dataset.with_background
use_default_label = dataset.use_default_label
self.clsid2catid, self.catid2name = get_category_info(anno_file, with_background,
use_default_label)
is_bbox_normalized = False
if hasattr(self.model, 'is_bbox_normalized') and \
callable(self.model.is_bbox_normalized):
is_bbox_normalized = self.model.is_bbox_normalized()
self.is_bbox_normalized = is_bbox_normalized
self.infer_prog = infer_prog
def process_img(self, img):
mean = [0.485, 0.456, 0.406]
std = [0.229, 0.224, 0.225]
shape = img.shape[:2]
img = cv2.resize(img, (self.size, self.size))
# RBG img [224,224,3]->[3,224,224]
img = img[:, :, ::-1].astype('float32').transpose((2, 0, 1)) / 255
img_mean = np.array(mean).reshape((3, 1, 1))
img_std = np.array(std).reshape((3, 1, 1))
img -= img_mean
img /= img_std
img = img.astype('float32')
img = np.expand_dims(img, axis=0)
shape = np.expand_dims(np.array(shape), axis=0)
im_id = np.zeros((1, 1), dtype=np.int64)
return img, im_id, shape
def detect(self, img):
# img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
raw = img.copy()
img, im_id, shape = self.process_img(img=img)
outs = self.exe.run(self.infer_prog,
feed={'image': img, 'im_size': shape.astype(np.int32), 'im_id': im_id},
fetch_list=self.values,
return_numpy=False)
res = {
k: (np.array(v), v.recursive_sequence_lengths())
for k, v in zip(self.keys, outs)
}
bbox_results = bbox2out(
[res], self.clsid2catid, self.is_bbox_normalized)
result = self.draw_bbox(raw, self.catid2name,
bbox_results, self.draw_threshold)
return result
測試檢測+識別
效果如圖:
det = VehicleDetector()
im = cv2.imread('./car.jpg')
result = det.detect(im)
plt.imshow(result[:, :, [2,1,0]])
plt.show()
cv2.imwrite('./result.png', result)
2020-05-06 16:41:19,803-INFO: Loading parameters from ./vehicle_yolov3_darknet...
2020-05-06 16:41:19,804-WARNING: ./vehicle_yolov3_darknet.pdparams not found, try to load model file saved with [ save_params, save_persistables, save_vars ]
2020-05-06 16:41:19,804-WARNING: ./vehicle_yolov3_darknet.pdparams not found, try to load model file saved with [ save_params, save_persistables, save_vars ]
2020-05-06 16:41:20,397-INFO: Not found annotation file contrib/VehicleDetection/vehicle.json, load coco17 categories.
顏色:Yellow
朝向:Front
類型:passengerCar
True
進行車輛逆行檢測:
進行逆行檢測只需要規定好正確朝向
對於非正確朝向的車輛,我們標註爲違規。
正常車輛爲綠色,違規車輛標註爲紅色
right_direct = 'Rear'
class new_Det(VehicleDetector):
def __init__(self):
self.size = 608
self.draw_threshold = 0.1
self.cfg = load_config('./configs/vehicle_yolov3_darknet.yml')
self.place = fluid.CUDAPlace(
0) if use_gpu else fluid.CPUPlace()
# self.place = fluid.CPUPlace()
self.exe = fluid.Executor(self.place)
self.model = create(self.cfg.architecture)
self.classifier = classifier
self.init_params()
def init_params(self):
startup_prog = fluid.Program()
infer_prog = fluid.Program()
with fluid.program_guard(infer_prog, startup_prog):
with fluid.unique_name.guard():
inputs_def = self.cfg['TestReader']['inputs_def']
inputs_def['iterable'] = True
feed_vars, loader = self.model.build_inputs(**inputs_def)
test_fetches = self.model.test(feed_vars)
infer_prog = infer_prog.clone(True)
self.exe.run(startup_prog)
if self.cfg.weights:
checkpoint.load_params(self.exe, infer_prog, self.cfg.weights)
extra_keys = ['im_info', 'im_id', 'im_shape']
self.keys, self.values, _ = parse_fetches(
test_fetches, infer_prog, extra_keys)
dataset = self.cfg.TestReader['dataset']
anno_file = dataset.get_anno()
with_background = dataset.with_background
use_default_label = dataset.use_default_label
self.clsid2catid, self.catid2name = get_category_info(anno_file, with_background,
use_default_label)
is_bbox_normalized = False
if hasattr(self.model, 'is_bbox_normalized') and \
callable(self.model.is_bbox_normalized):
is_bbox_normalized = self.model.is_bbox_normalized()
self.is_bbox_normalized = is_bbox_normalized
self.infer_prog = infer_prog
def process_img(self, img):
mean = [0.485, 0.456, 0.406]
std = [0.229, 0.224, 0.225]
shape = img.shape[:2]
img = cv2.resize(img, (self.size, self.size))
# RBG img [224,224,3]->[3,224,224]
img = img[:, :, ::-1].astype('float32').transpose((2, 0, 1)) / 255
img_mean = np.array(mean).reshape((3, 1, 1))
img_std = np.array(std).reshape((3, 1, 1))
img -= img_mean
img /= img_std
img = img.astype('float32')
img = np.expand_dims(img, axis=0)
shape = np.expand_dims(np.array(shape), axis=0)
im_id = np.zeros((1, 1), dtype=np.int64)
return img, im_id, shape
def detect(self, img):
# img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
raw = img.copy()
img, im_id, shape = self.process_img(img=img)
outs = self.exe.run(self.infer_prog,
feed={'image': img, 'im_size': shape.astype(np.int32), 'im_id': im_id},
fetch_list=self.values,
return_numpy=False)
res = {
k: (np.array(v), v.recursive_sequence_lengths())
for k, v in zip(self.keys, outs)
}
bbox_results = bbox2out(
[res], self.clsid2catid, self.is_bbox_normalized)
result = self.draw_bbox(raw, self.catid2name,
bbox_results, self.draw_threshold)
return result
def draw_bbox(self, image, catid2name, bboxes, threshold):
raw = image.copy()
for dt in np.array(bboxes):
catid, bbox, score = dt['category_id'], dt['bbox'], dt['score']
if score < threshold or catid == 6:
# if score < threshold:
continue
xmin, ymin, w, h = bbox
xmin = int(xmin)
ymin = int(ymin)
xmax = int(xmin + w)
ymax = int(ymin + h)
roi = raw[ymin:ymax, xmin:xmax].copy()
label = self.classifier.predict(roi)
if right_direct in label:
color = (0, 255, 0)
else:
color = (0, 0, 255)
print('檢測到違章逆行!')
cv2.rectangle(image, (xmin, ymin), (xmax, ymax), color, 4)
print(label)
print()
return image
det = new_Det()
2020-05-06 16:42:29,823-INFO: Loading parameters from ./vehicle_yolov3_darknet...
2020-05-06 16:42:29,825-WARNING: ./vehicle_yolov3_darknet.pdparams not found, try to load model file saved with [ save_params, save_persistables, save_vars ]
2020-05-06 16:42:29,825-WARNING: ./vehicle_yolov3_darknet.pdparams not found, try to load model file saved with [ save_params, save_persistables, save_vars ]
2020-05-06 16:42:30,284-INFO: Not found annotation file contrib/VehicleDetection/vehicle.json, load coco17 categories.
最終效果:
im = cv2.imread('./demo.png')
result = det.detect(im)
plt.imshow(result[:, :, [2, 1, 0]])
plt.show()
檢測到違章逆行!
顏色:Black
朝向:Front
類型:saloonCar