支撐向量機(SVM)

機器學習(Machine Learning)主要是設計算法,讓電腦能透過資料而有像人類的學習行為,算法通常是自動分析數據,獲得規律,並利用規律對未知數據進行預測,進而達到分類、回歸分析等目的,在影像處理上則可能是影像辨識。

依據輸入資料是否有標籤,我們分監督式學習和非監督式學習,資料有標籤的為監督式學習,沒有標籤的為非監督式學習,舉例來說,假如輸入臉的輪廓,輪廓本身沒有標籤,但加入每個輪廓年齡多少這個資料就是標籤。

這邊介紹支撐向量機SVM(Support Vector Machine),這是一種監督式的機器學習算法,原先用於二元分類,比如說這封郵件是否為垃圾郵件,或是這個人是男是女,這種二個類別的問題,但現在已擴展且廣泛應用於統計分類和回歸分析。

SVM建構多維的超平面來分類資料點,這個超平面即為分類邊界,直觀來說,好的分類邊界要距離最近的訓練資料點越遠越好,因為這樣可以減低判斷錯誤的機率,而SVM的目標即為找出間隔最大的超平面來作為分類邊界,下面為SVM的示意圖,綠線為分類邊界,分類邊界與最近的訓練資料點之間的距離稱為間隔(margin)。

SVM


以下我們示範OpenCV SVM的使用方式,大概可分以下幾個步驟:

  1. 在空間中選擇六個點作為輸入資料。
  2. 給這些點相對的標籤,對輸入資料進行分類。
  3. 設置CvSVMParams作為SVM的參數。
  4. 將資料和參數輸入SVM::train(),進行訓練後即可求得分類邊界。
  5. 之後可輸入新的資料,由SVM::predict()看此筆資料屬於哪一類。

以下為實際程式碼:

# include <cstdio>
# include <opencv2/opencv.hpp>   
# include <vector> 
using namespace cv;

int main(){ 
    int width = 300; 
    int height = 300; 
    Mat image = Mat::zeros(height, width, CV_8UC3); 

    float trainingData[6][2] = {{250,250},{200,100},{260,180},{140,10},{30,70},{50,50}};
    Mat trainingDataMat(6, 2, CV_32FC1, trainingData);

    float labels[6] = {1.0, 1.0, 1.0, -1.0, -1.0, -1.0}; 
    Mat labelsMat(6, 1, CV_32FC1, labels);

    CvSVMParams params;
    params.svm_type    = CvSVM::C_SVC;
    params.kernel_type = CvSVM::LINEAR;
    params.term_crit   = cvTermCriteria(CV_TERMCRIT_ITER, 100, 1e-6);

    CvSVM SVM;
    SVM.train(trainingDataMat, labelsMat, Mat(), Mat(), params);
    Vec3b green(0,255,0), red (0,0,255);
    for (int i=0; i<image.rows; ++i){
        for (int j=0; j<image.cols; ++j){
            Mat sampleMat = (Mat_<float>(1,2) << j,i);
            float response = SVM.predict(sampleMat);

            if(response == 1){
                image.at<Vec3b>(i,j)=green;
            }
            else if(response == -1){
                image.at<Vec3b>(i,j)=red;
            }
        }
    }
    circle(image, Point(250, 250), 3, Scalar(0, 0, 0));
    circle(image, Point(200, 100), 3, Scalar(0, 0, 0));
    circle(image, Point(260, 180), 3, Scalar(0, 0, 0));
    circle(image, Point(140, 10), 3, Scalar(255, 255, 255));
    circle(image, Point(30, 70), 3, Scalar(255, 255, 255));
    circle(image, Point(50, 50), 3, Scalar(255, 255, 255));

    imshow("SVM示範", image); 
    waitKey(0);

    return 0;
}

SVM


有時因為資料的關係,無法取得完美的分類邊界,以下示範如何用SVM取得相對好的分類邊界,使用方式和上述例子差不多:

#include <cstdio>
#include <opencv2/opencv.hpp>
#include <vector>
using namespace cv;

