2018-11-28【機(jī)器視覺筆錄】OpenCV特征提取與檢測(cè)

--------------------------------
Author : ShawnDong
updateDate :2018.11.28
Blog : ShawnDong98.github.io
--------------------------------

概述

什么是圖像特征

可以表達(dá)圖像中對(duì)象的主要信息大渤,并且以此為依據(jù)可以從其他未知圖像中檢測(cè)出相似或者相同對(duì)象

常見的圖像特征

  • 邊緣
  • 角點(diǎn)
  • 紋理

提取方法
描述子生成

**特征提取與描述

  • SIFT
  • SURF
  • HOG
  • Haar
  • LBP
  • KAZE
  • AKAZE
  • BRISK

對(duì)特征檢測(cè)的描述可以簡單總結(jié)為DDM

  • Detection:檢測(cè)對(duì)象
  • Description:描述對(duì)象
  • Matching:匹配對(duì)象

Haaris角點(diǎn)檢測(cè)

Harris角點(diǎn)檢測(cè)理論(1998提出)


最核心的部分:


參數(shù)說明

  void cv::cornerHarris(
      InputArray src,
      OutputArray dst, 
      int blockSize,
      int ksize,
      double k,
      int borderType = BORDER_DEFAULT
      )

blockSize:計(jì)算lambda1和lambda2乘積時(shí)的矩陣大小
Kszie: 窗口大小
K: 表示計(jì)算角度相應(yīng)時(shí)候的參數(shù)大小疯潭,默認(rèn)在0.04~0.06
閾值t, 用來過濾角度響應(yīng)

代碼演示

void Harris_Demo(int, void*)
{
    Mat norm_dst, normScaleDst;
    dst = Mat::zeros(gray_src.size(), CV_32FC1);

    int blockSize = 2;
    int ksize = 3;
    double k = 0.04;
    cornerHarris(gray_src, dst, blockSize, ksize, k, BORDER_DEFAULT);
    normalize(dst, norm_dst, 0, 255, NORM_MINMAX, CV_32FC1, Mat());
    convertScaleAbs(norm_dst, normScaleDst);

    Mat resultImg = src.clone();
    for (int row = 0; row < resultImg.rows; row++)
    {
        uchar* currentRow = normScaleDst.ptr(row);
        for (int col = 0; col < resultImg.cols; col++)
        {
            int value = (int)* currentRow;
            if (value > thresh)
            {
                circle(resultImg, Point(col, row), 2, Scalar(0, 0, 255), 2, 8, 0);
            }
            currentRow++;
        }
    }
    imshow(output_title, resultImg);
}

突發(fā)奇想:RGB圖片是如何轉(zhuǎn)化為灰度圖像的懒棉?

總的來說RGB圖像是有3個(gè)通道巷懈,也就是一個(gè)3維的矩陣该抒,而灰度圖,大家都知道只有一個(gè)通道顶燕,那么如何將一個(gè)3通道的事物轉(zhuǎn)為1通道的事物呢凑保?
其實(shí)這其中是有一個(gè)轉(zhuǎn)換公式的,簡單來說涌攻,就是把RGB3個(gè)通道的分量按照一定的比例計(jì)算到灰度圖像中欧引。即 公式所闡述的那樣,Gray = R0.299 + G0.587 + B*0.114

Shi-Tomasi角點(diǎn)檢測(cè)

Shi-Tomasi角點(diǎn)檢測(cè)理論(94)


和Harris角點(diǎn)檢測(cè)大致相似癣漆,取lambda1和lambda2中的最小值维咸,計(jì)算量小。

參數(shù)說明

void cv::goodFeatureToTrack(
          InputArray image,
          OutputArray corners,
          int maxCorners,
          double qualityLevel,
          InputArray mask = noArray(),
          int blockSize,
          bool useHarrisDectector = false,
          double k = 0.04
          )

maxCorners:表示返回角點(diǎn)的數(shù)目惠爽,如果檢測(cè)出來角點(diǎn)數(shù)目大于最大數(shù)目癌蓖,則返回最大數(shù)目
qualityLevel: 表示最小可接受的向量值1500, 0.01婚肆, 15
minDistance: 兩個(gè)角點(diǎn)之間的最小距離(歐幾里得距離)
blocksize: 計(jì)算導(dǎo)數(shù)微分不同的窗口大小
useHarrisDetector:是否使用Harris角點(diǎn)檢測(cè)

代碼演示

