【Xcode】データをアプリ内に保存するためのメモ2(画像編)

【Xcode5.1.1 + iOS 7.1 + MacOX10.9.4】

前回データ(変数)の保存について書きましたが、今回は画像です。アイコン程度の小さな画像をアプリ内に保存できるようにしたい。

・UIImageJPEGRepresentationを使って、画像をJPEG化。
・[NSHomeDirectory() stringByAppendingPathComponent:@”Documents”]で、データ保存用を一般的に使われるDocumentsディレクトリを指定する。

というところがミソのようです。

で、書いてみたコードがコレ。ここではカメラ/カメラロールを編集モードで呼び出した後に、250*250のjpegに保存する処理を書いています。カメラの呼び出しなどは割愛してます。関数「resize」は、自前の関数であることががわかるように書いておきました。

YoheiM.NET [XCODE] iPhoneで画像をアプリケーション内に保存するには

-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
    //編集された画像を取得
    UIImage *editedImage = (UIImage *)[info objectForKey:UIImagePickerControllerEditedImage];
    
    // 画像をリサイズ
    UIImage *resize = [self resize:editedImage rect:CGRectMake(0, 0, 250, 250)];
    
    //リサイズした画像をDocumentsフォルダに保存
    NSData *data = UIImageJPEGRepresentation(resize, 1.0f);
    
    icon_count++;
    
    //画像をアプリ内部に保存
    NSString *path = [NSString stringWithFormat:@"%@/newicon%@.jpg",
                      [NSHomeDirectory() stringByAppendingPathComponent:@"Documents"],[NSString stringWithFormat:@"%d",icon_count]];
    
    //保存に成功・失敗したログをコンソールに出力(デバッグ用)
    if ([data writeToFile:path atomically:YES]) {
        NSLog(@"Save completed. %@",path);
        
        //保存した画像のパスを配列に追加
        [_array_icon addObject:[NSString stringWithFormat:@"../Documents/newicon%@.jpg",[NSString stringWithFormat:@"%d",icon_count]]];
        
        //アイコン画像配列:保存
        if ([_delegate respondsToSelector:@selector(saveData)]){
            [_delegate saveData];
        }
        
        //画像一覧を再描画
        [self.myCollectionView reloadData];
    } else {
        NSLog(@"could not save");
    }
    
    //---ここに保存後の処理を書く。
}

//画像をリサイズする
- (UIImage *)resize:(UIImage *)image rect:(CGRect)rect
{
    UIGraphicsBeginImageContext(rect.size);
    [image drawInRect:rect];
    UIImage* resizedImage = UIGraphicsGetImageFromCurrentImageContext();
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetInterpolationQuality(context, kCGInterpolationHigh);
    UIGraphicsEndImageContext();
    return resizedImage;
}

これで前回分と合わせると、保存した画像をアプリ起動時に呼び出せます。

【Xcode】データをアプリ内に保存するためのメモ1(変数編)

【Xcode5.1.1 + iOS 7.1 + MacOX10.9.4】

設定などのデータ(変数など)をアプリ内に保存するには、NSUserDefaultsを使うといいらしい。

iPhoneアプリ開発の虎の巻 NSUserDefaults
A Day In The Life NSUserDefaults を使ったデータの保存方法

で、load,saveの際に呼び出す関数を書いてみた。

//アイコン関連情報:読み込み
-(void)loadData
{
    defaults = [NSUserDefaults standardUserDefaults];

    //アイコン画像配列
    NSArray *array = [defaults arrayForKey:@"icon"];
    if (array) {
        //既存値を使用
        array_icon = [array mutableCopy];

    } else {
        //配列を新規に作成
        array_icon = [NSMutableArray arrayWithObjects:@"icon_250.jpg", @"miku_250.jpg", @"logo_2tones_250.jpg",@"shugo_250.jpg", nil];

    }

    //アイコン選択番号
    int num = (int)[defaults integerForKey:@"selectNum"];
    if (num) {
        //既存値を使用
        select_num = (long)num;
    } else {
        //初期値を設定
        select_num = 0;
    }

}

//アイコン関連情報:保存
-(void)saveData
{
    //配列:設定
    NSArray *array = [array_icon copy];
    [defaults setObject:array forKey:@"icon"];

    //番号:設定
    int num = (int)select_num;
    [defaults setInteger:num forKey:@"selectNum"];

    //更新
    BOOL successful = [defaults synchronize];
    if (successful) {
        NSLog(@"%@", @"データの保存に成功しました。");
    }
}

