OpenCV 之ios 離散傅立葉變換

1.目標(biāo)

本文檔嘗試解答如下問(wèn)題:

2源碼

#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 "DisccreteTransfromViewController.h"

@interface DisccreteTransfromViewController ()

@end

@implementation DisccreteTransfromViewController

- (void)viewDidLoad {
    [super viewDidLoad];
//    UIImage * src1Image = [UIImage imageNamed:@"lena.jpg"];
    UIImage * src1Image = [UIImage imageNamed:@"imageTextR.png"];

    
     Mat source = [self cvMatFromUIImage:src1Image];
    Mat I;
    cvtColor(source, I, COLOR_BGRA2GRAY);;
    UIImageView *imageView;
    imageView = [self createImageViewInRect:CGRectMake(0, 100, 150, 150)];
      [self.view addSubview:imageView];
    imageView.image  = [self UIImageFromCVMat:I];
    
    Mat padded;                            //expand input image to optimal size
       int m = getOptimalDFTSize( I.rows );
       int n = getOptimalDFTSize( I.cols );
    copyMakeBorder(I, padded, 0, m - I.rows, 0, n - I.cols, BORDER_CONSTANT, Scalar::all(0));
    imageView = [self createImageViewInRect:CGRectMake(0, 250, 150, 150)];
         [self.view addSubview:imageView];
       imageView.image  = [self UIImageFromCVMat:padded];
    
    //! [complex_and_real]
        Mat planes[] = {Mat_<float>(padded), Mat::zeros(padded.size(), CV_32F)};
        Mat complexI;
        merge(planes, 2, complexI);         // Add to the expanded another plane with zeros
    //! [complex_and_real]

    //! [dft]
        dft(complexI, complexI);            // this way the result may fit in the source matrix
    //! [dft]

        // compute the magnitude and switch to logarithmic scale
        // => log(1 + sqrt(Re(DFT(I))^2 + Im(DFT(I))^2))
    //! [magnitude]
        split(complexI, planes);                   // planes[0] = Re(DFT(I), planes[1] = Im(DFT(I))
        magnitude(planes[0], planes[1], planes[0]);// planes[0] = magnitude
        Mat magI = planes[0];
    //! [magnitude]
    
    //! [log]
        magI += Scalar::all(1);                    // switch to logarithmic scale
        log(magI, magI);
    //! [log]
    
    //! [crop_rearrange]
    // crop the spectrum, if it has an odd number of rows or columns
    magI = magI(cv::Rect(0, 0, magI.cols & -2, magI.rows & -2));

    // rearrange the quadrants of Fourier image  so that the origin is at the image center
       int cx = magI.cols/2;
       int cy = magI.rows/2;

    Mat q0(magI, cv::Rect(0, 0, cx, cy));   // Top-Left - Create a ROI per quadrant
    Mat q1(magI, cv::Rect(cx, 0, cx, cy));  // Top-Right
    Mat q2(magI, cv::Rect(0, cy, cx, cy));  // Bottom-Left
    Mat q3(magI, cv::Rect(cx, cy, cx, cy)); // Bottom-Right

       Mat tmp;                           // swap quadrants (Top-Left with Bottom-Right)
       q0.copyTo(tmp);
       q3.copyTo(q0);
       tmp.copyTo(q3);

       q1.copyTo(tmp);                    // swap quadrant (Top-Right with Bottom-Left)
       q2.copyTo(q1);
       tmp.copyTo(q2);
    
    //! [crop_rearrange]
    //! [normalize]
    normalize(magI, magI, 0, 1, NORM_MINMAX); // Transform the matrix with float values into a
                                                // viewable image form (float between values 0 and 1).

    imageView = [self createImageViewInRect:CGRectMake(0, 400, 150, 150)];
            [self.view addSubview:imageView];
          imageView.image  = [self UIImageFromCVMat:magI];
}
#pragma mark  - private
//brgx
- (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;
    cvtColor(cvMat, dst, COLOR_RGBA2BGRA);

  return dst;
}