void ShiTomasi_Demo(int, void*)
{
    if (num_corners < 5)
    {
        num_corners = 5;
    }
    vector<Point2f>corners;
    double qualityLevel = 0.01;
    double minDistance = 10;
    int blockSize = 3;
    bool useHarris = false;
    double k = 0.04;
    Mat resultImg = src.clone();
    goodFeaturesToTrack(gray_src, corners, num_corners, qualityLevel, minDistance, Mat(), blockSize, useHarris, k);
    printf("Number of Detected Corners: %d\n", corners.size());

    for (size_t t = 0; t < corners.size(); t++)
    {
        circle(resultImg, corners[t], 2, Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)), 2, 8, 0);
    }
    imshow(output_title, resultImg);

}

自定義角點(diǎn)檢測(cè)器

自定義角點(diǎn)檢測(cè)器

  • 基于Harris與Shi-Tomasi角點(diǎn)檢測(cè)
  • 首先通過計(jì)算矩陣M得到lambda1和lambda2兩個(gè)特征值根據(jù)他們得到角點(diǎn)響應(yīng)值
  • 然后自己設(shè)置閾值實(shí)現(xiàn)計(jì)算出閾值得到有效響應(yīng)值的角點(diǎn)位置

相關(guān)API說明

  void cv::cornerEigenValsAndVecs(
      InputArray src,
      OutputArray dst,
      int blockSize,
      int ksize,
      int boderType=BODER_DEFAULT
      )
  void cv::cornerMinEigenVal(
      InputArray src,
      OutputArray dst,
      int blockSize,
      int ksize = 3,
      int boderType=BODER_DEFAULT
      )

自定義harris特征檢測(cè)

    //自定義Harris角點(diǎn)檢測(cè)
    cvtColor(src1, gray_src, COLOR_BGR2GRAY);
    //計(jì)算特征值
    int blockSize = 3;
    double k = 0.04;
    int ksize = 3;
    harris_dst = Mat::zeros(src1.size(), CV_32FC(6));
    harrisRspImg = Mat::zeros(src1.size(), CV_32FC1);
    cornerEigenValsAndVecs(gray_src, harris_dst, blockSize, ksize, 4);

    //計(jì)算響應(yīng)
    for (int row = 0; row < harris_dst.rows; row++)
    {
        for (int col = 0; col < harris_dst.cols; col++)
        {
            double lambda1 = harris_dst.at<Vec6f>(row, col)[0];
            double lambda2 = harris_dst.at<Vec6f>(row, col)[1];
            harrisRspImg.at<float>(row, col) = lambda1 * lambda2 - k * pow((lambda1 + lambda2), 2);
        }
    }

    minMaxLoc(harrisRspImg, &harris_min_rsp, &harris_max_rsp, 0, 0, Mat());
    cout << harris_min_rsp << endl;
    cout << harris_max_rsp << endl;
    namedWindow(harris_win, CV_WINDOW_AUTOSIZE);
    createTrackbar("Quality Value:", harris_win, &qualityLevel, max_count, CustomHarris_Demo);
    CustomHarris_Demo(0, 0);

void CustomHarris_Demo(int, void*)
{
    if (qualityLevel < 10)
    {
        qualityLevel = 10;
    }
    Mat resultImg = src1.clone();
    float t = harris_min_rsp + (((double)qualityLevel) / max_count)*(harris_max_rsp - harris_min_rsp);
    for (int row = 0; row < src1.rows; row++)
    {
        for (int col = 0; col < src1.cols; col++)
        {
            float v = harrisRspImg.at<float>(row, col);
            if (v > t)
            {
                circle(resultImg, Point(col, row), 2, Scalar(0, 0, 255), 2, 8, 0);
            }
        }
    }
    imshow(harris_win, resultImg);
}

自定義ShiTomasi特征檢測(cè)

    //計(jì)算最小特征值
    int blockSize = 3;
    double k = 0.04;
    int ksize = 3;
    cvtColor(src1, gray_src, COLOR_BGR2GRAY);
    shiTomasiRsp = Mat::zeros(src1.size(), CV_32FC1);
    cornerMinEigenVal(gray_src, shiTomasiRsp, blockSize, ksize, 4);
    minMaxLoc(shiTomasiRsp, &shitomasi_min_rsp, &shitomasi_max_rsp, 0, 0, Mat());
    namedWindow(shiTomasi_win, CV_WINDOW_AUTOSIZE);
    createTrackbar("Quality:", shiTomasi_win, &sm_qualityLevel, max_count, CustomShiTomasi_Demo);
    CustomShiTomasi_Demo(0, 0);

void CustomShiTomasi_Demo(int, void*)
{
    if (sm_qualityLevel < 20)
    {
        sm_qualityLevel = 20;
    }
    Mat resultImg = src1.clone();
    float t = shitomasi_min_rsp + (((double)sm_qualityLevel) / max_count)*(shitomasi_max_rsp - shitomasi_min_rsp);
    for (int row = 0; row < src1.rows; row++)
    {
        for (int col = 0; col < src1.cols; col++)
        {
            float v = shiTomasiRsp.at<float>(row, col);
            if (v > t)
            {
                circle(resultImg, Point(col, row), 2, Scalar(0, 0, 255), 2, 8, 0);
            }
        }
    }
    imshow(shiTomasi_win, resultImg);
}

