1. 程式人生 > >凌風探梅的專欄

凌風探梅的專欄


參考資料:

  • 《OpenCV 2 Computer Vision Application Programming Cookbook》
  • 《The OpenCV Reference Manual》

讀取視訊

12345678910111213141516171819202122232425262728293031323334353637
#include <stdio.h>#include <string>#include <opencv2/imgproc/imgproc.hpp>#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>int main(){ // Open the video file cv::VideoCapture capture("../stomp.avi"); // check if video successfully opened if (!capture.isOpened()) return 1; // Get the frame rate double rate = capture.get(CV_CAP_PROP_FPS); bool stop(false); cv::Mat frame; // current video frame
cv::namedWindow("Extracted Frame"); // Delay between each frame in ms // corresponds to video frame rate int delay = 1000 / rate; // for all frames in video while (!stop){ // read next frame if any if (!capture.read(frame)) break; cv::imshow("Extracted Frame", frame); // introduce a delay
// or press key to stop if (cv::waitKey(delay) >= 0) stop = true; } // Close the video file // Not required since called by destructor capture.release();}

也可以通過類似的方法讀入攝像頭捕捉的視訊,要改動的地方僅僅是將上面的視訊檔名改為攝像頭的 ID,預設的攝像頭 ID 為 0。

寫入視訊

123456789101112131415161718192021222324252627282930313233343536373839404142
#include <iostream>#include <iomanip>#include <sstream>#include <string>#include <vector>#include <dbg.h>#include <opencv2/core/core.hpp>#include <opencv2/highgui/highgui.hpp>using namespace cv;using namespace std;int main(){    VideoCapture cap;    cap.open(0);    namedWindow("MyVideo", 1);    double dWidth = cap.get(CV_CAP_PROP_FRAME_WIDTH);    double dHeight = cap.get(CV_CAP_PROP_FRAME_HEIGHT);    Size frameSize(static_cast<int>(dWidth), static_cast<int>(dHeight));    VideoWriter oVideoWriter("MyVideo.avi", CV_FOURCC('P','I','M','1'), 20, frameSize, true);	// initialize the VideoWriter objetct    while (1) {        Mat frame;        bool bSuccess = cap.read(frame);        if (!bSuccess){ // if not success, break loop            cout << "ERROR: cannot read a frame from video file" << endl;            break;        }        oVideoWriter.write(frame);        imshow("MyVideo", frame); // show the frame in "MyVideo" window        if(waitKey(10) == 27) {// wait for ESC key            cout << "ESC key is pressed by user" << endl;            break;        }    }    return 0;}

目標檢測

mean-shift 和 camshift

均值漂移(mean-shift)

mean-shift 演算法是一種在一組資料的密度分佈中尋找區域性極值的穩定 [1] 的方法。若分佈是連續的,處理過程就比較容易,這種情況下本質上只需要對資料的密度直方圖應用爬山演算法即可。然而,對於離散的資料集,這個問題在某種程度上是比較麻煩的。

mean-shift 演算法的步驟如下:

  1. 選擇搜尋視窗。
  2. 計算視窗(可能是帶權重的)的重心。
  3. 將視窗的中心設定在計算出的重心處。
  4. 返回第 2 步,直到視窗的位置不再變化(通常會)。

OpenCV 提供 cv::meanshift() 函式來進行 mean-shift 演算法跟蹤。

1
int cv::meanShift(InputArray probImage, Rect& window, TermCriteria criteria)

其中,

  • probImage - 影象直方圖反投影后的結果;
  • window - 初始的查詢視窗,即要跟蹤的區域;
  • criteria - 迭代搜尋演算法的終止條件,主要由 mean-shift 移動的最大迭代次數和可視為視窗位置收斂的最小移動距離組成。
  • 返回的是收斂時演算法的迭代次數。

對於第一個引數 probImage ,可以直接使用 cv::calcBackProject() 得到的結果。但《OpenCV 2 Computer Vision Application Programming Cookbook》建議先把影象轉換到 HSV 顏色空間,然後使用 Hue 單通道的直方圖的反投影變換結果作為 probImage ;

獲取彩色影象的 Hue 通道的直方圖演算法實現如下:

1234567891011121314151617181920212223242526272829303132333435363738394041
// Computes the 1D Hue histogram with a mask.// BGR source image is converted to HSVcv::MatND getHueHistogram(const cv::Mat &image,                             int minSaturation = 0) {    cv::MatND hist;    // Convert to HSV color space    cv::Mat hsv;    cv::cvtColor(image, hsv, CV_BGR2HSV);       // Mask to be used (or not)       cv::Mat mask;       if (minSaturation > 0) {           // Spliting the 3 channels into 3 images           std::vector<cv::Mat> v;           cv::split(hsv, v);           // Mask out the low saturated pixels           cv::threshold(v[1], mask, minSaturation,                         255, cv::THRESH_BINARY);       }    // Prepare arguments for a 1D hue histogram    hranges[0]= 0.0;    hranges[1]= 180.0;    channels[0]= 0; // the hue channel       // Compute histogram    cv::calcHist(&hsv,         1,			// histogram of 1 image only        channels,	// the channel used        mask,	// no mask is used        hist,		// the resulting histogram        1,			// it is a 1D histogram        histSize,	// number of bins        ranges		// pixel value range    );    return hist;}