-(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

3.原理

對(duì)一張圖像使用傅立葉變換就是將它分解成正弦和余弦兩部分。也就是將圖像從空間域(spatial domain)轉(zhuǎn)換到頻域(frequency domain)然评。 這一轉(zhuǎn)換的理論基礎(chǔ)來(lái)自于以下事實(shí):任一函數(shù)都可以表示成無(wú)數(shù)個(gè)正弦和余弦函數(shù)的和的形式。傅立葉變換就是一個(gè)用來(lái)將函數(shù)分解的工具瞧柔。 2維圖像的傅立葉變換可以用以下數(shù)學(xué)公式表達(dá):

式中 f 是空間域(spatial domain)值吼渡,F 則是頻域(frequency domain)值股耽。 轉(zhuǎn)換之后的頻域值是復(fù)數(shù), 因此寺酪,顯示傅立葉變換之后的結(jié)果需要使用實(shí)數(shù)圖像(real image) 加虛數(shù)圖像(complex image), 或者幅度圖像(magitude image)加相位圖像(phase image)梅尤。 在實(shí)際的圖像處理過(guò)程中,僅僅使用了幅度圖像赔硫,因?yàn)榉葓D像包含了原圖像的幾乎所有我們需要的幾何信息炒俱。 然而,如果你想通過(guò)修改幅度圖像或者相位圖像的方法來(lái)間接修改原空間圖像爪膊,你需要使用逆傅立葉變換得到修改后的空間圖像权悟,這樣你就必須同時(shí)保留幅度圖像和相位圖像了。

在此示例中推盛,我將展示如何計(jì)算以及顯示傅立葉變換后的幅度圖像峦阁。由于數(shù)字圖像的離散性,像素值的取值范圍也是有限的耘成。比如在一張灰度圖像中榔昔,像素灰度值一般在0到255之間。 因此瘪菌,我們這里討論的也僅僅是離散傅立葉變換(DFT)撒会。 如果你需要得到圖像中的幾何結(jié)構(gòu)信息,那你就要用到它了师妙。請(qǐng)參考以下步驟(假設(shè)輸入圖像為單通道的灰度圖像 I):

1.將圖像延擴(kuò)到最佳尺寸

離散傅立葉變換的運(yùn)行速度與圖片的尺寸息息相關(guān)诵肛。當(dāng)圖像的尺寸是2, 3默穴,5的整數(shù)倍時(shí)曾掂,計(jì)算速度最快惫谤。 因此,為了達(dá)到快速計(jì)算的目的珠洗,經(jīng)常通過(guò)添湊新的邊緣像素的方法獲取最佳圖像尺寸溜歪。函數(shù)getOptimalDFTSize() 返回最佳尺寸,而函數(shù) copyMakeBorder() 填充邊緣像素:

Mat padded;                            //將輸入圖像延擴(kuò)到最佳的尺寸
int m = getOptimalDFTSize( I.rows );
int n = getOptimalDFTSize( I.cols ); // 在邊緣添加0
copyMakeBorder(I, padded, 0, m - I.rows, 0, n - I.cols, BORDER_CONSTANT, Scalar::all(0));

添加的像素初始化為0.

2.為傅立葉變換的結(jié)果(實(shí)部和虛部)分配存儲(chǔ)空間

傅立葉變換的結(jié)果是復(fù)數(shù)许蓖,這就是說(shuō)對(duì)于每個(gè)原圖像值蝴猪,結(jié)果是兩個(gè)圖像值。 此外膊爪,頻域值范圍遠(yuǎn)遠(yuǎn)超過(guò)空間值范圍自阱, 因此至少要將頻域儲(chǔ)存在 float 格式中。 結(jié)果我們將輸入圖像轉(zhuǎn)換成浮點(diǎn)類型米酬,并多加一個(gè)額外通道來(lái)儲(chǔ)存復(fù)數(shù)部分