亞像素級(jí)別角點(diǎn)檢測(cè)

提高檢測(cè)精準(zhǔn)度
理論與現(xiàn)實(shí)總是不一致的租副,實(shí)際情況下幾乎所有的角點(diǎn)都不會(huì)是一個(gè)真正的準(zhǔn)確像素點(diǎn)。(100较性, 5)實(shí)際上(100.234用僧, 5.789)

亞像素定位

  • 插值方法
  • 基于圖像矩計(jì)算
  • 曲線擬合方法-(高斯曲面、多項(xiàng)式赞咙、橢圓曲面)

代碼演示

void SubPixel_Demo(int, void*)
{
    if (max_corners < 5)
    {
        max_corners = 5;
    }
    vector<Point2f> corners;
    double qualityLevel = 0.01;
    double minDistance = 10;
    int blockSize = 3;
    double k = 0.04;
    cvtColor(src, gray_src, CV_BGR2GRAY);
    goodFeaturesToTrack(gray_src, corners, max_corners, qualityLevel, minDistance, Mat(), blockSize, false, k);
    cout << "number of corners: " << corners.size() << endl;
    Mat resultImg = src.clone();
    for (size_t t = 0; t < corners.size(); t++)
    {
        circle(resultImg, corners[t], 2, Scalar(0, 0, 255), 2, 8, 0);
    }

    imshow(output_title, resultImg);
    Size winSize = Size(5, 5);
    Size zerozone = Size(-1, -1);
    TermCriteria tc = TermCriteria(TermCriteria::EPS + TermCriteria::MAX_ITER, 40, 0.001);
    cornerSubPix(gray_src, corners, winSize, zerozone, tc);

    for (size_t t = 0; t < corners.size(); t++)
    {
        cout << (t + 1) << "point[x, y]: " << corners[t].x << "," << corners[t].y << endl;
    }
}

SURF特征檢測(cè)

SURF(Speeded Up Rpbust Features)特征關(guān)鍵特性:

  • 特征檢測(cè)
  • 尺度空間
  • 選擇不變性
  • 特征向量

工作原理

  1. 選擇圖像中的POI(Point of Interest) Hessian Matrix
  2. 在不同的尺度空間發(fā)現(xiàn)關(guān)鍵點(diǎn)责循,非最大信號(hào)抑制
  3. 發(fā)現(xiàn)特征點(diǎn)方法、旋轉(zhuǎn)不變性要求
  4. 生成特征向量

Hessian矩陣
積分向量

相關(guān)API

    cv::xfeatures2d::SURF::create(
      double hessian Threshold = 100,
      int nOctaves = 4,
      int cOctaveLayers = 3,
      bool extended = false,
      bool upright = false
      )
  • upright:0表示計(jì)算選擇不變性攀操, 1表示不計(jì)算院仿, 速度更快
  • HessianThreshold 默認(rèn)值在300~500之間
  • Octave: 4表示在四個(gè)尺度空間
  • OctaveLayers: 表示每個(gè)尺度的層數(shù)

代碼演示

    //SURF特征檢測(cè)
    int minHessian = 100;
    Ptr<SURF> detector = SURF::create(minHessian);
    vector<KeyPoint> keypoints;
    detector->detect(src, keypoints, Mat());

    //繪制關(guān)鍵點(diǎn)
    Mat keypoint_img;
    drawKeypoints(src, keypoints, keypoint_img, Scalar::all(-1), DrawMatchesFlags::DEFAULT);
    imshow(output_title, keypoint_img);

SIFT特征檢測(cè)

SIFT(Scale-Invariant Feature Transform)特征檢測(cè)關(guān)鍵特性:

  • 建立尺度空間,尋找最大值

工作原理:
1.構(gòu)建圖像高斯金字塔, 求取DOG歹垫, 發(fā)現(xiàn)每一級(jí)的最大與最小值剥汤。
2.構(gòu)建的高斯金字塔,每一層根據(jù)sigma的值不同排惨,可以分為多個(gè)等級(jí)吭敢,最少有四個(gè)

  • 關(guān)鍵點(diǎn)定位(尋找關(guān)鍵點(diǎn)準(zhǔn)確位置與刪除弱邊緣)

1.我們?cè)谙袼丶?jí)別獲得了極值點(diǎn)的位置,但是更準(zhǔn)確的點(diǎn)應(yīng)該在亞像素位置暮芭,如何得到這個(gè)過程稱為關(guān)鍵點(diǎn)(準(zhǔn)確/精確)定位鹿驼。
2.刪除弱邊緣,通過Hessian矩陣特征值實(shí)現(xiàn),小于閾值自動(dòng)舍棄

  • 關(guān)鍵點(diǎn)方向指定

