2012年2月11日土曜日

iPhoneで画像処理 - 1


今回はiOS上で画像処理を行います。
iPhoneやiPadにはカメラがついてたりして、いろんな画像処理を施したカメラアプリが登場しています。
今回はその入門編です。

大きく分けて
  • CoreImageを使ったフィルタリング
  • 画素単位へのアクセス

をやって行きたいと思います。



CoreImageを使ったフィルタリング
CoreImageとは,iOSやMacの画像クラスの一つです。CoreFilterと組み合わせて簡単なフィルタリングを行うことが出来ます.
UIImageとの違いは、UIImageはViewとして表示するのに適したクラスで、このCoreImageは画像の変換とか処理に特化している印象です。

ではまずはプロジェクトを作って行きましょう
テンプレートはSingleViewApplicationを選びます。次にライブラリを追加します。
左カラムよりプロジェクトを選択、その中の Linked Frameworks and Libraries の項から+マークをクリック、
CoreImage.frameworkをAddします。



つぎは、UIを編集します。
.xibファイルを選んで、UIImageViewを二つとスライダーを設置します。
UIImageViewには処理前後の画像を、スライダーはフィルタのパラメータ調整に使います。


.hファイルを選んで、クラスを作って行きます。
今回は、UIImageView二つに加え、CIContextとCIFilterの変数も追加します
メソッドはスライダーの変更をトリガーに呼ばれるメソッドを一つ作ります。

@interface SENSViewController : UIViewController{
 IBOutlet UIImageView *originalImage;
 IBOutlet UIImageView *filteredImage;
 CIContext *myCIContext;
 CIFilter *myCIFilter;
}

- (IBAction)sliderMoved:(id)sender;

それぞれのパーツをxibと接続します。
こんな感じに。
あと何か画像を追加しておいてください。できればカラーのものがいいと思います。
では、処理の追加に移りましょう
viewDidLoadを次のように変更します
- (void)viewDidLoad
{
    [super viewDidLoad];
 // Do any additional setup after loading the view, typically from a nib.
 
 
 originalImage.image = [UIImage imageNamed:@"dambo.jpg"];

 myCIContext = [CIContext contextWithOptions:nil];
 [myCIContext retain];

 myCIFilter = [CIFilter filterWithName:@"CIExposureAdjust"];
 [myCIFilter setDefaults];
 CIImage *srcImage = [CIImage imageWithCGImage:originalImage.image.CGImage];
 [myCIFilter setValue:srcImage forKey:@"inputImage"];
 [myCIFilter retain];

}

CIContextは処理画像をUIImageViewに貼付ける際に一度挟む中間処理的なものというイメージです。

CIFilterはフィルタリング処理を行ってくれるクラスです。フィルタの名前を指定して、初期化を行います。
今回は露光を擬似的に調整するフィルタ、CIExposureAdjustを用います

その後、フィルタのパラメータを与える必要があるんですが、
[myCIFilter setDefaults];
とすることで、デフォルトの値で初期化を行うことが出来ます。

さらに、その後で
[myCIFilter setValue:srcImage forKey:@"inputImage"];
では、入力パラメータとして処理対象の画像を指定しています。




retainについて
何度かメソッドが呼ばれています。これは、このインスタンスをまだ解放しないでねというメッセージを送っています。
これがなかった場合、viewDidLoadが終了するとmyCIContextやmyCIImageなどは解放されてしまいます。
では、いつまで解放されないかというと、releaseメソッドを呼ぶまで解放されません。
具体的には、Objective-Cのメモリ管理機構の話になるんで詳細は避けますが、インスタンスはそれぞれ、何個のインスタンスから
参照されているかというretainCounterというものを持っています。

  • retainをするとこのカウンタが+1され、
  • releaseをするとこのカウンタが-1されます。
カウンタが0となったときにオブジェクトは解放されます。



では、次はスライダーの値を変更したときに呼ばれるメソッドの実装を行います。

-(IBAction)sliderMoved:(id)sender{
[myCIFilter setValue:[NSNumber numberWithFloat:[(UISlider *)sender value]]
  forKey:@"inputEV"];
CIImage *dstImage = myCIFilter.outputImage;
CGImageRef myCGImage = [myCIContext createCGImage:dstImage
fromRect:dstImage.extent];
filteredImage.image = [UIImage imageWithCGImage:myCGImage];
CGImageRelease(myCGImage);
}


まず、最初にフィルタのパラメータの一つであるinputEVを設定しています。


