从开始做毕设到现在已经有3个多月了,课题主要包括以下几个方面:动态环境中动态目标检测、动态目标跟踪、MFC设计、六足机器人设计等。这篇主要是简单介绍一下检测算法,具体源码在上一篇博客有,感兴趣的可以看一看。
检测算法使用传统的背景补偿,即通过计算相邻两帧图像的仿射变换矩阵,把前一帧映射到当前帧,再帧差就可以得出运动目标。
1、特征提取。相邻两帧提取特征点,使用SURF算法,主要还是稳准快。
2、特征匹配。将两帧提取的特征点匹配,主要有以下3步。
(1)、KNN粗略匹配,opencv有函数实现
(2)、最近邻匹配,对于前一帧里每一个点,在当前帧里找到匹配度最高和次高的两个点的汉明距离,记做d1.d2,d1/d2 < 阈值,则选匹配度最高的点为匹配点;否则去掉这一对匹配点
(3)、对称约束。先正向匹配,再反向匹配,若正反匹配的点对应,认为是匹配较好的点。
匹配到此结束
3、外点滤除。目的是去掉待检测目标上的点,提高仿射矩阵的精准度。内容太多,我就直接上图了。
4、背景补偿。通过上一步提取的背景点(内点)计算前一帧到当前帧的仿射矩阵H
5、前景提取。
(1)、由仿射矩阵H把前一帧映射到当前帧上,帧差(阈值大约40-60)
(2)、形态学操作、滤波等等
(3)、重点来了
离散区域归并。这一步比较重要是因为,前面帧差的结果经常是分散的,比如前景是个走动的人,经过上一步只会是一个人的分散的轮廓提取出来了,再绘制轮廓的话,一个人就被分成了好几块。(懒得配图了)方法如图:
图中说的可能不太详细,具体可以参考我的代码。
//目标离散区域归并
/*
img_rgb: RGB图
img_abs: 提取目标的二值化图
*/
void Func::combinTarget(Mat &img_rgb, Mat &img_abs)
{
Mat img_rgb1, img_abs1;
img_rgb.copyTo(img_rgb1);
img_abs.copyTo(img_abs1); //保存区域连接后的二值化图
cvtColor(img_rgb1, img_rgb1, CV_BGR2HSV); //RGB-》hsv
vector<vector<Point>> contours;
findContours(img_abs, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point(0, 0));
if (contours.size() > 1) {
//contour_center[0].clear();
contour_center[1].clear(); //初始化contour_center
//hsiMsg[0].clear();
hsiMsg[1].clear();
for (size_t i = 0; i < contours.size(); i++) //初始化hsiMsg
{
vector<double> msg(6);
msg[0] = 0.0; //H 均值
msg[0] = 0.0; //S 均值
msg[0] = 0.0; //I 均值
msg[0] = 0.0; //H 方差
msg[0] = 0.0; //S 方差
msg[0] = 0.0; //I 方差
hsiMsg[1].push_back(msg);
}
Rect rect; //缓存每个区域的外接矩形
//遍历每个轮廓
for (size_t i = 0; i < contours.size(); i++)
{
//获取轮廓质心
Moments mu = moments(contours[i]);
contour_center[1].push_back(Point2f(mu.m10 / mu.m00, mu.m01 / mu.m00));
//printf("质心%d: (%.1f, %.1f) \n", i, contour_center[i].x, contour_center[i].y);
//获取轮廓内部像素点个数
int pixelNum = 0; //缓存轮廓内部像素点个数
//获取轮廓内部颜色信息
Mat mat = Mat::zeros(img_rgb.size(), CV_8UC1);
cv::drawContours(mat, contours, i, Scalar::all(255), -1);
rect = boundingRect(contours[i]); //获取区域外接矩形
vector<double> hsi(3); // HSI
//在区域矩形内遍历目标点
//计算区域内HSI的均值
for (int row = rect.y; row < (rect.y+rect.height); row++)
{
const uchar* imgRow = img_rgb1.ptr<uchar>(row);
for (int col = rect.x; col < (rect.x+rect.width); col++)
{
if ((int)mat.at<uchar>(row, col) == 255) {
//获取HSV分量
hsi[0] = img_rgb1.at<Vec3b>(row, col)[0] * 2;
hsi[1] = img_rgb1.at<Vec3b>(row, col)[1] / 255;
hsi[2] = img_rgb1.at<Vec3b>(row, col)[2] / 255;
//计算区域内HSI的均值
hsiMsg[1][i][0] += hsi[0]; //H均值
hsiMsg[1][i][1] += hsi[1]; //S均值
hsiMsg[1][i][2] += hsi[2]; //I均值
pixelNum++; //记录轮廓内像素点个数
}
}
}
hsiMsg[1][i][0] /= pixelNum; //H均值
hsiMsg[1][i][1] /= pixelNum; //S均值
hsiMsg[1][i][2] /= pixelNum; //I均值
//计算区域内HSI的方差
for (size_t row = rect.y; row < (rect.y + rect.height); row++)
{
for (size_t col = rect.x; col < (rect.x + rect.width); col++)
{
if (mat.at<uchar>(row, col) == 255) {
//获取HSV分量
hsi[0] = img_rgb1.at<Vec3b>(row, col)[0] * 2;
hsi[1] = img_rgb1.at<Vec3b>(row, col)[1] / 255;
hsi[2] = img_rgb1.at<Vec3b>(row, col)[2] / 255;
//计算区域内HSI的均值
hsiMsg[1][i][3] += pow(hsi[0] - hsiMsg[1][i][0], 2) / pixelNum; //H方差
hsiMsg[1][i][4] += pow(hsi[1] - hsiMsg[1][i][1], 2) / pixelNum; //S方差
hsiMsg[1][i][5] += pow(hsi[2] - hsiMsg[1][i][2], 2) / pixelNum; //I方差
}
}
}
printf("轮廓%d: HSI均值:%.2f, %.2f, %.2f HSI方差:%.2f, %.2f, %.2f\n",
i, hsiMsg[1][i][0], hsiMsg[1][i][1], hsiMsg[1][i][2], hsiMsg[1][i][3], hsiMsg[1][i][4], hsiMsg[1][i][5]);
}
/*********** 到这里区域内的HSI颜色信息已统计完毕 **********/
//根据颜色信息归并
for (size_t i = 0; i < contours.size()-1; i++)
{
for (size_t j = i+1; j < contours.size(); j++)
{
//1.质心相距50以内视为临近
if (pts2fDist(contour_center[1][i], contour_center[1][j]) < 50.0) {
//比较颜色特征距离
double d = colorDist(hsiMsg[1][i], hsiMsg[1][j]); //计算颜色特征距离
printf("颜色特征距离:%.2f\n", d);
//putText(img_rgb, to_string(int(d)), Point((int)((contour_center[1][i].x + contour_center[1][j].x) / 2), ((int)(contour_center[1][i].y + contour_center[1][j].y) / 2)),
//FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0, 0, 255), 1, 8);
//2.颜色特征相差3000以内视为同一目标
if (d < 3000) {
line(img_abs1, Point2f(contour_center[1][i].x, contour_center[1][i].y), //连接归并区域
Point2f(contour_center[1][j].x, contour_center[1][j].y), Scalar::all(255), 3, 8);
printf("画线");
}
}
}
}
/******************* 根据位置和目标位移信息归并 *****************/
if (contour_center[0].empty()) {
contour_center[0].assign(contour_center[1].begin(), contour_center[1].end());
}
if (hsiMsg[0].empty()) {
hsiMsg[0].assign(hsiMsg[1].begin(), hsiMsg[1].end());
}
//下面是归并方法
//1.提取当前帧与前一帧位置接近的区域对
//2.计算区域对的颜色特征距离确定前后帧对应目标区域
vector<vector<double>> targetsMovMsg; //缓存相邻两帧对应目标区域的移动方向、距离和当前帧质心(x y)
vector<double> targetMovMsg(4); //缓存相邻两帧某一目标区域的移动方向、距离和当前帧质心(x y)
for (size_t i = 0; i < contour_center[1].size(); i++)
{
for (size_t j = 0; j < contour_center[0].size(); j++)
{
//1.距离小于20认为临近
if (pts2fDist(contour_center[1][i], contour_center[0][j]) < 30.0) {
//2.颜色特征距离小于10000认为是同一目标
if (colorDist(hsiMsg[1][i], hsiMsg[0][j]) < 8000.0) {
//3.计算目标移动方向、距离和当前帧质心
targetMovMsg[0] = tgtMovDirec(contour_center[0][j], contour_center[1][i]);//移动方向
targetMovMsg[1] = pts2fDist(contour_center[1][i], contour_center[0][j]); //移动距离
targetMovMsg[2] = (double)contour_center[1][i].x; //当前帧位置 X
targetMovMsg[3] = (double)contour_center[1][i].y; //当前帧位置 Y
targetsMovMsg.push_back(targetMovMsg); //记录
//将移动轨迹画在图上
line(img_rgb, Point(int(contour_center[0][j].x), int(contour_center[0][j].y)),
Point(int(contour_center[1][i].x), int(contour_center[1][i].y)), Scalar(0, 0, 255), 2, 8);
printf("由(%d, %d)->(%d, %d)移动了%.2f \n", int(contour_center[0][j].x), int(contour_center[0][j].y),
int(contour_center[1][i].x), int(contour_center[1][i].y), targetMovMsg[1]);
}
}
}
}
//比较相邻两帧目标区域的位置和移动信息
if (targetsMovMsg.size() > 1) {
for (size_t i = 0; i < targetsMovMsg.size() - 1; i++)
{
for (size_t j = i +1; j < targetsMovMsg.size(); j++)
{
//1.区域质心在当前帧相距小于50
Point2f p1 = Point2f((float)targetsMovMsg[i][2], (float)targetsMovMsg[i][3]);
Point2f p2 = Point2f((float)targetsMovMsg[j][2], (float)targetsMovMsg[j][3]);
printf("区域质心在当前帧距离:%.2f\n", pts2fDist(p1, p2));
if (pts2fDist(p1, p2) < 55.0) {
//2.评价不同物体的移动一致性
double movOffset = getmovOffset(targetsMovMsg[i], targetsMovMsg[j]);
printf("移动一致性: %.2f \n", movOffset);
if (movOffset <= 30) {
//移动一致性小于20,认为是同一目标
line(img_abs1, Point(int(targetsMovMsg[i][2]), int(targetsMovMsg[i][3])), //连接归并区域
Point(int(targetsMovMsg[j][2]), int(targetsMovMsg[j][3])), Scalar::all(255), 3, 8);
}
}
}
}
}
//更新前一帧区域位置和颜色信息
contour_center[0].assign(contour_center[1].begin(), contour_center[1].end()); //更新位置信息
hsiMsg[0].assign(hsiMsg[1].begin(), hsiMsg[1].end()); //更新HSI颜色信息
}
cv::imshow("目标归并", img_abs1);
cv::imshow("区域移动轨迹", img_rgb);
if(prev_con.empty())
img_abs.copyTo(prev_con);
cv::imshow("前一帧二值图", prev_con);
img_abs.copyTo(prev_con);
img_abs1.copyTo(img_abs);
}
6、绘制轮廓(目标识别)
7、迭代循环。到此算法结束
上个图吧(背景是不断变化的,我后面看看能不能传视频上来。红线是标记了区域归并前各分散区域的移动轨迹)。
平台是VS2017+opencv3.3.0