1.根據(jù)給定的窗口大小幸逆,求得每一層對(duì)應(yīng)的圖像的梯度
2.計(jì)算每個(gè)高斯權(quán)重,sigma=scale1.5, 0~360之間建立36個(gè)直方圖Bins
3.找最高峰對(duì)應(yīng)的Bin桶癣, 大于max
80%的都保留
4.實(shí)現(xiàn)了旋轉(zhuǎn)不變性,提高了匹配時(shí)候的穩(wěn)定性
5.大約有15%的關(guān)鍵點(diǎn)會(huì)有多個(gè)方向

  • 關(guān)鍵點(diǎn)描述子

1.擬合多項(xiàng)式插值尋找最大Peak
2.得到描述子 = 448 = 128

相關(guān)API

cv::xfeature2d::SIFT::create(
      int nfeatures = 0,
      int nOctaveLayers = 3,
      double contrastThreshold = 0.04,
      double 

代碼演示

        
    //SIFT特征檢測(cè)
    int numFeatures = 100;
    Ptr<SIFT> detector = SIFT::create(numFeatures);
    vector<KeyPoint> keypoints;
    detector->detect(src, keypoints, Mat());
    printf("Total KeyPoints: %d", keypoints.size());

    //繪制特征點(diǎn)
    Mat keypoint_img;
    drawKeypoints(src, keypoints, keypoint_img, Scalar::all(-1), DrawMatchesFlags::DEFAULT);
    namedWindow(output_title, CV_WINDOW_AUTOSIZE);
    imshow(output_title, keypoint_img);

HOG特征檢測(cè)

HOG特征描述子提取

  • 灰度圖像轉(zhuǎn)換

1.cvtColor
2.gray = R * 0.3 + 0.59 * G + 0.11 * B

  • 梯度計(jì)算
  1. 垂直方向


  2. 水平方向


  3. 梯度


  • 分網(wǎng)格的梯度方向直方圖

1.分割為8*8=64像素的Cell網(wǎng)格
2.對(duì)每個(gè)Cell求取方向直方圖(Orientation Hist)
每個(gè)Cell分為9個(gè)Bin筐乳,角度取值范圍為-180 ~ 180之間隘马, 對(duì)-180 ~ 0之間的加上180取正數(shù),對(duì)應(yīng)的值為梯度值扫步。方向?yàn)锽in數(shù)組0~9的Index

  • 塊描述子

塊描述子R(Rectange)-HOG
將2x2的網(wǎng)格單元組合成為一個(gè)大的塊(Block)
對(duì)每個(gè)塊之間有1/2部分是重疊區(qū)域
主要是將每個(gè)Cell的直方圖合并成為一個(gè)大的直方圖(Bin索引不變[0~9]之間)


  • 塊描述子歸一化

基于L2實(shí)現(xiàn)塊描述子歸一化魔策,歸一化因子計(jì)算


  • 特征數(shù)據(jù)與檢測(cè)窗口

1.最終獲得HOG描述算子(特征數(shù)據(jù))
2.需要正向訓(xùn)練200個(gè)左右的特征樣本
3.反向訓(xùn)練600 ~ 800個(gè)左右的特征樣本
4.初步測(cè)試、開窗檢測(cè)
舉例:
對(duì)于64x128的像素塊河胎,可以分為8x16個(gè)Cell分為7x15個(gè)塊(R-HOG)闯袒,總計(jì)的直方圖向量數(shù)為:7x15x2x2x9 = 12

  • 匹配方法

相關(guān)API

cv::HOGDescriptor::HOGDescriptor(
      Size winSize,
      Size blockSize,
      Size blockStride,
      Size cellSize,
      int nbins,
      ...//不常用的參數(shù)
      )
      

代碼演示

    Mat dst, dst_gray;
    resize(src, dst, Size(64, 128));
    cvtColor(dst, dst_gray, COLOR_BGR2GRAY);
    HOGDescriptor detector(Size(64, 128), Size(16, 16), Size(8, 8), Size(8, 8), 9);

    vector<float> descriptors;
    vector<Point> locations;
    detector.compute(dst_gray, descriptors, Size(0, 0), Size(0, 0), locations);
    printf("numbers of HOG descriptors: %d", descriptors.size());
    HOGDescriptor hog = HOGDescriptor();
    hog.setSVMDetector(hog.getDefaultPeopleDetector());

    vector<Rect> foundLocations;
    hog.detectMultiScale(src, foundLocations, 0, Size(8, 8), Size(32, 32), 1.05, 2);
    Mat result = src.clone();
    for (size_t t = 0; t < foundLocations.size(); t++)
    {
        rectangle(result, foundLocations[t], Scalar(0, 0, 255), 2, 8, 0);
    }
    namedWindow(output_title, CV_WINDOW_AUTOSIZE);
    imshow(output_title, result);

