話題の記事

【今更】漫画風ビデオカメラをつくる 〜OpenCVを利用したリアルタイムフィルタリング その1〜

2013.04.08

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

CM iOS部隊所属の平井です。今回は前回の記事「OpenCVを利用したリアルタイムフィルタリングの基本」で作成したOpenCVを利用したリアルタイムフィルタリングを行うためのプロジェクトと、結構前の記事「OpenCVで写真を漫画風に加工しよう 〜実装編〜」で作成した漫画風フィルタを組み合わせて漫画風ビデオカメラを作ってみましょう!

では早速。

ちなみに今回は以下の環境を前提に説明します。

Mac OS X 10.8 Moutain lion
Xcode 4.6.1
iOS SDK 6.1
iPhone 5

サンプルプロジェクトのダウンロード

今回紹介するiOSアプリのソースコードをGitHubにあげてあるのでダウンロードしてください。
hirai-yuki/RealTimeManga

実機につないでプロジェクトを実行すると、以下のように動画が漫画風になるはず。

漫画風ビデオ

今は表示をいい具合にするため、1秒間に8回程度処理するようにしてますが、意外とさくさく動くもんですね。

漫画風フィルタークラス

OpenCVを利用したリアルタイムフィルタリングの基本から変わっているのは、MonochromeFilerクラスがMangaFilterになったのと、ViewControllerのviewの背景をスクリーントーン画像を配置しただけ。

MangaFilter.h
#import <Foundation/Foundation.h>

/**
 画像を漫画風に加工する
 */
@interface MangaFilter : NSObject

/**
 フィルター処理
 
 @param     image      フィルター前の画像
 @return    フィルター後の画像
 */
+ (UIImage *)doFilter:(UIImage *)image;

@end
MangaFilter.mm
#import "MangaFilter.h"
#import "OpenCVUtil.h"

@implementation MangaFilter

+ (UIImage *)doFilter:(UIImage *)image
{
    UIImage *lineImage = [MangaFilter lineFilter:image];
    UIImage *monochromeImage = [MangaFilter monochromeFilter:image];
    
    CGRect imageRect = CGRectMake(0.0f, 0.0f, monochromeImage.size.width, monochromeImage.size.height);
    
    // オフスクリーン描画のためのグラフィックスコンテキストを用意
    UIGraphicsBeginImageContext(monochromeImage.size);
    
    // 白黒部分の画像をコンテキストに描画
    [monochromeImage drawInRect:imageRect];
    
    // 輪郭画像をコンテキストに描画
    [lineImage drawInRect:imageRect];
    
    // 4-4.合成画像をコンテキストから取得
    UIImage *margedImage = UIGraphicsGetImageFromCurrentImageContext();
    
    // 4-5.オフスクリーン描画を終了
    UIGraphicsEndImageContext();
    
    return margedImage;
}

+ (UIImage *)lineFilter:(UIImage *)image
{
    // CGImageからIplImageを作成
    IplImage *srcImage       = [OpenCVUtil IplImageFromUIImage:image];
    
    IplImage *grayscaleImage = cvCreateImage(cvGetSize(srcImage), IPL_DEPTH_8U, 1);
    IplImage *edgeImage      = cvCreateImage(cvGetSize(srcImage), IPL_DEPTH_8U, 1);
    IplImage *dstImage       = cvCreateImage(cvGetSize(srcImage), IPL_DEPTH_8U, 3);
    
    // グレースケール画像に変換
    cvCvtColor(srcImage, grayscaleImage, CV_BGR2GRAY);
    
    // グレースケール画像を平滑化
    cvSmooth(grayscaleImage, grayscaleImage, CV_GAUSSIAN, 3, 0, 0);
    
    // エッジ検出画像を作成
    cvCanny(grayscaleImage, edgeImage, 20, 120);
    
    // エッジ検出画像色を反転
    cvNot(edgeImage, edgeImage);
    
    // CGImage用にBGRに変換
    cvCvtColor(edgeImage, dstImage, CV_GRAY2BGR);
    
    // IplImageからCGImageを作成
    UIImage *effectedImage = [OpenCVUtil UIImageFromIplImage:dstImage];
    
    cvReleaseImage(&srcImage);
    cvReleaseImage(&grayscaleImage);
    cvReleaseImage(&edgeImage);
    cvReleaseImage(&dstImage);
    
    // 白色の部分を透過する
    const float colorMasking[6] = {255, 255, 255, 255, 255, 255};
    CGImageRef imageRef = CGImageCreateWithMaskingColors(effectedImage.CGImage, colorMasking);
    UIImage *lineImage = [UIImage imageWithCGImage:imageRef];
    CGImageRelease(imageRef);
    
    return lineImage;
}

+ (UIImage *)monochromeFilter:(UIImage *)image
{
    // CGImageからIplImageを作成
    IplImage *srcImage       = [OpenCVUtil IplImageFromUIImage:image];
    IplImage *grayScaleImage = cvCreateImage(cvGetSize(srcImage), IPL_DEPTH_8U, 1);
    IplImage *dstImage       = cvCreateImage(cvGetSize(srcImage), IPL_DEPTH_8U, 3);
    
    // グレースケール画像に変換
    cvCvtColor(srcImage, grayScaleImage, CV_BGR2GRAY);
    
    // グレースケール画像を1画素ずつ走査して3値化する
    for(int y = 0; y < grayScaleImage->height; y++) {
        for(int x = 0; x < grayScaleImage->width; x++) {
            int a = grayScaleImage->widthStep * y + x;
            uchar p = grayScaleImage->imageData[a];
            
            if (p < 70) {
                // 70より小さい場合、黒
                grayScaleImage->imageData[a] = 0;
            } else if (70 <= p && p < 120) {
                // 70以上、120未満の場合、灰色
                grayScaleImage->imageData[a] = 100;
            } else {
                // 120以上の場合、白
                grayScaleImage->imageData[a] = 255;
            }
        }
    }
    
    // CGImage用にBGRに変換
    cvCvtColor(grayScaleImage, dstImage, CV_GRAY2BGR);
    
    // IplImageからCGImageを作成
    UIImage *effectedImage = [OpenCVUtil UIImageFromIplImage:dstImage];
    
    cvReleaseImage(&srcImage);
    cvReleaseImage(&grayScaleImage);
    cvReleaseImage(&dstImage);
    
    // 灰色の部分を透過する
    const float colorMasking[6] = {100, 100, 100, 100, 100, 100};
    CGImageRef imageRef = CGImageCreateWithMaskingColors(effectedImage.CGImage, colorMasking);
    UIImage *monochromeFilter = [UIImage imageWithCGImage:imageRef];
    CGImageRelease(imageRef);
    
    return monochromeFilter;
}

@end

まとめ

やっぱりカメラは面白いですね!誰かOpenCVの透過と画像の合成の簡単なやり方教えてくださいww

次回はタップすると歪むとかそんなやつやろうかと思います。