/* ***********************************************************************************************************************
任務目標:
基於背景提取的目標跟蹤算法實踐及代碼分析。
*********************************************************************************************************************** */
include "opencv2/opencv.hpp"
using namespace cv;
using namespace std;
int labelTargets(Mat &src, Mat &mask, int thresh = 100);
// 在自定義函數模塊聲明中對thresh的值進行了賦值
int main()
{
char fn = "D:\CommonSoftware\OpenCV\Workplace\Example2_track\vtest.avi";
// 指針fn指向視頻地址,此處使用的是OpenCV庫中自帶的視頻
VideoCapture cap; // 定義VideoCapture類用以打開指定視頻
Mat source, image, foreGround, backGround, fgMask;
// Mat類:原始視頻、縮放后視頻、前景、背景、掩膜
Ptr
createBackgroundSubtractorMOG2().dynamicCast
/* ********************************************************************************************************************
1. 此處采用了模板類的概念,變量"pBgModel"是一個指向"BackgroundSubtractor"對象的指針。
這里的Mat.Ptr(i)[j]並未指定相應"i"和"j"的值,所以默認值都為0,即指向Mat第一行的第一個元素。
2. OpenCV中不止有一種背景提取方法,但是所有的背景提取方法都歸結為同一個基類"BackgroundSubtractor",
具體所采用的方法為"creatBackgroundSubtractorMOG2"。
3. "creatBackgroundSubtractorMOG2()"對"pBgModel"指針進行初始化,將其初始化成混合高斯模型。(注
意:在OpenCV 3.0以后的版本中不再含有"BackgroundSubtractorMOG"模型。)
4. 利用C++中的"dynamicCast"函數將pBgModel指針所指的內容動態的轉換成"BackgroundSubtractor"型,
並檢測是否成功。這里"BackgroundSubtractor"是一個類的引用,所以"()"中也需要放入一個類的引用,否則
拋出一個異常。
******************************************************************************************************************** */
cap.open(fn);
/* ********************************************************************************************************************
1. 使用cap類的open函數打開指針*fn所指的視頻
2. cap.open()中()內也可以放入數字,而放入數字代表的是接入幾號攝像機的視頻,該攝像機必須支持windows
的VFW。而且攝像機的編號是從0開始的。
******************************************************************************************************************** */
if (!cap.isOpened()) // cap類的isOpened函數查看是否成功打開
cout << "Cannot open the file: " << fn << endl;
for (; ; ) // 強制循環
{
cap >> source; // 將cap提取到的當前幀傳給source(原始圖像)
if (source.empty()) // 如果原始圖像為空,即視頻已結束
{
break; // 直接跳出循環
}
resize(source, image, Size(source.cols / 2, source.rows / 2), INTER_LINEAR);
// 如果原始圖像不為空,調用resize函數改變原始圖像尺寸,方式為"線性差值法"
if (foreGround.empty())
{ // 通過前景是否為空,判斷當前幀是否為視頻開始
foreGround.create(image.size(), image.type());
} // 如果是視頻開始,則為"前景圖像"創建矩陣空間,此處是按照原始圖像的縮放圖像的尺寸和類型進行初始化
pBgModel->apply(image, fgMask);
/* *********************************************************************************************************************
在這里pBgModel這個指針在定義時已經設定好了處理模式,而"apply"函數會實時的將"pBgModel"中的當前幀
縮放圖像"image"采用pBgModel的方式進行處理,並提取fgMask,即前景的掩膜。
********************************************************************************************************************* */
GaussianBlur(fgMask, fgMask, Size(5, 5), 0); // 利用高斯濾波器對前景的掩膜做平滑降噪
threshold(fgMask, fgMask, 30, 255, THRESH_BINARY); // 通過閾值化去除掩膜中灰度小於30的像素
foreGround = Scalar::all(0); // 利用"Scalar"函數將所創建的前景矩陣空間中所有元素賦初值0
image.copyTo(foreGround, fgMask); // 標記運動目標
/* **********************************************************************************************************************
調用Mat類的copyTo()函數,僅將原始圖像的縮放圖像(image)中的掩膜部分(fgMask)復制到前景圖像中。
********************************************************************************************************************** */
int nTargets = labelTargets(image, fgMask);
cout << "There are " << nTargets << " targets." << endl;
pBgModel->getBackgroundImage(backGround);
/* ***********************************************************************************************************************
這里使用了getBackgroundImage()函數,其會從pBgModel設定的模型中提取出背景。
*********************************************************************************************************************** */
imshow("Sized Image", image); // 顯示原始圖像的縮放圖像。
imshow("Background", backGround); // 顯示背景圖像。
imshow("Foreground", foreGround); // 顯示前景圖像。
imshow("Foreground Mask", fgMask); // 顯示前景圖像的掩膜。
char key = waitKey(100); // 每一幀等待100毫秒
if (key == 27) // 如果想中途終止程序,按"Esc"退出
{
break;
}
}
waitKey(0); // 程序執行結束后防止閃退
}
int labelTargets(Mat &src, Mat &mask, int thresh) // 自定義的目標標記函數
{
Mat seg = mask.clone(); // 將掩膜復制到seg當中
vector<vector
findContours(seg, cnts, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
// 調用findContours()函數對掩膜復制圖像seg中的Blob進行檢測,檢測方式是忽略內部邊緣僅識別外部邊緣
// 以下進行篩選
float area; // 定義浮點型area變量用以存放單一Blob外圍邊緣所圍面積
Rect rect;
int count = 0; // 定義整型變量用以計數
string strCount; // 定義字符串型變量
for (int i = cnts.size() - 1; i >= 0; i--)
{ // 調用vector的size()函數以檢測cnts第一維的個數,即Blob個體數,並對其進行循環處理
vector<Point> c = cnts[i]; // 定義一維點類型變量c用以存放Blob個體,即一維cnts集合
area = contourArea(c); // 調用contourArea()函數計算該單一Blob外圍邊緣所圍面積
if (area < thresh) // 濾除面積小於10的分割結果:可能是噪聲
{
continue;
}
count++; // 統計米粒數量
cout << "blob " << i << " : " << area << endl;
rect = boundingRect(c); // 創建包圍矩形數據
// 在原始圖像上畫出包圍矩形,並給每個矩形標號
rectangle(src, rect, Scalar(0, 0, 0xff), 1);
// 調用rectangle()函數在src圖像中,把rect包圍矩形用紅色,1個像素粗細的線框包圍起來
stringstream ss; // 定義字符流變量"ss",作用是將指定字符串生成輸入或輸出流
ss << count; // 將計數數據傳遞給字符流
ss >> strCount; // 將字符流中的數據傳送給字符串變量
putText(src, strCount, Point(rect.x, rect.y), CV_FONT_HERSHEY_PLAIN, 0.5, Scalar(0, 0xff, 0));
// 調用putText函數,在src圖像上將strCount字符串輸出在矩形框左上角,並定義了字體和字號和顏色
}
return count;
}
OpenCV中Mat類的指針ptr的使用:
- 定義Mat型的類”image”,1)image.ptr
(0),指向image第1行第1個元素的指針;2)image.ptr (1),指向image的第2行第1個元素的指針;3)image.ptr (0)[1],指向image第1行第2個元素的指針。
- 注意:在使用image.ptr()[]指針的時候,要注意防止索引值溢出(cv::Exception)。
- dynamic.cast的用法:
-
將一個基類對象指針(或引用)cast到繼承類指針,dynamic_cast會根據基類指針是否真正指向繼承類指針來做相應處理。
-
例如:dynamic_cast
(expression);
該運算符把expression轉換成type-id類型的對象。
Type-id 必須是類的指針、類的引用或者void*。
如果 type-id 是類指針類型,那么expression也必須是一個指針,(對指針進行dynamic_cast,失敗返回null,成功返回正常cast后的對象指針)。如果 type-id 是一個引用,那么 expression 也必須是一個引用(對引用進行dynamic_cast,失敗拋出一個異常,成功返回正常cast后的對象引用)。
dynamic_cast主要用於類層次間的上行轉換和下行轉換,還可以用於類之間的交叉轉換。
轉自 neilkuang