LBP(Local Binary Patterns)特征檢測(cè)

LBP特征介紹

LBP顧名思義,局部二值化游岳,也就是通過中心像素作為閾值政敢,進(jìn)行圖片的二值化。如圖所示胚迫,中心像素值為5喷户,比5小的全都賦為0,比5大的全都賦為1访锻。



由于直接利用的灰度比較褪尝,所以其具有灰度不變性;但是期犬,有兩個(gè)很明顯的缺點(diǎn):
1河哑、產(chǎn)生的二進(jìn)制模式多。
2龟虎、不具有旋轉(zhuǎn)不變性璃谨。

LBP表達(dá)

Pattern(模式):1001 1110
LBP: 1 + 2 + 4 + 8 + 16 + 32 +64 + 128 = 241, 不同位置像素權(quán)重之和
C(中心對(duì)比度): (6 + 7 + 8 + 9 + 7) / 5 - (5 + 2 + 1) / 3 = 4.7


LBP擴(kuò)展與多尺度表達(dá)

LBP統(tǒng)一模式

0->1或1->0的總跳數(shù)小于等于2則為統(tǒng)一模式


代碼演示

    cvtColor(src, gray_src, COLOR_BGR2GRAY);
    int width = gray_src.cols;
    int height = gray_src.rows;

    Mat lbpImage = Mat::zeros(gray_src.rows - 2, gray_src.cols - 2, CV_8UC1);
    for (int row = 1; row < height - 1; row++)
    {
        for (int col = 1; col < width - 1; col++)
        {
            uchar c = gray_src.at<uchar>(row, col);
            uchar code = 0;
            code |= (gray_src.at<uchar>(row - 1, col - 1) > c) << 7;
            code |= (gray_src.at<uchar>(row - 1, col) > c) << 6;
            code |= (gray_src.at<uchar>(row - 1, col + 1) > c) << 5;
            code |= (gray_src.at<uchar>(row, col + 1) > c) << 4;
            code |= (gray_src.at<uchar>(row + 1, col + 1) > c) << 3;
            code |= (gray_src.at<uchar>(row + 1, col) > c) << 2;
            code |= (gray_src.at<uchar>(row + 1, col - 1) > c) << 1;
            code |= (gray_src.at<uchar>(row, col - 1) > c) << 0;
            lbpImage.at<uchar>(row - 1, col - 1) = code;
        }
    }
void ELBP_Demo(int, void*)
{
    int offset = current_radius * 2;
    Mat elbpImage = Mat::zeros(gray_src.rows - offset, gray_src.cols - offset, CV_8UC1);
    int width = gray_src.cols;
    int height = gray_src.rows;

    int numNeighbors = 8;
    for (int n = 0; n < numNeighbors; n++)
    {
        float x = static_cast<float>(current_radius) * cos(2.0 * CV_PI * n / static_cast<float>(numNeighbors));
        float y = static_cast<float>(current_radius) * -sin(2.0 * CV_PI * n / static_cast<float>(numNeighbors));

        int fx = static_cast<int>(floor(x));
        int fy = static_cast<int>(floor(y));
        int cx = static_cast<int>(ceil(x));
        int cy = static_cast<int>(ceil(y));

        float ty = y - fy;
        float tx = x - fx;

        float w1 = (1 - tx) * (1 - ty);
        float w2 = tx * (1 - ty);
        float w3 = (1 - tx) * ty;
        float w4 = tx * ty;

        for (int row = current_radius; row < (height - current_radius); row++)
        {
            for (int col = current_radius; col < (width - current_radius); col++)
            {
                float t = w1 * gray_src.at<uchar>(row + fy, col + fx) + w2 * gray_src.at<uchar>(row + fy, col + cx) + 
                    w3 * gray_src.at<uchar>(row + cy, col + fx) + w2 * gray_src.at<uchar>(row + cy, col + cx);
                elbpImage.at<uchar>(row - current_radius, col - current_radius) +=
                    ((t > gray_src.at<uchar>(row, col)) &&
                     ((abs(t - gray_src.at<uchar>(row, col))) > std::numeric_limits<float>::epsilon())) << n;
            }
        }

    }
    imshow("ELBP Result", elbpImage);
}

積分圖計(jì)算

積分圖像計(jì)算介紹


積分圖上任意一點(diǎn)像素點(diǎn)的大小都是其左上方向像素點(diǎn)的值之和遣总。