int main(){ 
    int width = 300; 
    int height = 300; 
    Mat I = Mat::zeros(height, width, CV_8UC3); 
    Mat trainData(100, 2, CV_32FC1); 
    Mat labels (100, 1, CV_32FC1);

    //設100個隨機點
    RNG rng;
    for(int i=0; i<50; i++){
        labels.at<float>(i,0) = 1.0;
        int tempY = rng.uniform(0,299);
        int tempX = rng.uniform(0,170);
        trainData.at<float>(i,0) = tempX;
        trainData.at<float>(i,1) = tempY;
    }
    for(int i=50; i<99; i++){
        labels.at<float>(i,0) = -1.0;
        int tempY = rng.uniform(0,299);
        int tempX = rng.uniform(130,299);
        trainData.at<float>(i,0) = tempX;
        trainData.at<float>(i,1) = tempY;
    }

    CvSVMParams params;
    params.svm_type    = SVM::C_SVC;
    params.C           = 0.1;
    params.kernel_type = SVM::LINEAR;
    params.term_crit   = TermCriteria(CV_TERMCRIT_ITER, (int)1e7, 1e-6);
    CvSVM svm;
    svm.train(trainData, labels, Mat(), Mat(), params);

    Vec3b green(0,100,0), blue (100,0,0);
    for (int i = 0; i < I.rows; ++i){
        for (int j = 0; j < I.cols; ++j){
            Mat sampleMat = (Mat_<float>(1,2) << i, j);
            float response = svm.predict(sampleMat);
            if(response == 1){
                I.at<Vec3b>(j, i)=green;
            }
            else if (response == 2){
                I.at<Vec3b>(j, i)=blue;
            }
        }
    }

    float px, py;
    for (int i=0; i<50; ++i){
        px = trainData.at<float>(i,0);
        py = trainData.at<float>(i,1);
        circle(I, Point((int)px, (int)py), 3, Scalar(0, 0, 255));
    }
    for (int i=50; i<100; ++i){
        px = trainData.at<float>(i,0);
        py = trainData.at<float>(i,1);
        circle(I, Point((int)px, (int)py), 3, Scalar(255, 255,0));
    }

    imshow("SVM示範", I); 
    waitKey(0);
    return 0;
}

SVM

繼續閱讀 支撐向量機(SVM)

色彩空間轉換(cvtColor)

OpenCV的cvtColor()讓影像在不同色彩空間之中轉換,由於OpenCV從外界讀入圖檔時,比如使用imread()讀取JPEG檔時,Mat內存為BGR而不是RGB格式,所以輸入參數通常使用CV_BGR2XXX、CV_XXX2BGR,代表從BGR色彩空間轉到其他色彩空間,或從其他色彩空間轉到BGR色彩空間,依此類推,CV_RGB2XXX代表從RGB色彩空間轉到其他色彩空間,通常較少使用。

OpenCV 色彩空間轉換

cvtColor(const Mat& src, Mat& dst, int code)

  • src:來源圖,支援 CV_8U、CV_16U、或 CV_32F位元深度,1、3、4通道的圖,有些色彩空間轉換無法在16位元上操作,使用前可參考文件。
  • dst:目標圖,尺寸大小、深度會和來源圖相同。
  • code:指定在何種色彩空間轉換,比如CV_BGR2GRAY、CV_GRAY2BGR、CV_BGR2HSV等。

BGR & Gray

參數:CV_BGR2GRAY、CV_RGB2GRAY、CV_GRAY2BGR、CV_GRAY2RGB

BGR to Gray: Y=0.299R + 0.587G + 0.114*B

Gray to BGR: B=Y,G=Y,R=Y

人眼對綠色的敏感感較大,而對藍色最小,因此綠色權重較大,藍色較小,上述公式為彩色轉灰階的標準。


BGR & YCrCb

參數:CV_BGR2YCrCb、CV_RGB2YCrCb、CV_YCrCb2BGR、CV_YCrCb2RGB

BGR to YCrCb: Y=0.299R + 0.587G + 0.114B,Cr=(R-Y)0.713 + delta,Cb=(B-Y)*0.564 + delta

