iPhoneでRGB<-->HSV変換
iPhoneで色認識をしてみようと思います。
人の目(というかそれを処理しているのは脳ですが)は意識しなくても
- 色の彩度
- 色の明度
を認識できます。
例えば下の図に示す2つの四角の色は、
- 「濃い赤」「薄い赤」
とか
- 「暗い赤」「明るい赤」
と表現することができます。取りあえず「赤」は「赤」として読み取って欲しい訳です
でも、RGB空間では図に示すように(255,0,0),(193,0,0)と全く違う数値になってしまいます。*1
そこでHSV空間に変換します。HSVは色相(Hue)、彩度(Saturation・Chroma)、明度(Brightness・Lightness・Value)の三つの成分から成ります。今回知りたいのは色相のみです。
詳細はWikipedia::HSV色空間を参照して下さい。
数学的にはRGBの立方体色空間をHSVの円柱または円錐色空間への写像変換を行う事で出来ます。
OpenCVでは
cvCvtColor (srcImage, hsv, CV_BGR2HSV);
とやって変換しますが、
今回はベタにC言語っぽく記述しました。RGBをHSVに変換し、SとVを強制的に書き換えて、RGB空間に書き戻しています。
一応 iOS5+iPhone3GSとiPod touch 4Gでの実機動作を確認しています。
前回の1秒ごとに写真をキャプチャ…の部分に記述しています。#相変わらずUIGetScreenImage()を使っちゃってるけど
- (void)onTimer:(NSTimer*)timer { NSLog(@"timer1begin"); //画面キャプチャー開始 NSLog(@"画面キャプチャー開始"); //スクリーンキャプチャ CGImageRef imgRef = UIGetScreenImage(); //サイズ取得 size_t width = CGImageGetWidth(imgRef); size_t height = CGImageGetHeight(imgRef); size_t bitsPerComponent = CGImageGetBitsPerComponent(imgRef); size_t bitsPerPixel = CGImageGetBitsPerPixel(imgRef); size_t bytesPerRow = CGImageGetBytesPerRow(imgRef); CGColorSpaceRef colorSpace = CGImageGetColorSpace(imgRef); CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imgRef); bool shouldInterpolate = CGImageGetShouldInterpolate(imgRef); CGColorRenderingIntent renderingIntent = CGImageGetRenderingIntent(imgRef); CGDataProviderRef dataProvider = CGImageGetDataProvider(imgRef); CFDataRef data = CGDataProviderCopyData(dataProvider); UInt8 *buf = (UInt8*)CFDataGetBytePtr(data); double s,v; double h; double c; double cmax,cmin; NSMutableArray *hArray=[[NSMutableArray alloc]init]; for (int j = 0; j < height; j++) { for (int i = 0; i < width; i++) { UInt8 *pixel = buf + j * bytesPerRow + i * 4; UInt8 r = *(pixel + 2); UInt8 g = *(pixel + 1); UInt8 b = *(pixel + 0); //RGB->HSV変換時のR,G,Bは 0.0〜1.0 double dr = r / 255.0; double dg = g / 255.0; double db = b / 255.0; //hsvに変換 if (dr >= dg) cmax = dr; else cmax = dg; if (db >= cmax) cmax = db; if (dr <= dg) cmin = dr; else cmin = dg; if ( db <= cmin) cmin = db; v = cmax; c = cmax - cmin; if (cmax == 0) s = 0; else s = c/cmax; if (s != 0) { if (dr == cmax) { h = 60 * (dg - db)/c; } else if (dg == cmax) { h = 60 * (db - dr)/c + 120; } else if (db == cmax) { h = 60 * (dr - dg)/c + 240; } if (h < 0) h = h + 360; } //NSLog(@"%f %f %f",h,s,v); s = 0.5; v = 0.8; //ここで強制的にS,Vを指定している。 //rgbに再変換 int inn = (int)floor( h / 60 ); if(inn < 0) inn *= -1; double fl = ( h / 60 ) - inn; //if(!(inn & 1)) fl = 1 - fl; // if i is even double p = v * ( 1 - s ); double q = v * ( 1 - s * fl ); double t = v * (1 - (1 - fl) * s); ////計算結果のR,G,Bは0.0〜1.0なので255倍 v = v * 255; p = p * 255; q = q * 255; t = t * 255; switch( inn ) { case 0: r = v; g = t; b = p; break; case 1: r = q; g = v; b = p; break; case 2: r = p; g = v; b = q; break; case 3: r = p; g = q; b = v ; break; case 4: r = t; g = p; b = v ; break; case 5: r = v; g = p; b = q; break; } *(pixel + 2) = r; *(pixel + 1) = g; *(pixel + 0) = b; } } CFDataRef filterData = CFDataCreate(NULL, buf, CFDataGetLength(data)); CGDataProviderRef filterDataProvider = CGDataProviderCreateWithCFData(filterData); CGImageRef filterCgImage = CGImageCreate( width, height, bitsPerComponent, bitsPerPixel, bytesPerRow, colorSpace, bitmapInfo, filterDataProvider, NULL, shouldInterpolate, renderingIntent ); //CGImageRefをUIImageに変換 UIImage *img = [UIImage imageWithCGImage:filterCgImage]; //キャプチャーしたCGImageRef解放 CGImageRelease(imgRef); //画像を「写真」に保存 UIImageWriteToSavedPhotosAlbum(img, nil, nil, nil); }
NSMutableArrayオブジェクト使ってないじゃん…と言うのはちょっと勘弁してくださいw
色相データを今後配列にして
[hArray addObject: [NSNumber numberWithFloat: h]];
などと格納する予定です。
ちょっと彩度が弱いかも。。
また調整してみます。
■参考文献
- 橋本詳解::[Ubuntu][VMWare][OpenCV][C++]赤色検出
- OpenCVプログラミングブック 第2版( 奈良先端科学技術大学院大学 OpenCVプログラミングブック製作チーム)
*1:今は赤一色で考えているのでさほど問題では有りませんが