はじめに
今更 IplImage
かよ、という方、スミマセン。
この項で提示したいのは、OpenCVでのイメージの扱いを Objective-Cでラップすると便利だよ、ということなのです。cv::Mat
でも一向に構いませんので、その辺りは便利な風に読み替えていただければ幸いです。
ちなみに、OpenCV 3.0系統ではいわゆる、IplImage
的な Cインタフェースが今後メンテされないことが決まっているそうです。cv::Mat
を使うように心がけましょうw
便利なポイント
Mac で OpenCV 関連のプログラミングを行うにあたって、1つの方法としては OpenCVの世界にドップリ浸かって、ウィンドウも画像表示も、何から何までオープンソースの世界で済ます、というのがあると思います。
ただ、動作環境ターゲットを Macに絞った場合、やはり Cocoaが使いたくなる。さらに言えば、C++ boostの shared_ptrとかも使わず、Objective-Cの ARCでいいじゃない、という気分にもなってきます。
さらに、Xcodeには Debug Quick Look という強力な機能が備わっていて、Objective-Cで Wrapすればその機能も活用できます。これは、デバッガ上から何時でもcv::imshow()
を実行できるとイメージすると良いかも。
Swiftの存在
開発言語として Swiftも出てきましたね。
原則、ポインタアクセスなどを許さない Swiftですが、OpenCVを使おうとすると否が応でも Objective-Cでのラップが必要になってきます。
メイン部分や UI系統はあくまでも Swiftで組みつつ、内部のイメージ処理部分などを Objective-Cでラップされた OpenCVなど利用すれば、作りもエレガントなものになるのかなと思われますね。
では、ラップしてみる
まずは宣言。
@interface OImage : NSObject
@property (nonatomic, assign, readonly) IplImage *iplImage;
- (id)initWithIplImage:(IplImage *)iplImage;
- (id)initWithCvSize:(CvSize)size depth:(int)depth channels:(int)channels;
- (id)initWithBytes:(void *)bytes size:(CvSize)size bytesPerRow:(int)bytesPerRow channels:(int)channels;
- (NSImage *)NSImage;
そして実装です。(NULLチェック等はざっくり省略しております...)
-(CGImageRef)createCGImage
メソッドと、-(id)debugQuickLookObject
メソッドの組合せが、Xcodeの Debug Quick Look 機能に載る部分になります。
@implementation OImage
- (id)initWithIplImage:(IplImage *)iplImage {
if ((self = [super init]) == nil)
return nil;
_iplImage = cvCloneImage(iplImage);
return self;
}
- (id)initWithCvSize:(CvSize)size depth:(int)depth channels:(int)channels {
if ((self = [super init]) == nil)
return nil;
_iplImage = cvCreateImage(size, depth, channels);
return self;
}
- (id)initWithBytes:(void *)bytes size:(CvSize)size bytesPerRow:(int)bytesPerRow channels:(int)channels {
if ((self = [super init]) == nil)
return nil;
_iplImage = cvCreateImage(size, depth, channels);
memcpy(_iplImage->imageData, bytes, bytesPerRow * size.height);
return self;
}
- (CGImageRef)createCGImage {
CGBitmapInfo bitmapInfo = (CGBitmapInfo)kCGImageAlphaNone;
IplImage *iplImage = self.iplImage;
int componentBytes = 1;
NSColorSpace *cs = nil;
if (iplImage->nChannels == 1) {
colorspace = [NSColorSpace genericGrayColorSpace];
if (iplImage->depth == IPL_DEPTH_16U) {
bitmapInfo |= kCGBitmapByteOrder16Host;
componentBytes = sizeof(short);
} else if (iplImage->depth == IPL_DEPTH_32F) {
bitmapInfo |= kCGBitmapByteOrder32Host | kCGBitmapFloatComponents;
componentBytes = sizeof(float);
} else if (iplImage->depth == IPL_DEPTH_32S) {
bitmapInfo |= kCGBitmapByteOrder32Host;
componentBytes = sizeof(int);
}
} else {
colorspace = [NSColorSpace genericRGBColorSpace];
bitmapInfo = kCGBitmapByteOrder32Big | kCGImageAlphaPremultipleLast;
if (iplImage->nChannels == 3) {
iplImage = cvCreateImage(cvGetSize(iplImage), IPL_DEPTH_8U, 4);
cvCvtColor(self.iplImage, iplImage, CV_RGB2RGBA);
}
}
CGContextRef context = CGBitmapContextCreate(NULL, iplImage->width, iplImage->height, componentBytes * 8, iplImage->widthStep, cs.CGColorSpace, bitmapInfo);
void *data = CGBitmapContextGetData(context);
memcpy(data, iplImage->imageData, iplImage->imageSize);
CGImageRef cgimage = CGBitmapContextCreateImage(context);
CGContextRelease(context);
if (iplImage != self.iplImage)
cvReleaseImage(&iplImage);
return cgimage;
}
- (NSImage *)NSImage {
CGImageRef cgimage = [self createCGImage];
if (cgimage == NULL)
return nil;
NSImage *nsimage = [[NSImage alloc] initWithCGImage:cgimage size:NSZeroSize];
CGImageRelease(cgimage);
return nsimage;
}
- (id)debugQuickLookObject {
return [self NSImage];
}
- (void)dealloc {
if (_iplImage != NULL)
cvReleaseImage(&_iplImage);
}
あとは、せっかく作った Wrapperクラスですので、grayscaleとか、HSV変換とか、cvFindContours()
辺りの呼出しとか、たくさん細々とメソッドを追加していけば良いかと思います。