1、OrangePi应用场景
关于 Orange Pi AI Pro 开发板是香橙派联合华为精心打造的高性能 AI 开发板,其搭载了昇腾 AI 处理器,可提供 8TOPS INT8 的计算能力,内存提供了 8GB 和 16GB两种版本。可以实现图像、视频等多种数据分析与推理计算,可广泛用于教育、机器人、无人机等场景。此次我将测评板卡在智能交通上的应用。处理四路视觉检测、追踪,且完成多摄像头追踪。通常我们将该结构化数据后叫全息路口。
由于简单的开机,远程以及硬件等测评都较为简单,跑通官方demo即可,此处我们移植比较复杂的算法,将该算力压榨干净,以证明其强大的处理能力。硬件接口图如下
1.1 智能交通的全息路口介绍
近年来,国内城镇化迅速发展,城市交通出行需求增长迅速,给城市道路交通秩序与安全管控带来较大压力。路口作为城市道路的重要节点,需要具备精准的数字化感知能力,才能有效提升道路精细化治理能力。
而传统路口的信息感知不精准,交管部门无法根据路口排队车辆多少、有无事故车辆、有无车辆溢出等关键信息来指挥交通。
全息路网通过融合视频和雷达数据,结合高精地图、边缘计算等技术,可全天候精准采集路口车道级流量、排队长度、车辆速度、行车轨迹、停车次数、事故等信息,彻底解决路口绿灯空放问题,可大大缩短了红灯等待时间。某地交管部门在东二环路南北路段应用线性绿波带后。高峰期内,行程时间压缩了20%左右,平峰期行程时间平均压缩了30%,停车次数降低了2次以上。
全息路网也可快速发现路网的异常事件及违法行为,将事件视频、事件地址与辅助判罚报告通知指挥中心,形成接处警快速闭环机制,实现远程处理擦碰事故,有效缓解拥堵,避免二次事故。这里放出一张结果图吧,方便大家了解。使用视觉感知算法,将十字路口所有交通参与者识别、追踪、定位,孪生在高精地图上
1.2 Orange Pi AI Pro 开发板与相机连接图
总的来说就是用Orange Pi AI Pro处理四路摄像头数据,将四路摄像头接入交换机,再将Orange Pi AI Pro的网口也接入交换机,这样Orange Pi AI Pro就可以拉到四路相机的视频流了,示意图如下
首先我们在十字路口安装四个枪机,照射车辆去向,当然也可以复用交警的电警杆,路口安装示意图如下所示
2、Orange Pi AI Pro处理相机rtsp流
由于路口枪机均是网络相机,视觉算法的处理流程如下图所示
2.1 Orange Pi AI Pro解析rtsp视频流
用海康威视的摄像实时读取视频,读取视频的格式是YV12格式,摄像机进行压缩算法处理后的H264视频流通过RTSP协议传向网络应用层,用户拿到的数据是H265格式,需要使用ACLLite接口完成视频解码,将H265文件的前十帧并解码为YUV图片硬解码,得到原来的YV12格式的图像视频,最后经过opencv处理得到RGB格式显示到屏幕或者直接使用ACLLite进行颜色空间转换进行预处理。
官方给出了如何将H265编码视频如何解析成H265文件,具体可参考
https://gitee.com/ascend/EdgeAndRobotics/blob/master/Samples/VideoDecode/src/main.cpp
也可以参考这个案例
https://gitee.com/ascend/samples/tree/master/inference/modelInference/sampleResnetRtsp/cpp
但是并未给出如何解析rtsp流到H265的步骤,这里我们使用AclLite库,使用海康rtsp流作为样例的输入数据,使能Acllite解码rtsp,缩放图片。
直接贴代码
#include <dirent.h>
#include <opencv2/opencv.hpp>
#include "AclLiteUtils.h"
#include "AclLiteImageProc.h"
#include "AclLiteVideoProc.h"
#include "AclLiteResource.h"
#include "AclLiteError.h"
#include "AclLiteModel.h"
//#include "label.h"
using namespace std;
using namespace cv;
typedef enum Result {
SUCCESS = 0,
FAILED = 1
} Result;
aclrtRunMode runMode_;
ImageData resizedImage_;
int32_t modelWidth_;
int32_t modelHeight_;
Result ProcessInput(string testImgPath)
{
// read image from file
ImageData image;
AclLiteError ret = ReadJpeg(image, testImgPath);
if (ret == FAILED) {
ACLLITE_LOG_ERROR("ReadJpeg failed, errorCode is %d", ret);
return FAILED;
}
// copy image from host to dvpp
ImageData imageDevice;
ret = CopyImageToDevice(imageDevice, image, runMode_, MEMORY_DVPP);
if (ret == FAILED) {
ACLLITE_LOG_ERROR("CopyImageToDevice failed, errorCode is %d", ret);
return FAILED;
}
AclLiteImageProc imageProcess_;
// image decoded from JPEG format to YUV
ImageData yuvImage;
ret = imageProcess_.JpegD(yuvImage, imageDevice);
if (ret == FAILED) {
ACLLITE_LOG_ERROR("Convert jpeg to yuv failed, errorCode is %d", ret);
return FAILED;
}
// zoom image to modelWidth_ * modelHeight_
ret = imageProcess_.Resize(resizedImage_, yuvImage, modelWidth_, modelHeight_);
if (ret == FAILED) {
ACLLITE_LOG_ERROR("Resize image failed, errorCode is %d", ret);
return FAILED;
}
return SUCCESS;
}
int main(int argc, char *argv[])
{
int argNum = 2;
if ((argc < argNum)) {
ACLLITE_LOG_ERROR("Please input: ./main rtsp address");
return FAILED;
}
AclLiteError ret = aclrtGetRunMode(&runMode_);
if (ret == FAILED) {
ACLLITE_LOG_ERROR("get runMode failed, errorCode is %d", ret);
return FAILED;
}
std::string inputDataPath = string(argv[1]);
if (ret) {
ACLLITE_LOG_ERROR("Init resource failed, error %d", ret);
return FAILED;
}
// open camera
int device = 0;
AclLiteVideoProc cap = AclLiteVideoProc(inputDataPath, device);
if (!cap.IsOpened()) {
ACLLITE_LOG_ERROR("Open camera failed");
return FAILED;
}
// read frame from camera and inference
while (true)
{
ImageData image;
AclLiteError ret = cap.Read(image);
if (ret) {
break;
}
ACLLITE_LOG_INFO("Execute sample success");
return SUCCESS;
}
}
解码后可使用opencv将yuv转成RGB进行屏幕输出如图
同时拉四路相机流效果如下:
2.2 Orange Pi AI Pro yolo 推理
通过上面解码之后,我们已经得到YUV图,接下来以YOLOV7网络模型,需要使用Acllite对图片进行预处理,并通过模型转换使能静态AIPP功能,使能AIPP功能后,YUV420SP_U8格式图片转化为RGB,然后减均值和归一化操作,并将该信息固化到转换后的离线模型中,对YOLOV7网络执行推理,对图片进行物体检测和分类,并给出标定框和类别置信度。
图片物体检测,并且在图片上给出物体标注框,类别以及置信度。
模型训练与 转换直接参考官网
https://gitee.com/ascend/samples/tree/master/inference/modelInference/sampleYOLOV7
此处我贴出代码与效果
#include <dirent.h>
#include <opencv2/opencv.hpp>
#include "AclLiteUtils.h"
#include "AclLiteImageProc.h"
#include "AclLiteResource.h"
#include "AclLiteError.h"
#include "AclLiteModel.h"
#include "label.h"
using namespace std;
using namespace cv;
typedef enum Result {
SUCCESS = 0,
FAILED = 1
} Result;
typedef struct BoundBox {
float x;
float y;
float width;
float height;
float score;
size_t classIndex;
size_t index;
} BoundBox;
bool sortScore(BoundBox box1, BoundBox box2)
{
return box1.score > box2.score;
}
class SampleYOLOV7 {
public:
SampleYOLOV7(const char *modelPath, const int32_t modelWidth, const int32_t modelHeight);
Result InitResource();
Result ProcessInput(string testImgPath);
Result Inference(std::vector<InferenceOutput>& inferOutputs);
Result GetResult(std::vector<InferenceOutput>& inferOutputs, string imagePath, size_t imageIndex, bool release);
~SampleYOLOV7();
private:
void ReleaseResource();
AclLiteResource aclResource_;
AclLiteImageProc imageProcess_;
AclLiteModel model_;
aclrtRunMode runMode_;
ImageData resizedImage_;
const char *modelPath_;
int32_t modelWidth_;
int32_t modelHeight_;
};
SampleYOLOV7::SampleYOLOV7(const char *modelPath, const int32_t modelWidth, const int32_t modelHeight) :
modelPath_(modelPath), modelWidth_(modelWidth), modelHeight_(modelHeight)
{
}
SampleYOLOV7::~SampleYOLOV7()
{
ReleaseResource();
}
Result SampleYOLOV7::InitResource()
{
// init acl resource
AclLiteError ret = aclResource_.Init();
if (ret == FAILED) {
ACLLITE_LOG_ERROR("resource init failed, errorCode is %d", ret);
return FAILED;
}
ret = aclrtGetRunMode(&runMode_);
if (ret == FAILED) {
ACLLITE_LOG_ERROR("get runMode failed, errorCode is %d", ret);
return FAILED;
}
// init dvpp resource
ret = imageProcess_.Init();
if (ret == FAILED) {
ACLLITE_LOG_ERROR("imageProcess init failed, errorCode is %d", ret);
return FAILED;
}
// load model from file
ret = model_.Init(modelPath_);
if (ret == FAILED) {
ACLLITE_LOG_ERROR("model init failed, errorCode is %d", ret);
return FAILED;
}
return SUCCESS;
}
Result SampleYOLOV7::ProcessInput(string testImgPath)
{
// read image from file
ImageData image;
AclLiteError ret = ReadJpeg(image, testImgPath);
if (ret == FAILED) {
ACLLITE_LOG_ERROR("ReadJpeg failed, errorCode is %d", ret);
return FAILED;
}
// copy image from host to dvpp
ImageData imageDevice;
ret = CopyImageToDevice(imageDevice, image, runMode_, MEMORY_DVPP);
if (ret == FAILED) {
ACLLITE_LOG_ERROR("CopyImageToDevice failed, errorCode is %d", ret);
return FAILED;
}
// image decoded from JPEG format to YUV
ImageData yuvImage;
ret = imageProcess_.JpegD(yuvImage, imageDevice);
if (ret == FAILED) {
ACLLITE_LOG_ERROR("Convert jpeg to yuv failed, errorCode is %d", ret);
return FAILED;
}
// zoom image to modelWidth_ * modelHeight_
ret = imageProcess_.Resize(resizedImage_, yuvImage, modelWidth_, modelHeight_);
if (ret == FAILED) {
ACLLITE_LOG_ERROR("Resize image failed, errorCode is %d", ret);
return FAILED;
}
return SUCCESS;
}
Result SampleYOLOV7::Inference(std::vector<InferenceOutput>& inferOutputs)
{
// create input data set of model
AclLiteError ret = model_.CreateInput(static_cast<void *>(resizedImage_.data.get()), resizedImage_.size);
if (ret == FAILED) {
ACLLITE_LOG_ERROR("CreateInput failed, errorCode is %d", ret);
return FAILED;
}
// inference
ret = model_.Execute(inferOutputs);
if (ret != ACL_SUCCESS) {
ACLLITE_LOG_ERROR("execute model failed, errorCode is %d", ret);
return FAILED;
}
return SUCCESS;
}
Result SampleYOLOV7::GetResult(std::vector<InferenceOutput>& inferOutputs,
string imagePath, size_t imageIndex, bool release)
{
uint32_t outputDataBufId = 0;
float *classBuff = static_cast<float *>(inferOutputs[outputDataBufId].data.get());
// confidence threshold
float confidenceThreshold = 0.25;
// class number
size_t classNum = 80;
// number of (x, y, width, hight, confidence)
size_t offset = 5;
// total number = class number + (x, y, width, hight, confidence)
size_t totalNumber = classNum + offset;
// total number of boxs
size_t modelOutputBoxNum = 25200;
// top 5 indexes correspond (x, y, width, hight, confidence),
// and 5~85 indexes correspond object's confidence
size_t startIndex = 5;
// read source image from file
cv::Mat srcImage = cv::imread(imagePath);
int srcWidth = srcImage.cols;
int srcHeight = srcImage.rows;
// filter boxes by confidence threshold
vector <BoundBox> boxes;
size_t yIndex = 1;
size_t widthIndex = 2;
size_t heightIndex = 3;
size_t classConfidenceIndex = 4;
for (size_t i = 0; i < modelOutputBoxNum; ++i) {
float maxValue = 0;
float maxIndex = 0;
for (size_t j = startIndex; j < totalNumber; ++j) {
float value = classBuff[i * totalNumber + j] * classBuff[i * totalNumber + classConfidenceIndex];
if (value > maxValue) {
// index of class
maxIndex = j - startIndex;
maxValue = value;
}
}
float classConfidence = classBuff[i * totalNumber + classConfidenceIndex];
if (classConfidence >= confidenceThreshold) {
// index of object's confidence
size_t index = i * totalNumber + maxIndex + startIndex;
// finalConfidence = class confidence * object's confidence
float finalConfidence = classConfidence * classBuff[index];
BoundBox box;
box.x = classBuff[i * totalNumber] * srcWidth / modelWidth_;
box.y = classBuff[i * totalNumber + yIndex] * srcHeight / modelHeight_;
box.width = classBuff[i * totalNumber + widthIndex] * srcWidth/modelWidth_;
box.height = classBuff[i * totalNumber + heightIndex] * srcHeight / modelHeight_;
box.score = finalConfidence;
box.classIndex = maxIndex;
box.index = i;
if (maxIndex < classNum) {
boxes.push_back(box);
}
}
}
// filter boxes by NMS
vector <BoundBox> result;
result.clear();
float NMSThreshold = 0.45;
int32_t maxLength = modelWidth_ > modelHeight_ ? modelWidth_ : modelHeight_;
std::sort(boxes.begin(), boxes.end(), sortScore);
BoundBox boxMax;
BoundBox boxCompare;
while (boxes.size() != 0) {
size_t index = 1;
result.push_back(boxes[0]);
while (boxes.size() > index) {
boxMax.score = boxes[0].score;
boxMax.classIndex = boxes[0].classIndex;
boxMax.index = boxes[0].index;
// translate point by maxLength * boxes[0].classIndex to
// avoid bumping into two boxes of different classes
boxMax.x = boxes[0].x + maxLength * boxes[0].classIndex;
boxMax.y = boxes[0].y + maxLength * boxes[0].classIndex;
boxMax.width = boxes[0].width;
boxMax.height = boxes[0].height;
boxCompare.score = boxes[index].score;
boxCompare.classIndex = boxes[index].classIndex;
boxCompare.index = boxes[index].index;
// translate point by maxLength * boxes[0].classIndex to
// avoid bumping into two boxes of different classes
boxCompare.x = boxes[index].x + boxes[index].classIndex * maxLength;
boxCompare.y = boxes[index].y + boxes[index].classIndex * maxLength;
boxCompare.width = boxes[index].width;
boxCompare.height = boxes[index].height;
// the overlapping part of the two boxes
float xLeft = max(boxMax.x, boxCompare.x);
float yTop = max(boxMax.y, boxCompare.y);
float xRight = min(boxMax.x + boxMax.width, boxCompare.x + boxCompare.width);
float yBottom = min(boxMax.y + boxMax.height, boxCompare.y + boxCompare.height);
float width = max(0.0f, xRight - xLeft);
float hight = max(0.0f, yBottom - yTop);
float area = width * hight;
float iou = area / (boxMax.width * boxMax.height + boxCompare.width * boxCompare.height - area);
// filter boxes by NMS threshold
if (iou > NMSThreshold) {
boxes.erase(boxes.begin() + index);
continue;
}
++index;
}
boxes.erase(boxes.begin());
}
// opencv draw label params
const double fountScale = 0.5;
const uint32_t lineSolid = 2;
const uint32_t labelOffset = 11;
const cv::Scalar fountColor(0, 0, 255);
const vector <cv::Scalar> colors{
cv::Scalar(237, 149, 100), cv::Scalar(0, 215, 255),
cv::Scalar(50, 205, 50), cv::Scalar(139, 85, 26)};
int half = 2;
for (size_t i = 0; i < result.size(); ++i) {
cv::Point leftUpPoint, rightBottomPoint;
leftUpPoint.x = result[i].x - result[i].width / half;
leftUpPoint.y = result[i].y - result[i].height / half;
rightBottomPoint.x = result[i].x + result[i].width / half;
rightBottomPoint.y = result[i].y + result[i].height / half;
cv::rectangle(srcImage, leftUpPoint, rightBottomPoint, colors[i % colors.size()], lineSolid);
string className = label[result[i].classIndex];
string markString = to_string(result[i].score) + ":" + className;
cv::putText(srcImage, markString, cv::Point(leftUpPoint.x, leftUpPoint.y + labelOffset),
cv::FONT_HERSHEY_COMPLEX, fountScale, fountColor);
}
string savePath = "out_" + to_string(imageIndex) + ".jpg";
cv::imwrite(savePath, srcImage);
if (release){
free(classBuff);
classBuff = nullptr;
}
return SUCCESS;
}
void SampleYOLOV7::ReleaseResource()
{
model_.DestroyResource();
imageProcess_.DestroyResource();
aclResource_.Release();
}
int main()
{
const char* modelPath = "../model/yolov7x.om";
const string imagePath = "../data";
const int32_t modelWidth = 640;
const int32_t modelHeight = 640;
// all images in dir
DIR *dir = opendir(imagePath.c_str());
if (dir == nullptr)
{
ACLLITE_LOG_ERROR("file folder does no exist, please create folder %s", imagePath.c_str());
return FAILED;
}
vector<string> allPath;
struct dirent *entry;
while ((entry = readdir(dir)) != nullptr)
{
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0
|| strcmp(entry->d_name, ".keep") == 0)
{
continue;
}else{
string name = entry->d_name;
string imgDir = imagePath +"/"+ name;
allPath.push_back(imgDir);
}
}
closedir(dir);
if (allPath.size() == 0){
ACLLITE_LOG_ERROR("the directory is empty, please download image to %s", imagePath.c_str());
return FAILED;
}
// inference
string fileName;
bool release = false;
SampleYOLOV7 sampleYOLO(modelPath, modelWidth, modelHeight);
Result ret = sampleYOLO.InitResource();
if (ret == FAILED) {
ACLLITE_LOG_ERROR("InitResource failed, errorCode is %d", ret);
return FAILED;
}
for (size_t i = 0; i < allPath.size(); i++)
{
if (allPath.size() == i){
release = true;
}
std::vector<InferenceOutput> inferOutputs;
fileName = allPath.at(i).c_str();
ret = sampleYOLO.ProcessInput(fileName);
if (ret == FAILED) {
ACLLITE_LOG_ERROR("ProcessInput image failed, errorCode is %d", ret);
return FAILED;
}
ret = sampleYOLO.Inference(inferOutputs);
if (ret == FAILED) {
ACLLITE_LOG_ERROR("Inference failed, errorCode is %d", ret);
return FAILED;
}
ret = sampleYOLO.GetResult(inferOutputs, fileName, i, release);
if (ret == FAILED) {
ACLLITE_LOG_ERROR("GetResult failed, errorCode is %d", ret);
return FAILED;
}
}
return SUCCESS;
}
这里我还加上了车牌识别的功能
2.3 Orange Pi AI Pro目标追踪与定位
目标追踪的思路如下:
将检测框按置信度分成两类,高分框与低分框。先用高分框与追踪轨迹进行第一次匹配。第二次匹配使用低分框与未匹配的轨迹进行匹配对于得分高于阈值但未匹配的目标框,为其新建追踪轨迹.追踪代码完全基于Bytetrack且是跑在CPU上,大家直接去官方整合即可,这里介绍下定位
在用OpenCV中的函数,通过4对对应的点的坐标计算两个图像之间单应矩阵H,然后调用射影变换函数,将一幅图像变换到另一幅图像的视角中。当时只是知道通过单应矩阵,能够将图像1中的像素坐标(u1,v1))变换到图像2中对应的位置上(u2,v2)。
单应(Homography)是射影几何中的概念,又称为射影变换。它把一个射影平面上的点(三维齐次矢量)映射到另一个射影平面上,并且把直线映射为直线,具有保线性质。总的来说,单应是关于三维齐次矢量的一种线性变换,可以用一个3x3的非奇异矩阵H表示这是一个齐次坐标的等式,
x1=H * x2
在这里H是一个3×3的齐次矩阵,具有8个未知量。假设已经取得了两图像之间的单应,则可单应矩阵H可以将两幅图像关联起来。这有了很多实际的应用,例如图像的校正、对齐以及在SLAM中估计两个相机间的运动。此处我用作做为联合标定。首先我们将是地面平面假设为一个二维水平面,忽略其坡度。
即可得
地面平面上点p1(x1,y1),与图像上点p2(x2,y2)是一对匹配得点对,其单应性矩阵为H,则有
接下来我们求解这个矩阵
在真实的应用场景中,我们计算的点对中都会包含噪声。比如点的位置偏差几个像素,甚至出现特征点对误匹配的现象,如果只使用4个点对来计算单应矩阵,那会出现很大的误差。因此,为了使得计算更精确,一般都会使用远大于4个点对来计算单应矩阵。另外上述方程组采用直接线性解法通常很难得到最优解,所以实际使用中一般会用其他优化方法,如奇异值分解、Levenberg-Marquarat(LM)算法等进行求解。
cv::Mat oHomoMat_inv= cv::findHomography(m_vo2dPt, m_vo3dPt, 0, 0);
本工程里使用了四种方法,分别为直接解方程组,最小二乘法,ransac方法,基于PROSAC的鲁棒算法,具体解法可自行百度。标定之后的结果以及换算结果如图
3、多相机追踪
(a)融合单个杆件上相机与枪机的检测结果
首先需要将单个相机单元的结果进行初步筛选整合,虽然鱼眼的作用是对枪机进行补盲,但不能直接对两者的结果进行简单相加,因为可能会有由于处于鱼眼和枪机的重叠区域而被重复检测的目标,因此需要剔除可能存在的重复检测结果:
选择遍历枪机的检测结果,按照GPS的距离的每个目标的进行比,找出可能重复识别的目标, 之后将这些初步选出的枪机检测结果的GPS坐标使用相机的单应性矩阵映射到图像坐标系上,此时枪机的GPS坐标变为鱼眼图像上的一个像素点,再与检测框对比计算IOU,经过该轮比对,如IOU超过阈值,能够关联成功即可视为一个重复检测的目标,在这里删除重复检测在枪机下的结果,再将去除后的结果进行合并,即为一个相机单元的检测结果,包括每个目标的类型,在图像坐标的位置(可能是枪机或鱼眼),在GPS坐标的位置,速度,航向角等信息。
(b) 目标三级关联算法
在本步骤中,为了保证重合区域能够关联匹配,首次提出目标三级关联算法完成多杆件目标去重,以及目标跨杆件仍能被追踪。具体实施如下:根据步骤1-5得到各路相机单元的结构化数据,目标ID,目标速度,目标位置、目标航向角、目标车牌,目标时间戳等。
(1)首先分别接收多路相机单元数据,存在独立相机单元的缓冲区,单个相机单元的数据由1-5步骤生成,得到单个目标重要属性attrobj
其中id表示为目标单杆件唯一ID,lat, lon为目标的经纬度,heading为目标与正北方向夹角,len, width, height为目标的长宽高,timestamp为目标出现的时间戳,x,y表示在该相机下的像素坐标。同时该杆件相机数据由多个目标组成,且有个 发送时间戳TimeStamp与杆件名称deviceID,单个杆件数据组成为
(2)多相机追踪根据单杆件相机发送的数据perceptroni 中的目标时间戳timestamp的先后顺序初始化多杆件追踪目标列表tracker,即按照timestamp的顺序对perceptron集合进行排序,将最早出现的perceptron所有目标作为路口多杆件全息目标的集合,记为tracker,且赋予该目标一个全局ID。
全局ID是用来表示该目标在整个路口的唯一性,attri表示该目标的位置相关属性,deviceID则表示该目标由某一个杆件摄像机创建而来。
(3) a) 将接收到的其他杆件相机数据依次按照deviceID,从整个路口目标列表里查询当前目标ID上一帧由哪一个单相机列表维护,若查询到则依然由那个相机进行属性维护,即若新收到的perceptron数据中deviceID相同,则直接对比attr中的id是否相同,若相同则直接更新目标的位置,时间戳等信息; 使用单个相机ID预关联的方案,可以大大降低计算量,节省资源 b)若不同,则将该相机下percptroni的该目标的经纬度(lat,long)通过H矩阵的逆重投影到当前tracker维护的列表目标所在相机的图像坐标系中得到pixel(x,y)进行一一计算1-IOU。
c)关联的相机的图像坐标进行Dioumn = 1-IOU交并比关联,即将perceptroni中的目标属性atti(lat,lon)通过计算转换为单相机下的图像坐标x,y进行计算交并比,其中转换后的图像坐标宽高使用tracker关联目标的宽高。 d)若步骤仍关联不上, 则构建一个代价矩阵cost_matrix,使用距离关联,首先构建一个M x N的全为0的矩阵,其中M为tracker中目标的个数,N为上一步iou未关联上的单杆件perceptron目标个数。
距离计算方法为:
。
其中R为地球半径,(lat1,lon1) 为tracker目标经纬度, (lat2,lon2) 为未关联的单杆件目标经纬度。接下里根据距离构建代价矩阵cos_matrix
使用匈牙利匹配算法对关联矩阵进行匹配,完成所有目标的关联。
(4)将所有关联上的相机属性进行更新整个路口的目标追踪列表,即使用perceptron中的目标属性更新tracker目标中的属性,从而得到整个路口的目标数据tracker,为了进行下一帧关联准确度更高,保证空间一致性,这里对所有tracker中的经纬度根据当前速度进行预测,使用匀速直线运动模型对所有目标经纬度进行预测。
目标纬度预测:
目标经度预测:
其中arc为地球的平均半径,v为当前tracker目标速度,T为关联时间戳之差。
(4)整合路口目标列表,由于目标各个相机中目标视野不一致,所得到的位置有区别,考虑到相机和目标距离越远,产生误差的可能性越大,采用均值滤波对tracker中的目标位置数据进行处理不够稳健,由于距离目标过远的相机产生的误差会造成平均值的位置产生偏差,因此我们采取加权平均,首先获取到路口目标列表内每个目标的距离每个杆件单元的距离,直接根据目标经纬度以及杆件上的摄像机安装经纬度便可得到,假设目标距离四个杆件的距离分别为,则在加权平均时的权重分别为对距离进行归一化的结果,即
进而求出结合了四个杆件信息获得的最终坐标,形成完整路侧感知数据。
由于篇幅有限,后续我再将展开续更。。。。。。。。。。。。