使用 cv::meanshift() 函式在兩幅影象間跟蹤某一物體的步驟如下:

  1. 讀入第一張影象,定義好目標跟蹤視窗,即感興趣區域 ROI 。
  2. 計算這個 ROI 的 Hue 通道的直方圖;
  3. 讀入第二張影象,轉換到 HSV 顏色空間,並對其 Hue 分量進行閾值處理 cv::threshold(),去掉低飽和度的畫素,以保證較高的飽和度;
  4. 應用 ROI 的 直方圖資訊對第 3 步得到的影象進行反投影變換;
  5. 對反投影變換的結果在進行一次按位與操作 cv::bitwise_and() ,去除結果中低飽和度的畫素;
  6. 最後,對第 5 步得到的結果進行 meanshift 操作 cv::meanShift(),得到新的跟蹤視窗。

實現如下:

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071
int main(){    // Read reference image    cv::Mat image= cv::imread("../baboon1.jpg");    if (!image.data)        return 0;     // Define ROI    cv::Mat imageROI= image(cv::Rect(110,260,35,40));    cv::rectangle(image, cv::Rect(110,260,35,40), cv::Scalar(0,0,255));    // Display image    cv::namedWindow("Image 1");    cv::imshow("Image 1",image);    // Get the Hue histogram    int minSat=65;    ColorHistogram hc;    cv::MatND colorhist= hc.getHueHistogram(imageROI, minSat);    ObjectFinder finder;    finder.setHistogram(colorhist);    finder.setThreshold(0.2f);    // Second image    image= cv::imread("../baboon3.jpg"); 	// Display image    cv::namedWindow("Image 2");    cv::imshow("Image 2",image);    // Convert to HSV space    cv::Mat hsv;    cv::cvtColor(image, hsv, CV_BGR2HSV);    // Split the image    vector<cv::Mat> v;    cv::split(hsv,v);    // Eliminate pixels with low saturation    cv::threshold(v[1],v[1],minSat,255,cv::THRESH_BINARY);    cv::namedWindow("Saturation");    cv::imshow("Saturation",v[1]);    // Get back-projection of hue histogram    int ch[1]={0};    cv::Mat result= finder.find(hsv,0.0f,180.0f,ch,1);    cv::namedWindow("Result Hue");    cv::imshow("Result Hue",result);    // Eliminate low stauration pixels    cv::bitwise_and(result,v[1],result);    cv::namedWindow("Result Hue and raw");    cv::imshow("Result Hue and raw",result);    cv::Rect rect(110,260,35,40);    cv::rectangle(image, rect, cv::Scalar(0,0,255));    cv::TermCriteria criteria(cv::TermCriteria::MAX_ITER,10,0.01);    cout << "meanshift= " << cv::meanShift(result,rect,criteria) << endl;    cv::rectangle(image, rect, cv::Scalar(0,255,0));    // Display image    cv::namedWindow("Image 2 result");    cv::imshow("Image 2 result",image);    cv::waitKey();    return 0;}

《The OpenCV Reference Manual》裡還建議再對反投影的結果進行降噪處理。例如,可以先使用 cv::findContours() 檢索到一些閉合的輪廓邊,使用 cv::contourArea() 去掉一些面積較小的輪廓邊,再使用 cv::drawContours() 填充剩下的輪廓邊。

camshift

camshift 是“continuously adaptive mean-shift”的縮寫,顧名思義就是 mean-shift 的一個改進版本,它的實現基於 meanshift ,而與 meanshift 的最大區別在於視窗的大小和朝向是可變的。如果有一個易於分割的分佈(例如保持緊密的人臉特徵),此演算法可以根據人在走近或遠離攝像機時臉的尺寸而自動調整視窗的尺寸。

OpenCV 提供 cv::Camshift() 函式來進行 mean-shift 演算法跟蹤。

1
RotatedRect CamShift(InputArray probImage, Rect& window, TermCriteria criteria)

該函式將返回一個經過旋轉的矩形結構體 RotatedRect ,該結構體包含了目標的位置、大小和朝向資訊。可以通過 RotatedRect::boundingRect() 函式獲得查詢視窗的下一個位置。

OpenCV 自帶的示例程式中包含一個應用 camshift 捕捉彩色物體的示例 camshiftdemo.c 。

特徵匹配

Harris 角點

經典版本

123456
void cornerHarris(InputArray src,	// input                OutputArray dst,	// output                int blockSize,	// neighborhood size                int ksize,		// aperture size                double k,			// Harris parameter                int borderType=BORDER_DEFAULT )

示例:

1234567891011
// Detect Harris Cornerscv::Mat cornerStrength;cv::cornerHarris(image,cornerStrength,                3,	// neighborhood size                3,	// aperture size                0.01); // Harris parameter// threshold the corner strengthscv::Mat harrisCorners;double threshold= 0.0001;cv::threshold(cornerStrength,harrisCorners,            threshold,255,cv::THRESH_BINARY_INV);

原圖:

結果圖:

與原圖結合:

改進版本

用上面的方法得到的 Harris 角點在影象中的分佈不是很均勻,一個改進的方法是使用 cv::goodFeaturesToTrack() 方法(聽名字就很彪