OpenCV 之ios 霍夫圓變換

OpenCV 之ios 霍夫圓變換

目標(biāo)

在這個(gè)教程中你將學(xué)習(xí)如何:

  • 使用OpenCV函數(shù) HoughCircles 在圖像中檢測圓.

原理

霍夫圓變換

  • 霍夫圓變換的基本原理和上個(gè)教程中提到的霍夫線變換類似, 只是點(diǎn)對應(yīng)的二維極徑極角空間被三維的圓心點(diǎn)x, y還有半徑r空間取代.

  • 對直線來說, 一條直線能由參數(shù)極徑極角 (r,θ)表示. 而對圓來說, 我們需要三個(gè)參數(shù)來表示一個(gè)圓, 如上文所說現(xiàn)在原圖像的邊緣圖像的任意點(diǎn)對應(yīng)的經(jīng)過這個(gè)點(diǎn)的所有可能圓是在三維空間有下面這三個(gè)參數(shù)來表示了,其對應(yīng)一條三維空間的曲線. 那么與二維的霍夫線變換同樣的道理, 對于多個(gè)邊緣點(diǎn)越多這些點(diǎn)對應(yīng)的三維空間曲線交于一點(diǎn)那么他們經(jīng)過的共同圓上的點(diǎn)就越多,類似的我們也就可以用同樣的閾值的方法來判斷一個(gè)圓是否被檢測到, 這就是標(biāo)準(zhǔn)霍夫圓變換的原理, 但也正是在三維空間的計(jì)算量大大增加的原因, 標(biāo)準(zhǔn)霍夫圓變化很難被應(yīng)用到實(shí)際中:


這里的(xcenter,ycenter)表示圓心的位置 (下圖中的綠點(diǎn)) 而r 表示半徑, 這樣我們就能唯一的定義一個(gè)圓了, 見下圖:

  • 出于上面提到的對運(yùn)算效率的考慮, OpenCV實(shí)現(xiàn)的是一個(gè)比標(biāo)準(zhǔn)霍夫圓變換更為靈活的檢測方法: 霍夫梯度法, 也叫2-1霍夫變換(21HT), 它的原理依據(jù)是圓心一定是在圓上的每個(gè)點(diǎn)的模向量上, 這些圓上點(diǎn)模向量的交點(diǎn)就是圓心, 霍夫梯度法的第一步就是找到這些圓心, 這樣三維的累加平面就又轉(zhuǎn)化為二維累加平面. 第二部根據(jù)所有候選中心的邊緣非0像素對其的支持程度來確定半徑. 21HT方法最早在Illingworth的論文The Adaptive Hough Transform中提出并詳細(xì)描述, 也可參照Yuen在1990年發(fā)表的A Comparative Study of Hough Transform Methods for Circle Finding, Bradski的《學(xué)習(xí)OpenCV》一書則對OpenCV中具體對算法的具體實(shí)現(xiàn)有詳細(xì)描述并討論了霍夫梯度法的局限性.

例程

這個(gè)例程是用來干嘛的?

  • 加載一幅圖像并對其模糊化以降噪
  • 對模糊化后的圖像執(zhí)行霍夫圓變換 .
  • 在窗體中顯示檢測到的圓.
#ifdef __cplusplus
#import <opencv2/opencv.hpp>
#import <opencv2/imgcodecs/ios.h>
#import <opencv2/imgproc.hpp>
#import <opencv2/highgui.hpp>
#import <opencv2/core/operations.hpp>

#import <opencv2/core/core_c.h>
using namespace cv;
using namespace std;

#endif
#import "HoughCirclesViewController.h"

@interface HoughCirclesViewController ()

@end

@implementation HoughCirclesViewController

const int cannyThresholdInitialValue = 100;
const int accumulatorThresholdInitialValue = 50;
const int maxAccumulatorThreshold = 200;
const int maxCannyThreshold = 255;
int cannyThreshold = cannyThresholdInitialValue;
int accumulatorThreshold = accumulatorThresholdInitialValue;

 Mat src, src_gray;