loadDataは、viewDidLoadで、saveDataはデータに変更があったタイミングで呼び出しています。

上のコードでの注意点

1.アプリではNSMutableArray(可変配列)を使っているのだけれど、NSUserDefaultsにはNSArray(固定配列)しか保存できないので、ロード/セーブ時に配列をキャストしてやる必要がある、ということです。

iPhoneアプリ開発メモ NSArrayはNSMutableArrayにキャスト可能か?

2.データ保存の際は、明示的に synchronize で更新しないと保存タイミングがOS任せになるらしいです。

iPhoneアプリ開発者の憂鬱 NSUserDefaultsの保存処理でハマった

3.実際にNSUserDefaultsはどこに保存されるのか?テスト等での確認に。

iPhoneアプリ開発者の為のAndroidアプリ開発のポイント(現忘備録) [iOS]NSUserDefaultsの保存先・実機のデータを見る方法

【Xcode】UICollectionViewを使う

UICollectionViewで、グリッド的なものが出来るらしいです。まだテスト中なんだけど、参照リンクが多すぎるのでメモ。
Collection Viewの使い方(iOS/XCode/Storyboard/UICollectionView)
[iPhone] UICollectionView で Grid 表示
iOS6から追加されたCollectionViewのサンプル
UICollectionView その1 セルの表示
Collection View プログラミングガイド(iOS用)【PDF】

【2014.7.18追記】画面遷移がうまくいかず、これを見てsegue.identifierが設定されていないことに気づいた。

Storyboard での画面遷移前はprepareForSegue メソッドが呼ばれる

【Xcode】StoryBoardの使い方

以前はInteface Builderとか使ってなかったので、イマイチわかってない気が。勿論、簡単なサンプルとかは問題ないけど、自分でUI考えたりすると混乱するので、関連リンクを集めておきます。随時追加する予定。

 
[iOS]Storyboardで始めるiPhoneアプリ開発 #1 – pushセグエを使う
Xcodeのストーリーボードの使い方
iOS Storyboardによる画面遷移の実装(Navigation Controller)

iOSの顔検出を画像で表示する(2)

以前書いた記事で、UIImage.imageOrientationがどんな状態でも、一度UIImageで書き直してしまえば問題ないと書いたんですが、やはりうまくいかないので、色々検索していたら見つけました。

参考:笑顔やまばたきも!iPhoneカメラでカンタン顔認識

featuresInImageで顔検出する際に、画像の向きを渡してやらないといけないらしい。optionは既に設定していたので見落としていたのですが・・・

 

    //オプション辞書作成(顔検出の精度/高精度)
    NSDictionary *options = [NSDictionary
                             dictionaryWithObject:CIDetectorAccuracyHigh
                             forKey:CIDetectorAccuracy];
    
    //CIDetectorを生成(画像解析/現在は顔検出のみ指定可能)
    CIDetector *faceDetector = [CIDetector
                                detectorOfType:CIDetectorTypeFace
                                context:_imageContext
                                options:options];

↑ここで指定するoptionは、検出の精度。

    //顔検出(位置情報を配列で返す)>画像向きのオプションを追加
    NSInteger orentation = [self exifOrientation:_originalImage];
    
    NSDictionary *imageOptions = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:orentation] forKey:CIDetectorImageOrientation];
    
    NSArray *faces = [faceDetector featuresInImage:coreImage
                                           options:imageOptions];

↑ここで指定するoptionは、検出に使用する画像の向き。

ということらしいです。

画像の向きの取得は、参考サイトに習って関数化(exifOrientation:_originalImage)してあります。

これでようやく、iPhoneのどの向きで撮影しても、ちゃんと顔検出が出来るようになりました。

よく見たら、前回の記事で参考にしたサイトにも記述があるのですが、特定の数値を指定していたりで、よくわかっていませんでした。

参考:
まみったー iPhone の向きと UIImageOrientation メモ
iOS 5のCIDetectorを使って顔認識してみた
笑顔やまばたきも!iPhoneカメラでカンタン顔認識

テスト環境:iPhone5+Xcode 5.1.1

UIImagwViewにAspect Fitで配置されたUIImageの位置・サイズを求める