Mat planes[] = {Mat_<float>(padded), Mat::zeros(padded.size(), CV_32F)};
Mat complexI;
merge(planes, 2, complexI);         // 為延擴(kuò)后的圖像增添一個(gè)初始化為0的通道

3.進(jìn)行離散傅立葉變換. 支持圖像原地計(jì)算 (輸入輸出為同一圖像):

dft(complexI, complexI);            // 變換結(jié)果很好的保存在原始矩陣中

4.將復(fù)數(shù)轉(zhuǎn)換為幅度

復(fù)數(shù)包含實(shí)數(shù)部分(Re)和復(fù)數(shù)部分 (imaginary - Im)沛豌。 離散傅立葉變換的結(jié)果是復(fù)數(shù),對(duì)應(yīng)的幅度可以表示為:


轉(zhuǎn)化為OpenCV代碼:

split(complexI, planes);                   // planes[0] = Re(DFT(I), planes[1] = Im(DFT(I))
magnitude(planes[0], planes[1], planes[0]);// planes[0] = magnitude
Mat magI = planes[0];

5.對(duì)數(shù)尺度(logarithmic scale)縮放

傅立葉變換的幅度值范圍大到不適合在屏幕上顯示赃额。高值在屏幕上顯示為白點(diǎn)加派,而低值為黑點(diǎn),高低值的變化無(wú)法有效分辨跳芳。為了在屏幕上凸顯出高低變化的連續(xù)性芍锦,我們可以用對(duì)數(shù)尺度來(lái)替換線性尺度:


這里加1 是為了防止對(duì)m取對(duì)數(shù)值是復(fù)數(shù). 對(duì)小于1的數(shù)去對(duì)數(shù)是負(fù)值.

轉(zhuǎn)化為OpenCV代碼:

magI += Scalar::all(1);                    // 轉(zhuǎn)換到對(duì)數(shù)尺度
log(magI, magI);

6.剪切和重分布幅度圖象限

還記得我們?cè)诘谝徊綍r(shí)延擴(kuò)了圖像嗎? 那現(xiàn)在是時(shí)候?qū)⑿绿砑拥南袼靥蕹恕榱朔奖泔@示飞盆,我們也可以重新分布幅度圖象限位置(注:將第五步得到的幅度圖從中間劃開(kāi)得到四張1/4子圖像娄琉,將每張子圖像看成幅度圖的一個(gè)象限,重新分布即將四個(gè)角點(diǎn)重疊到圖片中心)吓歇。 這樣的話原點(diǎn)(0,0)就位移到圖像中心孽水。

magI = magI(Rect(0, 0, magI.cols & -2, magI.rows & -2));
int cx = magI.cols/2;
int cy = magI.rows/2;

Mat q0(magI, Rect(0, 0, cx, cy));   // Top-Left - 為每一個(gè)象限創(chuàng)建ROI
Mat q1(magI, Rect(cx, 0, cx, cy));  // Top-Right
Mat q2(magI, Rect(0, cy, cx, cy));  // Bottom-Left
Mat q3(magI, Rect(cx, cy, cx, cy)); // Bottom-Right

Mat tmp;                           // 交換象限 (Top-Left with Bottom-Right)
q0.copyTo(tmp);
q3.copyTo(q0);
tmp.copyTo(q3);

q1.copyTo(tmp);                    // 交換象限 (Top-Right with Bottom-Left)
q2.copyTo(q1);
tmp.copyTo(q2);

7.歸一化

這一步的目的仍然是為了顯示。 現(xiàn)在我們有了重分布后的幅度圖城看,但是幅度值仍然超過(guò)可顯示范圍[0,1] 匈棘。我們使用 normalize() 函數(shù)將幅度歸一化到可顯示范圍。

normalize(magI, magI, 0, 1, CV_MINMAX); // 將float類型的矩陣轉(zhuǎn)換到可顯示圖像范圍
                                        // (float [0析命, 1]).

結(jié)果

image.png