- (void)viewDidLoad {
    [super viewDidLoad];

    UIImage * src1Image = [UIImage imageNamed:@"stuff.jpg"];
     src  = [self cvMatFromUIImage:src1Image];
    UIImageView *imageView;
    imageView = [self createImageViewInRect:CGRectMake(0, 100, 150, 150)];
    [self.view addSubview:imageView];
    imageView.image  = [self UIImageFromCVMat:src];
   cvtColor( src, src_gray, COLOR_BGR2GRAY );
    imageView = [self createImageViewInRect:CGRectMake(0, 250, 150, 150)];
    [self.view addSubview:imageView];
    imageView.image  = [self UIImageFromCVMat:src_gray];
    GaussianBlur( src_gray, src_gray, cv::Size(9, 9), 2, 2 );

    imageView = [self createImageViewInRect:CGRectMake(0, 400, 150, 150)];
    [self.view addSubview:imageView];
    imageView.image  = [self UIImageFromCVMat:src_gray];
    
    [self createSliderFrame:CGRectMake(150, 100, 100, 50) maxValue:maxCannyThreshold minValue:0 block:^(float value) {
            cannyThreshold = std::max(cannyThreshold, 1);
            [self HoughDetection];
    }];
    
    [self createSliderFrame:CGRectMake(150, 150, 100, 50) maxValue:maxAccumulatorThreshold minValue:0 block:^(float value) {
          accumulatorThreshold = std::max(accumulatorThreshold, 1);
        [self HoughDetection];
       }];
  [self HoughDetection];
}

 -(void)HoughDetection
{
    std::vector<Vec3f> circles;
           // runs the actual detection
    HoughCircles( src_gray, circles, HOUGH_GRADIENT, 1, src_gray.rows/8, cannyThreshold, accumulatorThreshold, 0, 0 );

           // clone the colour, input image for displaying purposes
    Mat display = src.clone();
    for( size_t i = 0; i < circles.size(); i++ )
    {
        cv::Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));
        int radius = cvRound(circles[i][2]);
               // circle center
        circle( display, center, 3, Scalar(0,255,0), -1, 8, 0 );
               // circle outline
        circle( display, center, radius, Scalar(0,0,255), 3, 8, 0 );
    }
    
    UIImageView *imageView;
      imageView = [self createImageViewInRect:CGRectMake(150, 250, 150, 150)];
      [self.view addSubview:imageView];
      imageView.image  = [self UIImageFromCVMat:display];

}
#pragma mark  - private
//brg
- (cv::Mat)cvMatFromUIImage:(UIImage *)image
{
  CGColorSpaceRef colorSpace =CGColorSpaceCreateDeviceRGB();
    
  CGFloat cols = image.size.width;
  CGFloat rows = image.size.height;
    Mat cvMat(rows, cols, CV_8UC4); // 8 bits per component, 4 channels (color channels + alpha)
  CGContextRef contextRef = CGBitmapContextCreate(cvMat.data,                 // Pointer to  data
                                                 cols,                       // Width of bitmap
                                                 rows,                       // Height of bitmap
                                                 8,                          // Bits per component
                                                 cvMat.step[0],              // Bytes per row
                                                 colorSpace,                 // Colorspace
                                                 kCGImageAlphaNoneSkipLast |
                                                 kCGBitmapByteOrderDefault); // Bitmap info flags
  CGContextDrawImage(contextRef, CGRectMake(0, 0, cols, rows), image.CGImage);
  CGContextRelease(contextRef);
    
    Mat dst;
    Mat src;
    cvtColor(cvMat, dst, COLOR_RGBA2BGRA);
    cvtColor(dst, src, COLOR_BGRA2BGR);

  return src;
}

-(UIImage *)UIImageFromCVMat:(cv::Mat)cvMat
{
//    mat 是brg 而 rgb
    Mat src;
    NSData *data=nil;
    CGBitmapInfo info =kCGImageAlphaNone|kCGBitmapByteOrderDefault;
    CGColorSpaceRef colorSpace;
    if (cvMat.depth()!=CV_8U) {
        Mat result;
        cvMat.convertTo(result, CV_8U,255.0);
        cvMat = result;
    }
  if (cvMat.elemSize() == 1) {
      colorSpace = CGColorSpaceCreateDeviceGray();
      data= [NSData dataWithBytes:cvMat.data length:cvMat.elemSize()*cvMat.total()];
  } else if(cvMat.elemSize() == 3){
      cvtColor(cvMat, src, COLOR_BGR2RGB);
       data= [NSData dataWithBytes:src.data length:src.elemSize()*src.total()];
      colorSpace = CGColorSpaceCreateDeviceRGB();
  }else if(cvMat.elemSize() == 4){
      colorSpace = CGColorSpaceCreateDeviceRGB();
      cvtColor(cvMat, src, COLOR_BGRA2RGBA);
      data= [NSData dataWithBytes:src.data length:src.elemSize()*src.total()];
      info =kCGImageAlphaNoneSkipLast | kCGBitmapByteOrderDefault;
  }else{
      NSLog(@"[error:] 錯(cuò)誤的顏色通道");
      return nil;
  }
  CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data);
  // Creating CGImage from cv::Mat
  CGImageRef imageRef = CGImageCreate(cvMat.cols,                                 //width
                                     cvMat.rows,                                 //height
                                     8,                                          //bits per component
                                     8 * cvMat.elemSize(),                       //bits per pixel
                                     cvMat.step[0],                            //bytesPerRow
                                     colorSpace,                                 //colorspace
                                     kCGImageAlphaNone|kCGBitmapByteOrderDefault,// bitmap info
                                     provider,                                   //CGDataProviderRef
                                     NULL,                                       //decode
                                     false,                                      //should interpolate
                                     kCGRenderingIntentAbsoluteColorimetric                   //intent
                                     );
  // Getting UIImage from CGImage
  UIImage *finalImage = [UIImage imageWithCGImage:imageRef];
  CGImageRelease(imageRef);
  CGDataProviderRelease(provider);
  CGColorSpaceRelease(colorSpace);
  return finalImage;
 }