通過這一公式可以通過積分圖計(jì)算出任意一點(diǎn)的像素值

API說明

void cv::integral(
      InputArray src,
      OutputArray sum,
      OutputArray sqsum,
      OutputArray tilted,
      int sdepth = -1,
      int sqdepth = -1
      )

代碼演示

    Mat sumii = Mat::zeros(src.rows + 1, src.cols + 1, CV_32FC1);
    Mat sqsumii = Mat::zeros(src.rows + 1, src.cols + 1, CV_64FC1);
    integral(src, sumii, sqsumii);

    Mat iiResult;
    normalize(sumii, iiResult, 0, 255, NORM_MINMAX, CV_8UC1, Mat());
    imshow("Integral Image", iiResult);

Haar特征

Haar特征介紹(Haar Like Features)

  • 高類間變異性
  • 低類內(nèi)變異性
  • 局部強(qiáng)度差(可以理解成對(duì)比度)
  • 不同尺度
  • 計(jì)算效率高

特征描述子

特征描述子與匹配

  1. 什么是圖像特征描述子

通過detector提取keypoint睬罗,然后對(duì)keypoint根據(jù)不同的算法獲取周圍窗口的采樣點(diǎn)轨功,再歸一化,就形成了描述子容达。

  1. 描述子與匹配

通過匹配算法在圖像中找到描述子古涧,如下圖所示暴力匹配Broute-Froce匹配


代碼演示

    int minHessian = 400;
    Ptr<SURF> detector = SURF::create(minHessian);
    vector<KeyPoint> keypoint_1;
    vector<KeyPoint> keypoint_2;

    Mat descriptor_1, descriptor_2;
    detector->detectAndCompute(src1, Mat(), keypoint_1, descriptor_1);
    detector->detectAndCompute(src2, Mat(), keypoint_2, descriptor_2);

    BFMatcher matcher;
    vector<DMatch> matches;
    matcher.match(descriptor_1, descriptor_2, matches);

    Mat matchesImg;
    drawMatches(src1, keypoint_1, src2, keypoint_2, matches, matchesImg);
    imshow("Descriptor Demo", matchesImg);

FLANN特征匹配

FLANN特征匹配介紹
這是一種快速匹配的算法,快速最近鄰逼近搜索函數(shù)庫(Fast Approximate Nearest Neighbor Seach Library)

代碼展示

    //FLAAN快速匹配
    int minHessian = 400;
    Ptr<SURF> detector = SURF::create(minHessian);
    vector<KeyPoint> keypoint_obj;
    vector<KeyPoint> keypoint_scene;

    Mat descriptor_obj, descriptor_scene;
    detector->detectAndCompute(src1, Mat(), keypoint_obj, descriptor_obj);
    detector->detectAndCompute(src2, Mat(), keypoint_scene, descriptor_scene);

    FlannBasedMatcher matcher;
    vector<DMatch> matches;
    matcher.match(descriptor_obj, descriptor_scene, matches);

    double minDist = 1000;
    double maxDist = 0;
    for (int i = 0; i < descriptor_obj.rows; i++)
    {
        double dist = matches[i].distance;
        if (dist > maxDist)
        {
            maxDist = dist;
        }
        if (dist < minDist)
        {
            minDist = dist;
        }
    }
    printf("max distance : %f\n", maxDist);
    printf("min distance : %f\n", minDist);

    vector<DMatch> goodMatches;
    for (int i = 0; i < descriptor_obj.rows; i++)
    {
        double dist = matches[i].distance;
        if (dist < max(50 * minDist, 0.02))
        {
            goodMatches.push_back(matches[i]);
        }

    }

    Mat matchesImg;
    drawMatches(src1, keypoint_obj, src2, keypoint_scene, goodMatches, matchesImg, Scalar::all(-1), 
        Scalar::all(-1), vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);
    imshow("Flann Matching Result", matchesImg);

平面對(duì)象識(shí)別

對(duì)象形變與位置變換

  1. findHomography:發(fā)現(xiàn)兩個(gè)平面的透視變換花盐, 生成變換矩陣
  2. perspectiveTransform:透視變換