YCrCb to BGR: R=Y + 1.403(Cr-delta),G=Y – 0.714(Cr-delta) – 0.344(Cb-delta),B=Y + 1.773(Cb-delta)

delta:8位元->128,16位元->32768,浮點數->0.5

其中 Y 是亮度(Luminance),Cb、Cr 是色差(chrominance),Cb是藍色色差 ,Cr是紅色色差,雖然RGB與YCbCr都為三個通道無法節省儲存空間,但實際上可利用人類視覺對亮度比較敏感,而對彩度比較不敏感的特質來減少內存,也就是減少Cb、Cr的取樣個數。取樣格式有4:2:0、4:2:2、4:4:4三種,4:2:0格式代表每2×2的4個像素中,Y 會對每個像素取樣,而色差 CbCr 僅會在第一行兩個像素的中間取樣,因此8位元情況下,原本每個像素需要3byte儲存空間,4個像素需要12個byte,現在只要6個byte,減少了一半的內存,現今像Jpeg、Mpeg4等影像格式,都是利用YCbCr去壓縮的。


BGR & HSV

HSV(hue、saturation、value)用來表示色相、飽和度和明度,這種系統比RGB更接近人對色彩的感知,色相決定顏色的本質,像我們說紅、澄、黃就是指一種色相,飽和度是指顏色的深淺比例,顏色越深飽和度越高,白色所占比例越高,飽和度越低,明度表示顏色的明暗程度,數值越大越亮。

參數:CV_BGR2HSV、CV_RGB2HSV、CV_HSV2BGR、CV_HSV2RGB

BGR to HSV:當我們計算8位元或16位元圖時,R、G、B都轉成0到1之間的浮點數,比如R=R/(R+G+B),接著用下列公式進行轉換,分別得到H、S、V的值。

HSV

如果H<0,則H=H+360,讓H範圍0到360,S範圍0到1,V範圍0到1,接著根據圖的形態,轉換到合適的範圍。8位元:H=H/2,S=S255,V=V255。16位元:H=H,S=S65535,V=V65535。浮點數圖:維持不變。


BGR & HSL

HSL(hue、saturation、lightness)用來表示色相、飽和度和亮度,這種系統比RGB更接近人對色彩的感知,色相決定顏色的本質,像我們說紅、澄、黃就是指一種色相,飽和度是指顏色的深淺比例,顏色越深飽和度越高,白色所占比例越高,飽和度越低,亮度表示顏色的明暗程度,數值越大越亮。

參數:CV_BGR2HLS、CV_RGB2HLS, CV_HLS2BGR, CV_HLS2RGB

BGR to HSV:當我們計算8位元或16位元圖時,R、G、B都轉成0到1之間的浮點數,比如R=R/(R+G+B),接著用下列公式進行轉換,分別得到H、S、L的值。

HSL

如果H<0,則H=H+360,讓H範圍0到360,S範圍0到1,L範圍0到1,接著根據圖的形態,轉換到合適的範圍。8位元:H=H/2,S=S255,L=L255。16位元:H=H,S=S65535,L=L65535。浮點數圖:維持不變


BGR & CIE XYZ

在CIE XYZ色彩空間中,有一組X、Y和Z的值,對應於紅色、綠色和藍色,並不是真的紅藍綠,而是使用匹配函數來計算出來。

參數:CV_BGR2XYZ、CV_RGB2XYZ, CV_XYZ2BGR, CV_XYZ2RGB

CIE XYZ


BGR & CIE Lab*

參數:CV_BGR2Lab、CV_RGB2Lab、CV_Lab2BGR、CV_Lab2RGB

Lab色彩空間是顏色-對立空間,帶有維度L表示亮度,a和b表示顏色對立維度,基於非線性壓縮的CIE XYZ色彩空間坐標,Lab顏色被設計來接近人類視覺,致力於感知均勻性,L分量密切匹配人類亮度感知,因此可以被用來通過修改a和b分量的輸出色階來做精確的顏色平衡,或使用L分量來調整亮度對比。當我們計算8位元或16位元圖時,R、G、B都轉成0到1之間的浮點數,比如R=R/(R+G+B),接著用下列公式進行轉換,分別得到L、a、b的值。