上面的知識(shí)還是很不好理解的,因此需要多看看才行,我找了一篇講解傅里葉變換的比較好的文章供大家參考

傅里葉變換
傅里葉變換


github 地址

摘錄博客

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末主卫,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子鹃愤,更是在濱河造成了極大的恐慌簇搅,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,383評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件软吐,死亡現(xiàn)場(chǎng)離奇詭異瘩将,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)姿现,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)肠仪,“玉大人,你說(shuō)我怎么就攤上這事备典∫炀桑” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,852評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵提佣,是天一觀的道長(zhǎng)吮蛹。 經(jīng)常有香客問(wèn)我,道長(zhǎng)拌屏,這世上最難降的妖魔是什么潮针? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,621評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮倚喂,結(jié)果婚禮上每篷,老公的妹妹穿的比我還像新娘。我一直安慰自己端圈,他們只是感情好焦读,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著枫笛,像睡著了一般。 火紅的嫁衣襯著肌膚如雪刚照。 梳的紋絲不亂的頭發(fā)上刑巧,一...
    開(kāi)封第一講書(shū)人閱讀 49,929評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音无畔,去河邊找鬼啊楚。 笑死,一個(gè)胖子當(dāng)著我的面吹牛浑彰,可吹牛的內(nèi)容都是我干的恭理。 我是一名探鬼主播,決...
    沈念sama閱讀 39,076評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼郭变,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼颜价!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起诉濒,我...
    開(kāi)封第一講書(shū)人閱讀 37,803評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤周伦,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后未荒,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體专挪,經(jīng)...
    沈念sama閱讀 44,265評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了寨腔。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片速侈。...
    茶點(diǎn)故事閱讀 38,716評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖迫卢,靈堂內(nèi)的尸體忽然破棺而出倚搬,到底是詐尸還是另有隱情,我是刑警寧澤靖避,帶...
    沈念sama閱讀 34,395評(píng)論 4 333
  • 正文 年R本政府宣布潭枣,位于F島的核電站,受9級(jí)特大地震影響幻捏,放射性物質(zhì)發(fā)生泄漏盆犁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評(píng)論 3 316
  • 文/蒙蒙 一篡九、第九天 我趴在偏房一處隱蔽的房頂上張望谐岁。 院中可真熱鬧,春花似錦榛臼、人聲如沸伊佃。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,798評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)航揉。三九已至,卻和暖如春金刁,著一層夾襖步出監(jiān)牢的瞬間帅涂,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,027評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工尤蛮, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留媳友,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,488評(píng)論 2 361
  • 正文 我出身青樓产捞,卻偏偏與公主長(zhǎng)得像醇锚,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子坯临,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評(píng)論 2 350

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

  • 一焊唬、傅立葉變換的由來(lái) 關(guān)于傅立葉變換,無(wú)論是書(shū)本還是在網(wǎng)上可以很容易找到關(guān)于傅立葉變換的描述看靠,但是大都是些故弄玄虛...
    constant007閱讀 4,416評(píng)論 1 10
  • 離散傅里葉變換(DFT) 定義 離散傅里葉變換(Discrete Fourier Transform求晶,縮寫(xiě)為DFT...
    亞歐沙龍閱讀 2,442評(píng)論 0 1
  • 一直生活在親人朋友中間,于是習(xí)慣了她()們噓寒問(wèn)暖衷笋,習(xí)慣了被提醒芳杏,習(xí)慣了被保護(hù)矩屁,所以只要感覺(jué)到委屈一點(diǎn)就會(huì)把情...
    鄒戒戒閱讀 249評(píng)論 0 3
  • 我有機(jī)會(huì)體驗(yàn)了人類情緒最深處的地方,并且在那兒久久徘徊不能離開(kāi)爵赵,思考的越多吝秕,離那種最初的快樂(lè)就越來(lái)越遠(yuǎn),這是必然的...
    名字不長(zhǎng)只有九個(gè)字閱讀 203評(píng)論 0 0