代碼示例

    //透視變換
    vector<Point2f> obj;
    vector<Point2f> objInScene;
    for (size_t t = 0; t < goodMatches.size(); t++)
    {
        obj.push_back(keypoint_obj[goodMatches[t].queryIdx].pt);
        objInScene.push_back(keypoint_scene[goodMatches[t].trainIdx].pt);
    }
    Mat H = findHomography(obj, objInScene, RANSAC);

    vector<Point2f> obj_corners(4);
    vector<Point2f> scene_corners(4);
    obj_corners[0] = Point(0, 0);
    obj_corners[1] = Point(src1.cols, 0);
    obj_corners[2] = Point(src1.cols, src1.rows);
    obj_corners[3] = Point(0, src1.rows);

    perspectiveTransform(obj_corners, scene_corners, H);

    line(matchesImg, scene_corners[0] + Point2f(src1.cols, 0), 
        scene_corners[1] + Point2f(src1.cols, 0), Scalar(0, 0, 255), 2, 8, 0);
    line(matchesImg, scene_corners[1] + Point2f(src1.cols, 0),
        scene_corners[2] + Point2f(src1.cols, 0), Scalar(0, 0, 255), 2, 8, 0);
    line(matchesImg, scene_corners[2] + Point2f(src1.cols, 0), 
        scene_corners[3] + Point2f(src1.cols, 0), Scalar(0, 0, 255), 2, 8, 0);
    line(matchesImg, scene_corners[3] + Point2f(src1.cols, 0),
        scene_corners[0] + Point2f(src1.cols, 0), Scalar(0, 0, 255), 2, 8, 0);

    line(src2, scene_corners[0], scene_corners[1], Scalar(0, 0, 255), 2, 8, 0);
    line(src2, scene_corners[1], scene_corners[2], Scalar(0, 0, 255), 2, 8, 0);
    line(src2, scene_corners[2], scene_corners[3], Scalar(0, 0, 255), 2, 8, 0);
    line(src2, scene_corners[3], scene_corners[0], Scalar(0, 0, 255), 2, 8, 0);

    imshow("Find Known Object Demo", matchesImg);
    imshow("Draw Object", src2);

AKAZE局部匹配

AKAZE局部匹配介紹

  • AOS構(gòu)造尺度空間
  • Hessian矩陣特征點(diǎn)檢測(cè)
  • 方向指定基于一階微分圖像
  • 描述子生成

與SIFT羡滑、SURF比較

  • 更加穩(wěn)定
  • 非線性尺度空間
  • AKAZE速度更加快
  • 比較新的算法,只有OpenCV新版本才可以用

這里要注意算芯,在使用AKAZE檢測(cè)下面這張圖的時(shí)候柒昏,我發(fā)現(xiàn)找不到KeyPoint,而其他KAZE,SURF,SIFT相比之下都有更好的效果熙揍,但是AKAZE速度確實(shí)要比其他算法快很多职祷,在沒有研究源碼的前提下,我猜測(cè)AKAZE可能是犧牲了檢測(cè)的準(zhǔn)確性届囚,提高了計(jì)算速度


代碼演示

    //AKAZE Detection
    Ptr<AKAZE> detector = AKAZE::create();
    vector<KeyPoint> keypoints;
    double t1 = getTickCount();
    detector->detect(src, keypoints, Mat());
    double t2 = getTickCount();
    double tkaze = 1000 * (t2 - t1) / getTickFrequency();
    printf("KAZE Time Consume(ms): %f", tkaze);

    Mat keypointImg;
    drawKeypoints(src, keypoints, keypointImg, Scalar::all(-1), DrawMatchesFlags::DEFAULT);
    imshow("kaze key points", keypointImg);
    //AKAZE Detection
    Ptr<AKAZE> detector = AKAZE::create();
    vector<KeyPoint> keypoint_obj;
    vector<KeyPoint> keypoint_scene;
    Mat descriptor_obj, descriptor_scene;
    double t1 = getTickCount();
    detector->detectAndCompute(src1, Mat(), keypoint_obj, descriptor_obj);
    detector->detectAndCompute(src2, Mat(), keypoint_scene, descriptor_scene);
    double t2 = getTickCount();
    double tkaze = 1000 * (t2 - t1) / getTickFrequency();
    printf("KAZE Time Consume(ms): %f", tkaze);

    //Matching
    //BFMatcher matcher(NORM_L2);
    FlannBasedMatcher matcher(new flann::LshIndexParams(20, 10, 2));
    vector<DMatch> matches;
    matcher.match(descriptor_obj, descriptor_scene, matches);

    //Draw KeyPoints
    Mat akazeMatchesImg;
    drawKeypoints(src1, keypoint_obj, src1, Scalar::all(-1), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);
    drawMatches(src1, keypoint_obj, src2, keypoint_scene, matches, akazeMatchesImg,
        Scalar::all(-1), Scalar::all(-1), vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);
    imshow("src1", src1);
    imshow("Akaze Match Results", akazeMatchesImg);

AKAZE描述子生成圖像是基于漢明距離的有梆,F(xiàn)lann對(duì)CV_8UC1這種類型不支持,所以當(dāng)適應(yīng)AKAZE+Flann意系,要以以下方式使用Flann

FlannBasedMatcher matcher(new flann::LshIndexParams(20, 10, 2));

BRISK特征檢測(cè)與匹配