UIImageViewでViewをAspect Fitに指定すると、比率を保ったままView内に画像を収めてくれます。こんな感じに。カメラロールで見るのと同じですね。

 

Aspect Fitでの画像表示

ただUIImageViewをクリックした時に、UIImageの座標を取得しようと思うと、基本的に縮小されているために計算が必要になります。その前段として、UIImageがUIImageView内でどのくらいのサイズで表示されているのかを取得する必要があります。まあ自前で計算できなくないですが、ちょっと面倒だし、そんなのメソッドがあるんじゃないかと探したら、見つかりました。

参考:UIImageView で ‘Aspect Fit (UIViewContentModeScaleAspectFit)’ を指定したときの画像サイズを取得する

    CGRect innerframe = AVMakeRectWithAspectRatioInsideRect(_originalImage.size, _myImageView.bounds);
 
    NSLog(@"--_originalImage.width %f",_originalImage.size.width);
    NSLog(@"--_originalImage.height %f",_originalImage.size.height);

    NSLog(@"--width %f",innerframe.size.width);
    NSLog(@"--height %f",innerframe.size.height);
    NSLog(@"--x %f",innerframe.origin.x);
    NSLog(@"--y %f",innerframe.origin.y);

※_originalImage:元画像(UIImage)
_myImageView:表示用View(UIImageView)

で、結果はこちら。

2014-07-06 02:15:32.946 faceCamera2[14996:60b] --_originalImage.width 2048.000000
2014-07-06 02:15:32.947 faceCamera2[14996:60b] --_originalImage.height 1536.000000
2014-07-06 02:15:32.948 faceCamera2[14996:60b] --width 320.000000
2014-07-06 02:15:32.950 faceCamera2[14996:60b] --height 240.000000
2014-07-06 02:15:32.951 faceCamera2[14996:60b] --x 0.000000
2014-07-06 02:15:32.953 faceCamera2[14996:60b] --y 142.000000

このメソッドは、AVFoundation.frameworkを使っているのでimportが必要です。しかし、UIImageViewにあってもよさそうなのに、何故、AVFoundationにあるのかは謎。おかげで見つけるのに時間かかりました。。。

iOSの顔検出を画像で表示する

要は、顔検出部分に画像を配置するだけなんですが、ちょっと引っかかったのでメモ。

参考:顔認識とCore Imageフィルタのコラボ

この手のサンプルはたくさんあるけど、大体がCIFilter使ってるパターンが多いので、その部分をUIImageで描画すればいいんじゃないかってメドをつけたんだけど、なかなかうまく行かなかった。大まかにいうと下記の点。

1.UIImageで描画する際に、y方向の座標が逆になる。

通常、左上が(0,0)になると思ってたんだけど、y方向は画像の左下が(0,0)になっているようで、その点を描画時に補正。

2.カメラ/カメラロールの画像の向き

iPhoneで撮影されたデータは、UIImage.imageOrientationに向きを持っている。

参考:まみったー iPhone の向きと UIImageOrientation メモ

で、この向きが縦位置の時にうまくいかなかったので、色々調べたら、一度、UIImageで書き直してしまえば問題ないらしい。

これはちょっと勘違いがありましたので、詳しくはこちらを。

参考:iPhoneカメラで撮影した画像のUIImageからCGImageを取り出すと90度傾く問題の解決法

これで基本的には問題ないようです。

一応、該当部分のソースはこんな感じです。

