C++ 11がそのまま動くiphone

iphoneアプリを作るにあたってopencvでカメラの画像をキャプチャするものを作ってみた。
opencvはテンプレートなどをバリバリ使うC++のライブラリであるが、xcodeで普通に混在できるようだった。
…ということはC++11のコードとかそのまま使えるのかなーと思ってやってみたら案外普通にいけた。


http://codezine.jp/article/detail/6639C++11のthreadを使った素数を数えるプログラムが乗っていたのでそれを簡単に移植してみた。
数万までの整数について素数であるかどうか調べるというもの。
普通に調べるだけならエラトステネスのふるいを使ったほうが断然早いのだが、そこはまあ何秒かかるのかわかりやすくするためにそうなっているんだと思う。


コードはこれ。

//ViewController.mm
//
// ViewControllerにボタンを一つ追加し、クリックした時のデリゲートを適当に設置しておく。

// include じゃなくて import
 #import "vector"
#import "thread"

//それぞれのスレッドにどこからどこまでを計算させるかを示すためのテンプレートクラス
template<typename T=int>
class div_range{
private:
    T lo_;
    T hi_;
    T stride_;
    T div_;
public:
    div_range( int lo, int hi, int div)
    : lo_(lo), hi_(hi), div_(div) { stride_ = (hi-lo)/div; }
    T lo(int n) const { return lo_ + stride_ * n; }
    T hi(int n) const { return ( ++n < div_ ) ? lo_ + stride_ * n : hi_; }
};

typedef std::tuple<int,int,int> thread_io;

//その整数が素数であるかどうかを調べる。
bool is_prime(int n)
{
    for( int i = 2; i < n; i++)
    {
        if( n % i == 0)
        {
            return false;
        }
    }
    return true;
}

int count_prime( const unsigned int lo, const unsigned int hi)
{
    int count = 0;
    for( int i = lo; i < hi; i++)
    {
        if( is_prime(i) ){
            count++;
        }
    }
    return count;
}

//シングルスレッドの場合 (参考まで)
void single(int M)
{
    std::chrono::system_clock::time_point start = std::chrono::system_clock::now();
    int number_of_prime = count_prime( 2, M);
    std::chrono::duration<double> sec = std::chrono::system_clock::now() - start;
    
    NSString *title = @"result";
    NSString *msg = [NSString stringWithFormat:@"%d %f [sec]", number_of_prime, sec.count()];
    
    UIAlertView *alert =
	[[UIAlertView alloc]
     initWithTitle:title
     message:msg
     delegate:Nil
     cancelButtonTitle:@"OK"
     otherButtonTitles:nil];
    [alert show];
}

//マルチスレッド
void multi(int M, int number_of_thread)
{
    std::vector<std::thread> thread( number_of_thread);
    std::vector<int> count( number_of_thread);
    div_range<> range( 2, M, number_of_thread);
    
    auto start = std::chrono::system_clock::now();
    for( int i = 0; i < number_of_thread; i++){
        thread[i] = std::thread( [&,i]( int lo, int hi){ count[i] = count_prime(lo,hi);}, range.lo(i), range.hi(i) );
    }
    int result = 0;
    for( int i = 0; i < number_of_thread; i++){
        thread[i].join();
        result += count[i];
    }
    std::chrono::duration<double> sec = std::chrono::system_clock::now() - start;
    NSString *title = @"result";
    NSString *msg = [NSString stringWithFormat:@"%d %f [sec]", number_of_thread, sec.count()];
    
    //結果を表示するためにとりあえずメッセージボックスを使う。(重なって表示されるがまあいいや)
    UIAlertView *alert =
	[[UIAlertView alloc]
     initWithTitle:title
     message:msg
     delegate:Nil
     cancelButtonTitle:@"OK"
     otherButtonTitles:nil];
    [alert show];

}


//ボタンが押されたら呼び出されるデリゲート
- (IBAction)showMap:(id)sender {
    const int M = 50000;
    for( int i = 1; i < 6; i++){ multi(M, i); }
}


結果は以下のとおりとなった。

手持ちのiMac 2011で普通にC++としてコンパイルして実行。(10万まで。最適化なし)

かかった秒数 スレッド数
1.89211[sec] 1
1.39071[sec] 2
1.07219[sec] 3
0.858417[sec] 4
0.743213[sec] 5
0.713778[sec] 6
0.67413[sec] 7
0.666978[sec] 8
0.642282[sec] 9


手持ちのiphone4Sで実行。(5万まで。最適化なし)
5.148979[sec] 1
3.889552[sec] 2
3.213674[sec] 3
2.985299[sec] 4
2.854148[sec] 5

と、コア数が増えれば増えるほど早くなっているのがよく分かる。
iphoneは4スレッドで、iMacは8スレッドで頭打ちになるはずだが、いずれも微妙に早くなっていたりする。

iphoneopencvをカメラでキャプチャする

ライブラリのビルドはなぜか毎回失敗する(ファイルが何か足りなかった)ので、大人しくiphoneようパッケージを落としてきて2.4.3だったか何かを設定する。
ボタンを押した時にカメラを起動するためのデリゲート takePictureを適当に書いて、画像を撮り終わったらそれをopencvの画像形式に変換する。
そして、適当に処理をしたらまたiOSの画像形式に変換して表示させる。
…がとりあえず拾ってきたソースをつなげただけなのでイマイチフォーマットやらなんやらがわからない。