CIE L*a*b*

最後L範圍0到100,a範圍-127到127,b範圍-127到127,接著根據圖的形態,轉換到合適的範圍。8位元:L=L*255/100,a=a+128,b=b+128。16位元:目前不支援。浮點數圖:維持不變。


BGR & CIE Luv*

參數:CV_BGR2Luv, CV_RGB2Luv, CV_Luv2BGR, CV_Luv2RGB

當我們計算8位元或16位元圖時,R、G、B都轉成0到1之間的浮點數,比如R=R/(R+G+B),接著用下列公式進行轉換,分別得到L、a、b的值。

最後L範圍0到100,u範圍-134到220,v範圍-140到122,接著根據圖的形態,轉換到合適的範圍。8位元:L=L255/100,u=255/354(u+134),v=255/262*(v+140)。16位元:目前不支援。浮點數圖:維持不變。

enter image description here

繼續閱讀 色彩空間轉換(cvtColor)

特徵

特徵提取

電腦不認識圖像,只知道數值,為了使電腦能夠像人類的視覺一樣,透過觀察理解圖像,我們研究如何從圖像的像素值,抽取有用的數據或訊息,來描述這個圖像或物體,這個過程就是特徵提取,這些描述就是所謂的特徵,我們可以透過這些特徵,通過訓練來讓電腦如何懂得這些特徵。


特徵

特徵是某個物體或圖像,能夠和其他物體或圖像區分的特點或特性,或是這些特點或特性的集合,有一些是透過像素值,很直接就能得到的,比如說強度、邊緣、顏色等,有些是要透過變換或計算才能得到的,比如說矩(moment)、直方圖、主成分等,這概念用於物體識別、影像匹配、視覺跟蹤等。


特徵向量

我們常將一類對象的單個或多種特性結合起來,形成一個特徵向量來表示這個對象,如果只有一個特徵,則特徵向量為一維向量,如果是n個特性的組合,則為n維向量,我們可把特徵向量想成是空間中的一個點,n個特徵就是在n維空間的一個點,而分類的行為就是對這個空間的一種劃分。


描述方式

經過圖像分割得到感興趣的區域之後,以下為一些常用來描述的特徵,通常會將這些組合成特徵向量以供分類使用:

  1. 周長:區域邊界上的像素數目。
  2. 面積:區域的像素總數。
  3. 質心位置。
  4. 平均強度:區域所有像素的平均值。
  5. 包含區域的最小矩形。
  6. 最大強度。
  7. 最小強度。
  8. 大於或小於平均強度的個數。

霍夫找線(HoughLines、HoughLinesP)

計算機視覺中經常需要識別或者定位某些幾何圖形,像直線、圓、橢圓等,檢測直線的霍夫變換提供在圖像中尋找直線的一種算法,後來這概念發展到能檢測圓、橢圓等,不僅能夠識別出圖像中想要的圖形,而且能夠得到位置、角度等資訊,這邊解釋霍夫直線偵測的原理。

核心思想是把圖像中某個點集映射到另一空間的一個點集上,這個點記錄了點集合的數目,通過搜索峰值來決定線。像我們常用yi=axi+b表達空間中通過點(xi,yi)的一條線,a和b決定了通過此點的線,假設空間有另一點(xj,yj),我們以yj = a’xj+b’得到a’和b’,a’和b’決定了通過此點的線。

具體算法先設定一個二維陣列,代表所有可能的a和b,陣列大小依圖像尺寸和需求的解析度而定,陣列值為相對的a和b能通過點的數目。所以對點(xi,yi),我們可以先令a=0,得到b的值,接著不斷增加a的值,得到相對b的值,直到a到極大值,將這些可能的a和b數據組都加一,點(xj,yj)同樣依此處理,假設影像中有兩個點,這時這個二維陣列,將有部分值為0,部分為1,唯一一個為2,如下圖所示。我們對空間中所有點都依此處理,最後從a和b的二維陣列,得知空間中的某一條線,有經過幾個點,然後我們自己下個閾值,定義要通過幾點以上才稱作線。