- (IBAction)checkFace2:(UIBarButtonItem *)sender {
    //UIImage.imageOrientationの値によって動作が異なるので、元画像を再描画しておく。
    UIImage *orgImage =[UIImage imageWithCGImage:_originalImage.CGImage];
    
    UIGraphicsBeginImageContext(orgImage.size);
    [orgImage drawInRect:CGRectMake(0, 0, orgImage.size.width, orgImage.size.height)];
    orgImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    
    
    CIImage *coreImage = [CIImage imageWithCGImage:orgImage.CGImage];
    
    //オプション辞書作成(顔検出の精度/高精度)
    NSDictionary *options = [NSDictionary
                             dictionaryWithObject:CIDetectorAccuracyHigh
                             forKey:CIDetectorAccuracy];
    
    //CIDetectorを生成(画像解析/現在は顔検出のみ指定可能)
    CIDetector *faceDetector = [CIDetector
                                detectorOfType:CIDetectorTypeFace
                                context:_imageContext
                                options:options];
    
    //顔検出(位置情報を配列で返す)
    NSArray *faces = [faceDetector featuresInImage:coreImage
                                           options:nil];
    //顔部分に画像を載せる
    for(CIFaceFeature *face in faces){
        NSLog(@"---check");
        coreImage = [CIFilter filterWithName:@"CISourceOverCompositing"
                               keysAndValues:kCIInputImageKey, [self makeIconForFace:face],
                     kCIInputBackgroundImageKey, coreImage, nil].outputImage;
    }
       
    CGImageRef cgImage = [_imageContext createCGImage:coreImage fromRect:[coreImage extent]];
    
    //image化するときの向きは、オリジナルに合わせないと横になる。何で?
    _myImageView.image = [UIImage imageWithCGImage:cgImage scale:1.0f orientation:_myImageView.image.imageOrientation];
    CGImageRelease(cgImage);

}
//顔にアイコンを描画
- (CIImage*)makeIconForFace:(CIFaceFeature*)face{
    
    // アイコンのUIImageを作成
    UIImage *iconImage = [UIImage imageNamed:@"icon_250_reasonably_small.jpg"];
    
    
    // 顔のサイズ情報を取得
    CGRect faceRect = [face bounds];
    
    int w = _originalImage.size.width;
    int h = _originalImage.size.height;
    int cx = faceRect.origin.x;
    int cy = faceRect.origin.y;
    int icon_w = faceRect.size.width;
    int icon_h = faceRect.size.height;
    
    //アイコンを顔サイズで描画(yは左下原点になるようなので補正)
    UIGraphicsBeginImageContext(CGSizeMake(w, h));
    [iconImage drawInRect:CGRectMake(cx, h - (cy + icon_h), icon_w, icon_h)];
    UIImage *iconImage_after = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    
    CIImage *image = [CIImage imageWithCGImage:iconImage_after.CGImage];
    
    return image;
}

テスト結果はこんな感じ。

iPhone実機でのテスト結果。
iPhone実機でのテスト結果。

テスト環境:iPhone5+Xcode 5.1.1

 

【Objective-C】CoreImageでの画像フィルタ

Swiftの話題が多い最近ですが、地味にObjective-Cのテストです。

カメラ又はカメラロールから読み込んだ画像にCoreImageでフィルタをかけてみました。こんな感じ。

IMG_5766

実際のコードなどはこの辺を参考に。

【iOS】たった数行で画像のフィルタ/エフェクトが実現できる超便利フレームワークCoreImage。とりあえず7つ紹介!

で、気づいた点は、iPhoneで撮影された画像は、UIImage.imageOrientation プロパティを持っているので、UIViewに戻すときに、指定してやらないと思わぬ向きになるのね。

  

_myImageView.image = [UIImageimageWithCGImage:cgimg scale:1.0forientation:_originalImage.imageOrientation];

結局こんな感じに元画像の値(_originalImage.imagePrientation)を使って設定するように書いたけど。あとはCGImageの解放を忘れずにってことでしょうか。

参考:
iPhone の向きと UIImageOrientation メモ
画像処理 CGImageRelease

やっぱり手を動かして書かないと気づかないことがあるなってことで、しばらく続ける予定、

Movable TypeからWordPressへの引っ越し

旧バージョンの Movable Type の利用に関する注意喚起って記事が出てて、使ってるMovable Type自体もアプデできない(以前レンタルサーバの機能でインストールした)ようなので、ブログをWordPressに変更しました。

とりあえずこの辺を参考に。

Movable TypeからWordPressへ移行方法(MTのWP化で手こずるハイフンに注意)

で、ここで書かれているハイフン対策が何故か効かず。まあ数も少ないので、手作業で直しました。

さくらのサーバを使ってるんだけど、何故かwp-adminだけ、これに引っかかりました。謎ですが。

さくらサーバーにWP(ワードプレス)インストールでForbidden・・

あとは、この辺のスパム対策を実施。

Akismet – コメントスパムをフィルタリングできるWordPressプラグイン
SI CAPTCHA Anti Spam – ブログのコメント欄のスパム対策WordPressプラグイン

見た目とか色々ありますが、ちょっとずつカスタマイズする予定。