NSNumber型は、整数型から浮動小数点型までをラップしてくれる便利なクラスです。今回は引数としてfloat型を与えるのでnumberWithFloatとしています。

フィルタ処理の結果は、フィルタのoutputImageというプロパティです。
多分このプロパティが呼ばれるときにフィルタリング処理を行っています。

その後、CIImage型からUIImage型までの変換を行っています。順番は

CIImage → CGImageRef → UIImage

という順番です。CGImage型はCoreGraphicsImageの略(多分)で、たしかハードウェアよりのなんかだったような…。

これを実行すると多分こんな感じになります。


どうしても一度CGImageRefをメモリ確保しているので、処理が遅くなってしまいますよね。
プロの方々はどうされてるんでしょうか…。






他のフィルタを使ってみる。

今回はフィルタとして露光量を擬似調整するCIExposureを使いましたが、他にはどういったフィルタがあるんでしょうか。
実装されているフィルタは以下のコードで確認することが出来ます。

 
 NSArray *options = [CIFilter filterNamesInCategory:kCICategoryBuiltIn];
 NSLog(@"options of filter");
 for (NSString *str in options) {
  CIFilter *filter = [CIFilter filterWithName:str];
  NSLog(@"%@", filter.attributes);
 }
名前だけ出力した結果が次のようになります。
  • CIAdditionCompositing
  • CIAffineTransform
  • CICheckerboardGenerator
  • CIColorBlendMode
  • CIColorBurnBlendMode
  • CIColorControls
  • CIColorCube
  • CIColorDodgeBlendMode
  • CIColorInvert
  • CIColorMatrix
  • CIColorMonochrome
  • CIConstantColorGenerator
  • CICrop
  • CIDarkenBlendMode
  • CIDifferenceBlendMode
  • CIExclusionBlendMode
  • CIExposureAdjust
  • CIFalseColor
  • CIGammaAdjust
  • CIGaussianGradient
  • CIHardLightBlendMode
  • CIHighlightShadowAdjust
  • CIHueAdjust
  • CIHueBlendMode
  • CILightenBlendMode
  • CILinearGradient
  • CILuminosityBlendMode
  • CIMaximumCompositing
  • CIMinimumCompositing
  • CIMultiplyBlendMode
  • CIMultiplyCompositing
  • CIOverlayBlendMode
  • CIRadialGradient
  • CISaturationBlendMode
  • CIScreenBlendMode
  • CISepiaTone
  • CISoftLightBlendMode
  • CISourceAtopCompositing
  • CISourceInCompositing
  • CISourceOutCompositing
  • CISourceOverCompositing
  • CIStraightenFilter
  • CIStripesGenerator
  • CITemperatureAndTint
  • CIToneCurve
  • CIVibrance
  • CIVignette
  • CIWhitePointAdjust

CIToneCurveは多分色調変換するフィルタですね。
CISepiaToneは色味をセピア調に変換してくれます。
他にも二つの色を合成するCIOverlayBlendModeなどがあります。

フィルタの詳細とそのパラメータはAppleのデベロッパー用サイトから確認できます。

例えばCISepiaToneは

CISepiaTone

Maps the colors of an image to various shades of brown.
Parameters
inputImage
A CIImage class whose display name is Image.
inputIntensity
An NSNumber class whose attribute type is CIAttributeTypeScalar and whose display name is Intensity.
Default value: 1.00 Minimum: 0.00 Maximum: 0.00 Slider minimum: 0.00 Slider maximum: 1.00 Identity: 0.00
Member of
CICategoryBuiltIn, CICategoryStillImage, CICategoryNonSquarePixels, CICategoryInterlaced, CICategoryVideo,CICategoryColorEffect
Localized Display Name
Sepia Tone
と書いてあります。
パラメータは二つで、
入力画像のinputImage、どれくらいセピア感を出すかというinputIntensityです。
inputImageはCIImage型
inputIntensityはNUNumber型と書いてあります。

この辺をどうやって入力するか、ですが、
inputImageは先ほどの例と同じで

[myCIFilter setValue:srcImage forKey:@"inputImage"];

とすればOK.
inputIntensityは先ほどの"inputEV" と同じように

[myCIFilter setValue:[NSNumber numberWithFloat:(何らかの値)]
  forKey:@"inputIntensity"];

で設定できます。何らかの値はスライダーの値を引っ張ってくるなり、予め与えておくなりで行けます。

0 件のコメント:

コメントを投稿