Lynx-EyEDの電音鍵盤 新館

制御とか数学とか駄文とか

iPhoneでRGB<-->HSV変換


iPhoneで色認識をしてみようと思います。
人の目(というかそれを処理しているのは脳ですが)は意識しなくても

  • 色の彩度
  • 色の明度

を認識できます。
例えば下の図に示す2つの四角の色は、

f:id:Lynx-EyED:20111024121227j:image

  • 「濃い赤」「薄い赤」

とか

  • 「暗い赤」「明るい赤」

と表現することができます。取りあえず「赤」は「赤」として読み取って欲しい訳です
でも、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]];

などと格納する予定です。

結果。
被写体はこんなの。
f:id:Lynx-EyED:20111024123948j:image

変換後
f:id:Lynx-EyED:20111024124515p:image

ちょっと彩度が弱いかも。。
また調整してみます。

■参考文献



*1:今は赤一色で考えているのでさほど問題では有りませんが