@end

解釋

  • 執(zhí)行霍夫圓變換:
 -(void)HoughDetection
{
    std::vector<Vec3f> circles;
           // runs the actual detection
    HoughCircles( src_gray, circles, HOUGH_GRADIENT, 1, src_gray.rows/8, cannyThreshold, accumulatorThreshold, 0, 0 );

           // clone the colour, input image for displaying purposes
    Mat display = src.clone();
    for( size_t i = 0; i < circles.size(); i++ )
    {
        cv::Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));
        int radius = cvRound(circles[i][2]);
               // circle center
        circle( display, center, 3, Scalar(0,255,0), -1, 8, 0 );
               // circle outline
        circle( display, center, radius, Scalar(0,0,255), 3, 8, 0 );
    }
    
    UIImageView *imageView;
      imageView = [self createImageViewInRect:CGRectMake(150, 250, 150, 150)];
      [self.view addSubview:imageView];
      imageView.image  = [self UIImageFromCVMat:display];

}

函數(shù)帶有以下自變量:

  • src_gray: 輸入圖像 (灰度圖)

  • circles: 存儲(chǔ)下面三個(gè)參數(shù):xc,:yc,r

    集合的容器來表示每個(gè)檢測到的圓.

  • CV_HOUGH_GRADIENT: 指定檢測方法. 現(xiàn)在OpenCV中只有霍夫梯度法

  • dp = 1: 累加器圖像的反比分辨率

  • min_dist = src_gray.rows/8: 檢測到圓心之間的最小距離

  • param_1 = 200: Canny邊緣函數(shù)的高閾值

  • param_2 = 100: 圓心檢測閾值.

  • min_radius = 0: 能檢測到的最小圓半徑, 默認(rèn)為0.

  • max_radius = 0: 能檢測到的最大圓半徑, 默認(rèn)為0

結(jié)果

使用圖片


smarties.png

cpp 代碼地址

cpp

github 地址

摘錄博客

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末二鳄,一起剝皮案震驚了整個(gè)濱河市刁笙,隨后出現(xiàn)的幾起案子叶眉,更是在濱河造成了極大的恐慌,老刑警劉巖一罩,帶你破解...
    沈念sama閱讀 222,183評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件今豆,死亡現(xiàn)場離奇詭異莉测,居然都是意外死亡颜骤,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門捣卤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來忍抽,“玉大人,你說我怎么就攤上這事董朝○睿” “怎么了?”我有些...
    開封第一講書人閱讀 168,766評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵益涧,是天一觀的道長锈锤。 經(jīng)常有香客問我,道長闲询,這世上最難降的妖魔是什么久免? 我笑而不...
    開封第一講書人閱讀 59,854評(píng)論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮扭弧,結(jié)果婚禮上阎姥,老公的妹妹穿的比我還像新娘。我一直安慰自己鸽捻,他們只是感情好呼巴,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評(píng)論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著御蒲,像睡著了一般衣赶。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上厚满,一...
    開封第一講書人閱讀 52,457評(píng)論 1 311
  • 那天府瞄,我揣著相機(jī)與錄音,去河邊找鬼碘箍。 笑死遵馆,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的丰榴。 我是一名探鬼主播货邓,決...
    沈念sama閱讀 40,999評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼四濒!你這毒婦竟也來了换况?” 一聲冷哼從身側(cè)響起职辨,我...
    開封第一講書人閱讀 39,914評(píng)論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎复隆,沒想到半個(gè)月后拨匆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體姆涩,經(jīng)...
    沈念sama閱讀 46,465評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡挽拂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了骨饿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片亏栈。...
    茶點(diǎn)故事閱讀 40,675評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖宏赘,靈堂內(nèi)的尸體忽然破棺而出绒北,到底是詐尸還是另有隱情,我是刑警寧澤察署,帶...
    沈念sama閱讀 36,354評(píng)論 5 351
  • 正文 年R本政府宣布闷游,位于F島的核電站,受9級(jí)特大地震影響贴汪,放射性物質(zhì)發(fā)生泄漏脐往。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評(píng)論 3 335
  • 文/蒙蒙 一扳埂、第九天 我趴在偏房一處隱蔽的房頂上張望业簿。 院中可真熱鬧,春花似錦阳懂、人聲如沸梅尤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽巷燥。三九已至,卻和暖如春号枕,著一層夾襖步出監(jiān)牢的瞬間缰揪,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評(píng)論 1 274
  • 我被黑心中介騙來泰國打工堕澄, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留邀跃,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,091評(píng)論 3 378
  • 正文 我出身青樓蛙紫,卻偏偏與公主長得像拍屑,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子坑傅,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評(píng)論 2 360

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