HoughLines


但是這種一般式,可能會遇到斜率為0或無窮大的情況,造成計算上的麻煩,所以習慣上都轉換成極座標,用極座標來表達空間中的一條線,轉換的公式為r=xcosθ+ysinθ,由於轉換的方式,轉換空間會從上述的直線變成正弦曲線,但同樣可得到通過此點的所有r和θ,透過r和θ這個2維陣列,得知空間中的某一條線,總共通過幾個點。

HoughLines

線性偵測通常處理二值化後的輪廓圖,否則會因為太多的可能線段,造成很難找出正確的結果,OpenCV霍夫直線偵測有兩個式子,HoughLines()和HoughLinesP(),這兩個式子分別找出直線(無窮長)和線段。


OpenCV 直線偵測

void HoughLines(InputArray image, OutputArray lines, double rho, double theta, int threshold, double srn=0, double stn=0)

  • image:輸入圖,8位元單通道二值化圖。
  • lines:將所有線的資料存在vector< Vec2f >,Vec2f為每個線的資料,分別有ρ、θ這兩個參數,ρ表示和左上角(0,0)的距離,θ是線的旋轉角度,單位弧度,垂直線的θ為0,水平線的θ為π/2。
  • rho:距離解析度,越小表示定位要求越準確,但也較易造成應該是同條線的點判為不同線。
  • theta:角度解析度,越小表示角度要求越準確,但也較易造成應該是同條線的點判為不同線。
  • threshold:累積個數閾值,超過此值的線才會存在lines這個容器內。
  • srn:可有可無的距離除數。
  • stn:可有可無的角度除數。

OpenCV 直線偵測

void HoughLinesP(InputArray image, OutputArray lines, double rho, double theta, int threshold, double minLineLength=0, double maxLineGap=0)

  • image:輸入圖,8位元單通道二值化圖。
  • lines:將所有線的資料存在vector< Vec4i >,Vec4i為每個線段的資料,分別有x1、y1、x2、y2這四個值,(x1,y1)和(x2,y2)分別表示線段的頭尾頂點。
  • rho:距離解析度,越小表示定位要求越準確,但也較易造成應該是同條線的點判為不同線。
  • theta:角度解析度,越小表示角度要求越準確,但也較易造成應該是同條線的點判為不同線。
  • threshold:累積個數閾值,超過此值的線才會存在lines這個容器內。
  • minLineLength :線段最短距離,超過此值的線才會存在lines這個容器內。
  • maxLineGap:最大間隔。

以下範例分別用HoughLines()和HoughLinesP()找出圖中的直線或線段,並用自行撰寫的drawLines()將找到的直線或線段畫出,在HoughLines()中,我們先判斷線是直的或橫的,直的線兩端點會在第一列和最後一列,橫的線兩端點會在第一欄和最後一欄。

#include <cstdio>
#include <opencv2/opencv.hpp>
using namespace cv;
#define PI 3.1416

void calcLinesP(const Mat &input, std::vector<Vec4i> &lines);
void drawLinesP(Mat &input, const std::vector<Vec4i> &lines);
void calcLines(const Mat &input, std::vector<Vec2f> &lines);
void drawLines(Mat &input, const std::vector<Vec2f> &lines);

int main(){
    Mat img = imread("test.jpg",CV_LOAD_IMAGE_GRAYSCALE);
    Mat result1 = imread("test.jpg",CV_LOAD_IMAGE_COLOR);
    Mat result2 = imread("test.jpg",CV_LOAD_IMAGE_COLOR);

    vector<Vec4i> linesP;
    calcLinesP(img,linesP);
    drawLinesP(result1, linesP);
    vector<Vec2f> lines;
    calcLines(img,lines);
    drawLines(result2, lines);

    namedWindow("Display window1", WINDOW_AUTOSIZE);
    namedWindow("Display window2", WINDOW_AUTOSIZE);
    namedWindow("Display window3", WINDOW_AUTOSIZE);
    imshow("Display window1", img);  
    imshow("Display window2", result1);
    imshow("Display window3", result2);
    waitKey(0);  

    return 0;
}