BRISK(Binary Robust Invariant Scalable KeyPoints)特征介紹

  • 構(gòu)建尺度空間
  • 特征點(diǎn)檢測(cè)
  • FAST9-16尋找特征點(diǎn)
  • 特征點(diǎn)定位
  • 關(guān)鍵點(diǎn)描述子

代碼演示

    //BRISKDetection
    Ptr<Feature2D> detector = BRISK::create();
    vector<KeyPoint> keypoints;
    double t1 = getTickCount();
    detector->detect(src, keypoints, Mat());
    double t2 = getTickCount();
    double tkaze = 1000 * (t2 - t1) / getTickFrequency();
    printf("KAZE Time Consume(ms): %f", tkaze);

    Mat keypointImg;
    drawKeypoints(src, keypoints, keypointImg, Scalar::all(-1), DrawMatchesFlags::DEFAULT);
    imshow("kaze key points", keypointImg);
    //BRISK Detection
    Ptr<Feature2D> detector = BRISK::create();
    vector<KeyPoint> keypoint_obj;
    vector<KeyPoint> keypoint_scene;
    Mat descriptor_obj, descriptor_scene;
    double t1 = getTickCount();
    detector->detectAndCompute(src1, Mat(), keypoint_obj, descriptor_obj);
    detector->detectAndCompute(src2, Mat(), keypoint_scene, descriptor_scene);
    double t2 = getTickCount();
    double tkaze = 1000 * (t2 - t1) / getTickFrequency();
    printf("KAZE Time Consume(ms): %f", tkaze);

    //Matching
    //BFMatcher matcher(NORM_L2);
    //FlannBasedMatcher matcher(new flann::LshIndexParams(20, 10, 2));
    FlannBasedMatcher matcher;
    vector<DMatch> matches;
    matcher.match(descriptor_obj, descriptor_scene, matches);

    //Draw KeyPoints
    Mat briskMatchesImg;
    drawKeypoints(src1, keypoint_obj, src1, Scalar::all(-1), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);
    drawMatches(src1, keypoint_obj, src2, keypoint_scene, matches, briskMatchesImg,
        Scalar::all(-1), Scalar::all(-1), vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);
    imshow("src1", src1);
    imshow("BRISK Match Results", briskMatchesImg);

BRISK和AKAZE存在著相似的問題泥耀,在檢測(cè)下面這張圖時(shí),檢測(cè)不到KeyPoints蛔添。


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末痰催,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子迎瞧,更是在濱河造成了極大的恐慌夸溶,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,884評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件夹攒,死亡現(xiàn)場(chǎng)離奇詭異蜘醋,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)咏尝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,347評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門压语,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人编检,你說我怎么就攤上這事胎食。” “怎么了允懂?”我有些...
    開封第一講書人閱讀 157,435評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵厕怜,是天一觀的道長。 經(jīng)常有香客問我,道長粥航,這世上最難降的妖魔是什么琅捏? 我笑而不...
    開封第一講書人閱讀 56,509評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮递雀,結(jié)果婚禮上柄延,老公的妹妹穿的比我還像新娘。我一直安慰自己缀程,他們只是感情好搜吧,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,611評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著杨凑,像睡著了一般滤奈。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上撩满,一...
    開封第一講書人閱讀 49,837評(píng)論 1 290
  • 那天蜒程,我揣著相機(jī)與錄音,去河邊找鬼伺帘。 笑死搞糕,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的曼追。 我是一名探鬼主播,決...
    沈念sama閱讀 38,987評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼汉规,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼礼殊!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起针史,我...
    開封第一講書人閱讀 37,730評(píng)論 0 267
  • 序言:老撾萬榮一對(duì)情侶失蹤晶伦,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后啄枕,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體婚陪,經(jīng)...
    沈念sama閱讀 44,194評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,525評(píng)論 2 327
  • 正文 我和宋清朗相戀三年频祝,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了泌参。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,664評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡常空,死狀恐怖沽一,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情漓糙,我是刑警寧澤铣缠,帶...
    沈念sama閱讀 34,334評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響蝗蛙,放射性物質(zhì)發(fā)生泄漏蝇庭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,944評(píng)論 3 313
  • 文/蒙蒙 一捡硅、第九天 我趴在偏房一處隱蔽的房頂上張望哮内。 院中可真熱鬧,春花似錦病曾、人聲如沸牍蜂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,764評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鲫竞。三九已至,卻和暖如春逼蒙,著一層夾襖步出監(jiān)牢的瞬間从绘,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,997評(píng)論 1 266
  • 我被黑心中介騙來泰國打工是牢, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留僵井,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,389評(píng)論 2 360
  • 正文 我出身青樓驳棱,卻偏偏與公主長得像批什,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子社搅,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,554評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容