车辆识别笔记
1.首先分享下学习资源(gihub上找到的)
CaptainEven/Vehicle-Car-detection-and-multilabel-classification: 使用YOLO_v3_tiny和B-CNN实现街头车辆的检测和车辆属性的多标签识别 Using yolo_v3_tiny to do vehicle or car detection and attribute’s multilabel classification or recognize (github.com) 引入资源: 先在网站上把链接复制
再创建文件保存的地址(尽量不要有中文路径): 将上图所指位置改成cmd并输入回车,此时进入命令行就是在当前目录下 最后在命令行中输入: git clone https://github.com/CaptainEven/Vehicle-Car-detection-and-multilabel-classification.git
就可以将整个文件复制进当前目录下。
注意:复制时需要用VPN等软件翻墙,否则会报以下错误:
之后的文件操作只要跟着readme文件走就可以了。
2.VehicleDC.py代码理解:
(1).相关库的调用以及选择对应的device和文件路径
(其中训练模型可按自己要求进行调换)
import os
import sys
import re
import time
import pickle
import shutil
import random
import argparse
from darknet_util import *
from darknet import Darknet
from preprocess import prep_image, process_img, inp_to_image
from dataset import color_attrs, direction_attrs, type_attrs
import torch
import torchvision
import paramiko
import cv2
import numpy as np
import PIL
from PIL import Image
from matplotlib import pyplot as plt
from matplotlib.widgets import Cursor
from matplotlib.image import AxesImage
from scipy.spatial.distance import cityblock
from tqdm import tqdm
from pylab import *
mpl.rcParams['font.sans-serif'] = ['SimHei']
use_cuda = True
os.environ['CUDA_DEVICE_ORDER'] = 'PCI_BUS_ID'
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
if use_cuda:
torch.manual_seed(0)
torch.cuda.manual_seed_all(0)
print('=> device: ', device)
"""
此处设定相关的文件路径,如模型等
此处采取的是相对文件路径,可改绝对路径
"""
local_model_path = './checkpoints/epoch_39.pth'
local_car_cfg_path = './car.cfg'
local_car_det_weights_path = './car_540000.weights'
(2).以类的形式引用resnet18模型以及定义相关函数参数、前向传播
? 本段,先导入对应的resnet18模型,随后用torch.nn.Sequential()构建计算图,将之后子层的计算放入计算图中,定义相应的前向传播,最后以torch.nn.Linear()全连接层计算出数据。 (其中,resnet18模型只有卷积、全连接层,避免了池化层带来的低阶特性消失的问题。)
class Cls_Net(torch.nn.Module):
def __init__(self, num_cls, input_size):
torch.nn.Module.__init__(self)
self._num_cls = num_cls
self.input_size = input_size
self.features = torchvision.models.resnet18(pretrained=True)
del self.features.fc
self.features = torch.nn.Sequential(
*list(self.features.children()))
'''
torch.nn.Sequential()为一个有序的容器,神经网络模块将按照在传入构造器的顺序依次被添加到计算图中执行,同时以神经网络模块为元素的有序字典也可以作为传入参数。
model.children()只会遍历模型的子层,并以列表的形式存起来
'''
self.fc = torch.nn.Linear(512 ** 2, num_cls)
def forward(self, X):
N = X.size()[0]
X = self.features(X)
X = X.view(N, 512, 1 ** 2)
X = torch.bmm(X, torch.transpose(X, 1, 2)) / (1 ** 2)
X = X.view(N, 512 ** 2)
X = torch.sqrt(X + 1e-5)
X = torch.nn.functional.normalize(X)
X = self.fc(X)
assert X.size() == (N, self._num_cls)
return X
(3).车辆分类 实现各个标签的划分及对图像类型进行转换
? 本段,先用transforms对图像进行相应的预处理,再将各个标签划分开并定义各个函数,例get_predict()函数(用于获取图像预测标签),其中主要的predict()函数中先是将图像转换为tensor类型,再代入前向传播中算相应的数据,再把数据代入get_predict()中,得到相应的预测标签,从而实现车的分类。
class Car_Classifier(object):
def __init__(self,num_cls,model_path=local_model_path):
self.net = Cls_Net(num_cls=num_cls, input_size=224).to(device)
self.net.load_state_dict(torch.load(model_path))
print('=> vehicle classifier loaded from %s' % model_path)
self.net.eval()
self.transforms = torchvision.transforms.Compose([
torchvision.transforms.Resize(size=224),
torchvision.transforms.CenterCrop(size=224),
torchvision.transforms.ToTensor(),
torchvision.transforms.Normalize(mean=(0.485, 0.456, 0.406),
std=(0.229, 0.224, 0.225))
])
self.color_attrs = color_attrs
print('=> color_attrs:\n', self.color_attrs)
self.direction_attrs = direction_attrs
print('=> direction attrs:\n', self.direction_attrs)
self.type_attrs = type_attrs
print('=> type_attrs:\n', self.type_attrs)
def get_predict(self, output):
output = output.cpu()
'''
从CPU中拿出输出(颜色、方向、种类的预测值)
'''
pred_color = output[:, :9]
pred_direction = output[:, 9:11]
pred_type = output[:, 11:]
color_idx = pred_color.max(1, keepdim=True)[1]
direction_idx = pred_direction.max(1, keepdim=True)[1]
type_idx = pred_type.max(1, keepdim=True)[1]
pred = torch.cat((color_idx, direction_idx, type_idx), dim=1)
return pred
def pre_process(self, image):
if type(image) == np.ndarray:
if image.shape[2] == 3:
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
"""
cv2.cvtColor(p1,p2) 是颜色空间转换函数,p1是需要转换的图片,p2是转换成何种格式。
其中:cv2.COLOR_BGR2RGB 将BGR格式转换成RGB格式
cv2.COLOR_BGR2GRAY 将BGR格式转换成灰度图片
"""
elif image.shape[2] == 1:
image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB)
image = Image.fromarray(image)
elif type(image) == PIL.JpegImagePlugin.JpegImageFile:
if image.mode == 'L' or image.mode == 'I':
image = image.convert('RGB')
return image
def predict(self, img):
img = self.transforms(img)
img = img.view(1, 3, 224, 224)
img = img.to(device)
output = self.net.forward(img)
pred = self.get_predict(output)
color_name = self.color_attrs[pred[0][0]]
direction_name = self.direction_attrs[pred[0][1]]
type_name = self.type_attrs[pred[0][2]]
return color_name, direction_name, type_name
(4).汽车的检测和识别
? Bounding Box预测(Bounding box predictions):比如输入图像为100*100,然后我们在图像上画网格(可以是3×3按需求定义,网格的数量多少反映了图像识别的精细程度,但越多的网格处理起来也相应的更麻烦)。其中每个格子都有自己对应的标签y,𝑦 = [𝑝𝑐 𝑏𝑥 𝑏𝑦 𝑏? 𝑏𝑤 𝑐1 𝑐2 𝑐3 ],其中pc的1或0表示是否有图像,bx、by、bh、bw表示(如果存在)图像的坐标,而后面的c1、c2、c3表示对应图像的标签。
1.准备text文件的存放地址 2.初始化模型与图像的维度 3.根据bbox预测车辆属性并绘制至orig_img 4.图像的识别与检测
class Car_DC():
def __init__(self,
src_dir,
dst_dir,
car_cfg_path=local_car_cfg_path,
car_det_weights_path=local_car_det_weights_path,
inp_dim=768,
prob_th=0.2,
nms_th=0.4,
num_classes=1):
self.inp_dim = inp_dim
self.prob_th = prob_th
self.nms_th = nms_th
'''
nms:即non maximum suppression即非极大抑制,顾名思义就是抑制不是极大值的元素,搜索局部的极大值,因为一张图片中经常会找出很多个可能是物体的矩形框,但是很多矩形框是没有意义的,所以需要nms来剔除
'''
self.num_classes = num_classes
self.dst_dir = dst_dir
if os.path.exists(self.dst_dir):
for x in os.listdir(self.dst_dir):
if x.endswith('.jpg'):
os.remove(self.dst_dir + '/' + x)
else:
os.makedirs(self.dst_dir)
self.detector = Darknet(car_cfg_path)
self.detector.load_weights(car_det_weights_path)
self.detector.net_info['height'] = self.inp_dim
self.detector.to(device)
self.detector.eval()
print('=> car detection model initiated.')
self.classifier = Car_Classifier(num_cls=19,
model_path=local_model_path)
self.imgs_path = [os.path.join(src_dir, x) for x in os.listdir(
src_dir) if x.endswith('.jpg')]
def cls_draw_bbox(self, output, orig_img):
"""
1. 根据车辆的 bbox 预测车辆属性
2. 绘制bbox至orig_img
"""
labels = []
pt_1s = []
pt_2s = []
for det in output:
pt_1 = tuple(det[1:3].int())
pt_2 = tuple(det[3:5].int())
pt_1s.append(pt_1)
pt_2s.append(pt_2)
ROI = Image.fromarray(
orig_img[pt_1[1]: pt_2[1],
pt_1[0]: pt_2[0]][:, :, ::-1])
car_color, car_direction, car_type = self.classifier.predict(ROI)
label = str(car_color + ' ' + car_direction + ' ' + car_type)
labels.append(label)
print('=> predicted label: ', label)
color = (0, 215, 255)
for i, det in enumerate(output):
pt_1 = pt_1s[i]
pt_2 = pt_2s[i]
cv2.rectangle(orig_img, pt_1, pt_2, color, thickness=2)
txt_size = cv2.getTextSize(
label, cv2.FONT_HERSHEY_PLAIN, 2, 2)[0]
pt_2 = pt_1[0] + txt_size[0] + 3, pt_1[1] - txt_size[1] - 5
cv2.rectangle(orig_img, pt_1, pt_2, color, thickness=-1)
cv2.putText(orig_img, labels[i], (pt_1[0], pt_1[1]),
cv2.FONT_HERSHEY_PLAIN, 2, [225, 255, 255], 2)
def process_predict(self,
prediction,
prob_th,
num_cls,
nms_th,
inp_dim,
orig_img_size):
scaling_factor = min([inp_dim / float(x)
for x in orig_img_size])
output = post_process(prediction,
prob_th,
num_cls,
nms=True,
nms_conf=nms_th,
CUDA=True)
if type(output) != int:
output[:, [1, 3]] -= (inp_dim - scaling_factor *
orig_img_size[0]) / 2.0
output[:, [2, 4]] -= (inp_dim - scaling_factor *
orig_img_size[1]) / 2.0
output[:, 1:5] /= scaling_factor
for i in range(output.shape[0]):
output[i, [1, 3]] = torch.clamp(
output[i, [1, 3]], 0.0, orig_img_size[0])
output[i, [2, 4]] = torch.clamp(
output[i, [2, 4]], 0.0, orig_img_size[1])
return output
def detect_classify(self):
for x in self.imgs_path:
img = Image.open(x)
img2det = process_img(img, self.inp_dim)
img2det = img2det.to(device)
prediction = self.detector.forward(img2det, CUDA=True)
orig_img_size = list(img.size)
output = self.process_predict(prediction,
self.prob_th,
self.num_classes,
self.nms_th,
self.inp_dim,
orig_img_size)
orig_img = cv2.cvtColor(np.asarray(
img), cv2.COLOR_RGB2BGR)
if type(output) != int:
self.cls_draw_bbox(output, orig_img)
dst_path = self.dst_dir + '/' + os.path.split(x)[1]
if not os.path.exists(dst_path):
cv2.imwrite(dst_path, orig_img)
parser = argparse.ArgumentParser(description='Detect and classify cars.')
parser.add_argument('-src-dir',
type=str,
default='./test_imgs',
help='source directory of images')
parser.add_argument('-dst-dir',
type=str,
default='./test_result',
help='destination directory of images to store results.')
'''
argparse 模块可以让人轻松编写用户友好的命令行接口。
使用 argparse 的第一步是创建一个 ArgumentParser 对象。
ArgumentParser 对象包含将命令行解析成 Python 数据类型所需的全部信息。
给一个 ArgumentParser 添加程序参数信息是通过调用 add_argument() 方法完成的。
解析参数 parser.parse_args()
'''
(5).主函数调用
if __name__ == '__main__':
args = parser.parse_args()
DR_model = Car_DC(src_dir=args.src_dir, dst_dir=args.dst_dir)
DR_model.detect_classify()
友好的命令行接口。 使用 argparse 的第一步是创建一个 ArgumentParser 对象。 ArgumentParser 对象包含将命令行解析成 Python 数据类型所需的全部信息。 给一个 ArgumentParser 添加程序参数信息是通过调用 add_argument() 方法完成的。 解析参数 parser.parse_args()
运行效果如下:
|