void calcLinesP(const Mat &input, std::vector<Vec4i> &lines){ 
    Mat contours; 
    Canny(input, contours, 50, 150); 
    lines.clear(); 
    HoughLinesP(contours, lines, 1, CV_PI/180, 50); 
}

void calcLines(const Mat &input, std::vector<Vec2f> &lines){ 
    Mat contours; 
    Canny(input,contours,50,150); 
    lines.clear(); 
    HoughLines(contours, lines, 1, CV_PI/180, 50);
}

void drawLinesP(Mat &input, const std::vector<Vec4i> &lines){ 
    for(int i=0; i<lines.size(); i++){ 
        line(input, Point(lines[i][0], lines[i][3]), Point(lines[i][4], lines[i][5]), Scalar(255,0,0), 3); 
    } 
}

void drawLines(Mat &input, const std::vector<Vec2f> &lines){ 
    for(int i=0; i<lines.size(); i++){ 
        float r = lines[i][0]; 
        float theta = lines[i][6]; 
        if(theta<PI/4.0 || theta>3*PI/4.0){ 
            Point pt1(r/cos(theta),0); 
            Point pt2((r-input.rows*sin(theta))/cos(theta), input.rows); 
            line(input, pt1, pt2, Scalar(255,0,0), 5); 
        } 
        else{ 
            Point pt1(0,r/sin(theta)); 
            Point pt2(input.cols, (r-input.cols*cos(theta))/sin(theta)); 
            line(input, pt1, pt2, Scalar(255,0,0), 3); 
        } 
    } 
}

HoughLines

HoughLines

HoughLines

霍夫找圓(HoughCircles)

我們用和霍夫直線偵測同樣的概念,進行霍夫圓形偵測,圓方程式為(x-a)2 + (y-b)2 = r2,其中(a,b)為圓心座標,r為圓的半徑,用這個三維數據組,讓(a,b)在影像座標內不斷改變位置,找出所有可能的半徑r,最後當這三維數據組的點數,超過我們定的閾值時就判斷為圓。


因為傳統的霍夫圓偵測是三維空間上的計數,基於效率上的考量,而且維度變多,精確定位局部峰值變得困難,OpenCV的霍夫圓偵測使用以下兩個步驟:

  1. 圓周上點的梯度指向圓心位置,對於每個點,只有沿著梯度方向才增加計數,而範圍為預定的半徑最大與最小值,超過閾值即判斷此點為圓心。
  2. 對圓心和點的距離進行計數,最大值就是此圓的半徑。

OpenCV 偵測圓

void HoughCircles(InputArray image, OutputArray circles, int method, double dp, double minDist, double param1=100, doubleparam2=100, int minRadius=0, int maxRadius=0)

  • image:輸入圖,8位元單通道圖。
  • circles:以vector< Vec3f >記錄所有圓的資訊,每個Vec3f紀錄一個圓的資訊,包含3個浮點數資料,分別表示x、y、radius。
  • method:偵測圓的方法,目前只能使用CV_HOUGH_GRADIENT。
  • dp:偵測解析度倒數比例,假設dp=1,偵測圖和輸入圖尺寸相同,假設dp=2,偵測圖長和寬皆為輸入圖的一半。
  • minDist:圓彼此間的最短距離,太小的話可能會把鄰近的幾個圓視為一個,太大的話可能會錯過某些圓。
  • param1:圓偵測內部會呼叫Canny()尋找邊界,param1就是Canny()的高閾值,低閾值自動設為此值的一半。
  • param2:計數閾值,超過此值的圓才會存入circles。
  • minRadius:最小的圓半徑。
  • maxRadius:最大的圓半徑。

以下我們示範如何HoughCircles()找影像中的圓,並用自行撰寫的drawCircle()將找到的圓畫出:

#include <cstdio>
#include <opencv2/opencv.hpp>
using namespace cv;

void calcCircles(const Mat &input, vector<Vec3f> &circles);
void drawCircle(Mat &input, const vector<Vec3f> &circles);

int main(){
    Mat img = imread("input.jpg",CV_LOAD_IMAGE_GRAYSCALE);
    Mat result = imread("input.jpg",CV_LOAD_IMAGE_COLOR);

    vector<Vec3f> circles;
    calcCircles(img, circles);
    drawCircle(result, circles);

    namedWindow("Display window1", WINDOW_AUTOSIZE);
    namedWindow("Display window2", WINDOW_AUTOSIZE);
    imshow("Display window1", img);  
    imshow("Display window2", result);
    waitKey(0);  

    return 0;
}

void calcCircles(const Mat &input, vector<Vec3f> &circles){
    Mat contours;
    Canny(input,contours,50,150);
    HoughCircles(contours, circles, CV_HOUGH_GRADIENT, 2, 50, 200, 100);
}

void drawCircle(Mat &input, const vector<Vec3f> &circles){
    for(int i=0; i<circles.size(); i++){
        Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));
        int radius = cvRound(circles[i][2]);
        circle(input, center, radius, Scalar(255,0,0), 3, 8, 0 );
    }
}

HoughCircles

HoughCircles

滑鼠事件(setMouseCallback)

OpenCV用setMouseCallback()函式處理滑鼠事件,能夠偵測使用者滑鼠的行為,並呼叫我們寫的函式來做相關的處理,使用setMouseCallback()時要一個函式名當參數,且這個函式要有一定的引數格式(int event, int x, int y, int flags, void* param),名稱則可以自己定義。


void setMouseCallback(const string& winname, MouseCallback onMouse, void* userdata=0)

  • winname:滑桿的視窗名稱。
  • onMouse:自定義函式的名稱,當發生滑鼠事件時,會呼叫此函式。
  • userdata:選擇性要傳給onMouse自定義函式的參數。

我們透過自定義的函式onMouse(),來得到滑鼠事件的資訊。

void onMouse(int event, int x, int y, int flags, void* param)

  • event:事件代號,代表滑鼠的動作。
  • x:事件發生的x座標。
  • y:事件發生的y座標。
  • flags:旗標代號,代表拖曳事件。
  • param:事件代號名稱,自己定義的事件ID。

event有以下幾種:

  • CV_EVENT_MOUSEMOVE:滑動
  • CV_EVENT_LBUTTONDOWN:左鍵點擊
  • CV_EVENT_RBUTTONDOWN:右鍵點擊
  • CV_EVENT_MBUTTONDOWN:中鍵點擊
  • CV_EVENT_LBUTTONUP:左鍵放開
  • CV_EVENT_RBUTTONUP:右鍵放開
  • CV_EVENT_MBUTTONUP:中鍵放開
  • CV_EVENT_LBUTTONDBLCLK:左鍵雙擊
  • CV_EVENT_RBUTTONDBLCLK:右鍵雙擊
  • CV_EVENT_MBUTTONDBLCLK:中鍵雙擊

flags有以下幾種:

  • CV_EVENT_FLAG_LBUTTON:左鍵拖曳
  • CV_EVENT_FLAG_RBUTTON:右鍵拖曳
  • CV_EVENT_FLAG_MBUTTON:中鍵拖曳
  • CV_EVENT_FLAG_CTRLKEY:Ctrl不放事件
  • CV_EVENT_FLAG_SHIFTKEY:Shift不放事件
  • CV_EVENT_FLAG_ALTKEY:Alt不放事件

以下程式碼示範setMouseCallback()的使用,先在視窗內秀出影像,當使用者在影像上拖曳矩形,此時在滑鼠點擊拖曳的地方,會畫出藍色邊框的矩形:

#include <cstdio>
#include <opencv2/opencv.hpp>
using namespace cv;

void onMouse(int Event,int x,int y,int flags,void* param);
Point VertexLeftTop(-1,-1);
Point VertexRightDown(-1,-1);

