OpenCV學習筆記:運動物體檢測、跟蹤和繪製曲線運動軌跡
一、簡介
本文章的起源是本人在做一個專案,用攝像頭識別筆,根據筆的運動,繪製出其軌跡。主要應用到的方法,有運動物體識別、運動物體檢測,以及繪製運動物體的運動軌跡。
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 <iostream>
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<opencv2\core.hpp>
using namespace cv;
using namespace std;
vector<Point> 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<Mat> 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<vector<Point>> contours;
vector<Vec4i> hierarcy;
findContours(imgThresholded, contours, hierarcy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);//查詢輪廓
//drawContours(imgOriginal, contours, -1, Scalar(0, 255, 0), 2);//繪製輪廓
vector<RotatedRect> 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;
}
}