一、简介
本文章的起源是本人在做一个项目,用摄像头识别笔,根据笔的运动,绘制出其轨迹。主要应用到的方法,有运动物体识别、运动物体检测,以及绘制运动物体的运动轨迹。
1、 运动物体的识别方法很多,主要就是要提取相关物体的特征,主要分为:
(1)各种色彩空间直方图,利用色彩空间的直方图分布作为目标跟踪的特征的一个显著性特点是可以减少物体远近距离对跟踪的影响,因为其颜色分布大致相同。
(2)轮廓特征,提取目标的轮廓特征不但可以加快算法的速度还可以在目标有小部分影响的情况下同样有效果。
(3)纹理特征,如果被跟踪目标是有纹理的,根据其纹理特征来跟踪,效果会有所改善。
详细教程可参考:
颜色识别:https://blog.csdn.net/clp786080772/article/details/51913158
轮廓特征:https://blog.csdn.net/qq_20823641/article/details/52143637
纹理特征:https://blog.csdn.net/h532600610/article/details/52957459?locationNum=2&fps=1
2、运动物体的跟踪涉及到的算法也比较多,其主要分类为:
(1)质心跟踪算法(Centroid):这种跟踪方式用于跟踪有界目标如飞机,目标完全包含在摄像机的视场范围内,对于这种跟踪方式可选用一些预处理算法:如白热(正对比度)增强、黑热(负对比度)增强,和基于直方图的统计(双极性)增强。
(2)多目标跟踪算法(MTT):多目标跟踪用于有界目标如飞机、地面汽车等。它们完全在跟踪窗口内。在复杂环境里的小目标跟踪MMT能给出一个较好的性能。
(3)相关跟踪算法(Correlation):相关可用来跟踪多种类型的目标,当跟踪目标无边界且动态不是很强时这种方式非常有效。典型应用于:目标在近距离的范围,且目标扩展到摄像机视场范围外,如一艘船。
(4)边缘跟踪算法(Edge):当跟踪目标有一个或多个确定的边缘而同时却又具有不确定的边缘,这时边缘跟踪是最有效的算法。典型地火箭发射,它有确定好的前边缘,但尾边缘由于喷气而不定。
(5)相位相关跟踪算法(Phase Correlation):相位相关算法是非常通用的算法,既可以用来跟踪无界目标也可以用来跟踪有界目标。在复杂环境下(如地面的汽车)能给出一个好的效果。
(6)场景锁定算法(SceneLock):该算法专门用于复杂场景的跟踪。适合于空对地和地对地场景。这个算法跟踪场景中的多个目标,然后依据每个点的运动,从而估计整个场景全局运动,场景中的目标和定位是自动选择的。当存在跟踪点移动到摄像机视场外时,新的跟踪点能自动被标识。瞄准点初始化到场景中的某个点,跟踪启动,同时定位瞄准线。在这种模式下,能连续跟踪和报告场景里的目标的位置。
(7)组合(Combined)跟踪算法:顾名思义这种跟踪方式是两种具有互补特性的跟踪算法的组合:相关类算法 +质心类算法。它适合于目标尺寸、表面、特征改变很大的场景(如小船在波涛汹涌的大海里行驶)。
3、绘制物体的曲线运动轨迹
(1)在应用opencv绘制运动轨迹之前,先对opencv绘制点、线、圆、矩形有一定的了解,具体可参考:
https://blog.csdn.net/qq_20823641/article/details/51991155
(2)绘制物体的曲线运动轨迹主要用到的方法是贝塞尔曲线。贝塞尔曲线,是应用于二维图像应用程序的数学曲线,可以通过它来精确画出曲线。对贝塞尔曲线进行进一步的了解,可以参考:
http://xuhehuan.com/2608.html
4、具体代码:
#include
#include
#include
#include
using namespace cv;
using namespace std;
vector points;
Point center;
//绘制贝塞尔曲线
Point pointAdd(Point p, Point q) {
p.x += q.x; p.y += q.y;
return p;
}
Point pointTimes(float c, Point p) {
p.x *= c; p.y *= c;
return p;
}
Point Bernstein(float u, Point qi,Point mid,Point mo)
{
Point a, b, c, r;
a = pointTimes(pow(u, 2), mo);
b = pointTimes(pow((1 - u), 2), qi);
c = pointTimes(2 * u*(1 - u), mid);
r = pointAdd(pointAdd(a, b), c);
return r;
}
int main(int argc, char** argv)
{
VideoCapture cap(1);//读取USB摄像头
if (!cap.isOpened())
return -1;
int iLowH = 0;
int iHighH = 5;
int iLowS = 45;
int iHighS = 255;
int iLowV = 45;
int iHighV = 255;
int nGaussianBlurValue = 3;
//采取颜色识别方法,利用滑条选色,参考HSV对应的颜色,获取目标物体
namedWindow("Control");
cvCreateTrackbar("LowH", "Control", &iLowH, 179); //Hue (0 - 179)
cvCreateTrackbar("HighH", "Control", &iHighH, 179);
cvCreateTrackbar("LowS", "Control", &iLowS, 255); //Saturation (0 - 255)
cvCreateTrackbar("HighS", "Control", &iHighS, 255);
cvCreateTrackbar("LowV", "Control", &iLowV, 255); //Value (0 - 255)
cvCreateTrackbar("HighV", "Control", &iHighV, 255);
while (true)
{
Mat imgOriginal;
cap >> imgOriginal;
//高斯滤波
GaussianBlur(imgOriginal, imgOriginal, Size(nGaussianBlurValue*+1, nGaussianBlurValue * 2 + 1), 0, 0);
Mat imgHSV;
vector hsvSplit;
cvtColor(imgOriginal, imgHSV, COLOR_BGR2HSV); //转换颜色空间
Mat element1 = getStructuringElement(MORPH_RECT, Size(5, 5));//获取结构元素
morphologyEx(imgHSV, imgHSV, MORPH_OPEN, element1);//开操作
morphologyEx(imgHSV, imgHSV, MORPH_CLOSE, element1);//闭操作
split(imgHSV, hsvSplit);//HSV图像分离
equalizeHist(hsvSplit[2], hsvSplit[2]);//直方图均衡化
merge(hsvSplit, imgHSV);//HSV图像聚合
Mat imgThresholded;
//根据颜色选取目标物体
inRange(imgHSV, Scalar(iLowH, iLowS, iLowV), Scalar(iHighH, iHighS, iHighV), imgThresholded);
Mat element = getStructuringElement(MORPH_RECT, Size(5, 5));//获取结构元素
morphologyEx(imgThresholded, imgThresholded, MORPH_OPEN, element);//开操作
morphologyEx(imgThresholded, imgThresholded, MORPH_CLOSE, element);//闭操作
morphologyEx(imgThresholded, imgThresholded, MORPH_ELLIPSE, element);//膨胀操作
vector> contours;
vector hierarcy;
findContours(imgThresholded, contours, hierarcy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);//查找轮廓
//drawContours(imgOriginal, contours, -1, Scalar(0, 255, 0), 2);//绘制轮廓
vector box(contours.size());
for (int i = 0; i < contours.size(); i++)
{
box[i] = fitEllipse(Mat(contours[i]));
center = box[i].center;
points.push_back(center);
//circle(imgOriginal, center, 3, Scalar(0, 255, 0));//绘制目标物体质点
//ellipse(imgOriginal, box[i], Scalar(0, 255, 0));//绘制拟合椭圆
for (int j = 2; j < points.size(); j += 2)
{
Point pre, last, mid;
pre = points[j - 2];
mid = points[j - 1];
last = points[j];
Point pt_pre = points[j-2];
Point pt_now;
//绘制贝塞尔曲线,一小段一小段的直线就能组合成曲线
for (int k = 0; k <= 10; k++)
{
float u = (float)k / 10;
Point new_point = Bernstein(u, pre, mid, last);
pt_now.x = (int)new_point.x;
pt_now.y = (int)new_point.y;
line(imgOriginal, pt_pre, pt_now, Scalar(0, 255, 0), 2, CV_AA, 0);//绘制直线
pt_pre = pt_now;
}
}
}
imshow("Thresholded Image", imgThresholded); //显示处理图像
imshow("Original", imgOriginal); //显示最终图像
char key = (char)waitKey(300);
if (key == 27)
break;
}
return 0;
}
}