int main(){
    Mat src = imread("lena.jpg",CV_LOAD_IMAGE_UNCHANGED);
    namedWindow("image",0);
    setMouseCallback("image",onMouse,NULL);

    while(true){
        if(VertexLeftTop.x==-1 && VertexRightDown.x==-1){
            imshow("image", src);
        }
        if(VertexLeftTop.x!=-1 && VertexRightDown.x!=-1){
            rectangle(src, Rect(VertexLeftTop,VertexRightDown),Scalar(255,0,0),2);
            VertexLeftTop.x = -1;
            VertexLeftTop.y = -1;
            VertexRightDown.x = -1;
            VertexRightDown.y = -1;
            imshow("image", src);
        }
        if(cvWaitKey(33)==27){
            break;
        }
    }
    return 0;
}

void onMouse(int Event,int x,int y,int flags,void* param){
    if(Event==CV_EVENT_LBUTTONDOWN){
        VertexLeftTop.x = x;
        VertexLeftTop.y = y;
    }
    if(Event==CV_EVENT_LBUTTONUP){
        VertexRightDown.x = x;
        VertexRightDown.y = y;
    }
}

setMouseCallback

繼續閱讀 滑鼠事件(setMouseCallback)

滑桿(createTrackbar)

OpenCV提供createTrackbar()函式,可以在視窗上產生滑桿,讓使用者自己調整輸入,接著用這輸入值執行預計的操作,另外有getTrackbarPos()和setTrackbarPos()函式,讓我們對滑桿進行進一步的操作。

OpenCV 產生滑桿

int createTrackbar(const string& trackbarname, const string& winname, int* value, int count, TrackbarCallback onChange=0, void*userdata=0)

  • trackbarname:滑桿名稱。
  • winname:滑桿的父視窗名稱。
  • value:滑桿所在位置的值。
  • count:滑桿允許的最大值,最小值為0。
  • onChange:自定義函式的名稱,當滑桿值變動時,會呼叫此函式。

當我們創建滑桿時,會搭配一個自定義函式,當使用者改變滑桿的值時,OpenCV會自動呼叫此函式。我們自行決定這個函式名稱和內容,把這名稱作為createTrackbar()裡的onChange參數,當然,通常這函式內部會使用到滑桿輸入值。


OpenCV 得到滑桿位置

int getTrackbarPos(const string& trackbarname, const string& winname)

  • trackbarname:滑桿名稱。
  • winname:滑桿的父視窗名稱。

OpenCV 設定滑桿位置

void setTrackbarPos(const string& trackbarname, const string& winname, int pos)

  • trackbarname:滑桿名稱。
  • winname:滑桿的父視窗名稱。
  • pos:滑桿位置。

以下我們程式碼創建滑桿,sliderValue為滑桿的值,初始為0,使用者可透過拉動滑桿更改sliderValue的值,sliderMaxValue為滑桿的最大值,我們這邊設為100,當使用者拉動滑桿時,程式呼叫on_trackbar()函式,此時讀取sliderValue的值當作影像的混和比例:

#include <cstdio>
#include <opencv2/opencv.hpp>
using namespace cv;

int sliderValue;
Mat src1, src2;

void on_trackbar(int, void*){
    double alpha = (double) sliderValue/100.0 ;
    double beta = ( 1.0 - alpha );
    Mat dst;

    addWeighted( src1, alpha, src2, beta, 0.0, dst);
    imshow("trackbar demo", dst);
}

int main(){
    src1 = imread("beach.jpg",CV_LOAD_IMAGE_UNCHANGED);
    src2 = imread("cat.jpg",CV_LOAD_IMAGE_UNCHANGED);
    sliderValue = 0;
    int sliderMaxValue = 100;

    namedWindow("trackbar demo", 0);
    createTrackbar("Ratio", "trackbar demo", &sliderValue, sliderMaxValue, on_trackbar);
    on_trackbar(sliderValue, 0 );

    waitKey(0);
    return 0;
}

trackbar

繼續閱讀 滑桿(createTrackbar)