カメラを起動して撮影するときはUIImagePickerControllerDelegateを使うらしい。
ボタンを押す → デリゲート(takePicture)
→UIImagePickerController(撮影モード) → 撮影完了 → UIImagePickerController
→UIImageをopencvの形式であるcv::matに変換する。 → opencvで好きなだけいじる → UIImageに変換してviewかなんかに表示させる。

//ViewController.mm

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    worldView.delegate = self;
    [worldView setShowsUserLocation:YES];
}

/*
 参考: http://www.patokeefe.com/archives/721
 */
- (UIImage *)UIImageFromMat:(cv::Mat)image {
    NSData *data = [NSData dataWithBytes:image.data length:image.elemSize()*image.total()];
	
    CGColorSpaceRef colorSpace;
	
    if (image.elemSize() == 1) {
        colorSpace = CGColorSpaceCreateDeviceGray();
    } else {
        colorSpace = CGColorSpaceCreateDeviceRGB();
    }
	
    CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data);
	
    // Creating CGImage from cv::Mat
    CGImageRef imageRef = CGImageCreate(image.cols, image.rows,
                                        8, 8 * image.elemSize(),
                                        image.step.p[0], colorSpace,
										kCGImageAlphaNone|kCGBitmapByteOrderDefault,
                                        provider, NULL, false,
                                        kCGRenderingIntentDefault);
	
    // Getting UIImage from CGImage
    UIImage *finalImage = [UIImage imageWithCGImage:imageRef];
    CGImageRelease(imageRef);
    CGDataProviderRelease(provider);
    CGColorSpaceRelease(colorSpace);
	
    return finalImage;
}

/*
 * UIImageからCvMatに変換する。
 */
- (cv::Mat)CvMatFromUIImage:(UIImage *)image {
    CGColorSpaceRef colorSpace = CGImageGetColorSpace(image.CGImage);
    CGFloat cols = image.size.width;
    CGFloat rows = image.size.height;
    
    cv::Mat cvMat(rows, cols, CV_8UC4); // 8 bits per component, 4 channels
    
    CGContextRef contextRef = CGBitmapContextCreate(cvMat.data,                 // Pointer to backing data
                                                    cols,                      // Width of bitmap
                                                    rows,                     // Height of bitmap
                                                    8,                          // Bits per component
                                                    cvMat.step[0],              // Bytes per row
                                                    colorSpace,                 // Colorspace
                                                    kCGImageAlphaNoneSkipLast |
                                                    kCGBitmapByteOrderDefault); // Bitmap info flags
    
    CGContextDrawImage(contextRef, CGRectMake(0, 0, cols, rows), image.CGImage);
    CGContextRelease(contextRef);
    
    return cvMat;
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}


//ボタンが押されたら呼ぶ。 ここでカメラを起動して画像を取ってくる。
- (void)takePicture:(id)sender{
    
    UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init];
    //カメラをサポートしているかどうか
    if([UIImagePickerController isCameraDeviceAvailable:UIImagePickerControllerCameraDeviceFront] ){
        [imagePicker setSourceType:UIImagePickerControllerSourceTypeCamera];
    }else{
        [imagePicker setSourceType:UIImagePickerControllerSourceTypePhotoLibrary];
    }
    
    [imagePicker setDelegate:self];
    
    //画像ピッカーを表示する。
    [self presentModalViewController:imagePicker animated:YES];
    
}

//写真を撮り終わったら呼び出される関数
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *) info{
    UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage];

    // おまじない
    UIGraphicsBeginImageContext(image.size);
    [image drawInRect:CGRectMake(0, 0, image.size.width, image.size.height)];
    image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    
    // cv::Matを生成
	cv::Mat srcImg = [self CvMatFromUIImage:image];
    cv::resize(srcImg, srcImg, cv::Size(400,640));
    
	// ここで画像を直接いじれる。ただし写真のフォーマットがどうなっているのかまだ調べていない。
	for( int y = 0; y < srcImg.rows; y++ ) {
		cv::Vec3b* ptr = srcImg.ptr<cv::Vec3b>( y );
		for( int x = 0; x < srcImg.cols; x++ ) {
			cv::Vec3b bgr = ptr[x];
			//ptr[x] = cv::Vec3b( 0, 128, 255);
                       ptr[x] = cv::Vec3b(1.0 * x / srcImg.cols * 255, 0, 0);
		}
	}
	
	// UIImageViewに表示する
	UIImage *newImage = [self UIImageFromMat:srcImg];
    [photoImage setImage:newImage];
	
    NSLog(@"tes");
    [self dismissViewControllerAnimated:YES completion:Nil];
}

//カメラがキャンセルされた時に呼び出される関数
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker{
    NSLog(@"camera cancel");
    [btnGps setTitle:@"GPS??" forState:UIControlStateHighlighted];
    
    [self dismissViewControllerAnimated:YES completion:Nil];
}
@end

画面遷移の仕方がいまいちよくわからない。