ページ

2014/06/25

[Android]Bitmap のモザイク加工処理

DevCamera [カメラ/連射/無音/無音連射/ビデオ] - Google Play の Android アプリ
顔検出カメラを追加して写真を撮ったらモザイクがかかるようにしてみた。
完全にお遊びで顔検出 API を使って作ってみたのでモザイクくらいしかやることがなかった
あまり意味のないカメラです。

ピクセルを取得して周辺のピクセルの色平均をとってピクセルを戻すだけの処理です。

public static Bitmap mosaic(Bitmap bitmap){
    int dot = 16;
    int width = bitmap.getWidth();
    int height = bitmap.getHeight();
    int square = dot * dot;
    int[] pixels = new int[width * height];
    bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
    // ピクセルデータ分ループ
    for(int w = 0, widthEnd = width / dot; w < widthEnd; w++){
        for(int h = 0, heightEnd = height / dot; h < heightEnd; h++){
            // ドットの中の平均値を使う
            int r = 0;
            int g = 0;
            int b = 0;
            int moveX = w * dot;
            int moveY = h * dot;
            for(int dw = 0; dw < dot; dw++){
                for(int dh = 0; dh < dot; dh++){
                    int dotColor = pixels[moveX + dw + (moveY + dh) * width];
                    r += Color.red(dotColor);
                    g += Color.green(dotColor);
                    b += Color.blue(dotColor);
                }
            }
            r = r / square;
            g = g / square;
            b = b / square;
            for(int dw = 0; dw < dot; dw++){
                for(int dh = 0; dh < dot; dh++){
                    pixels[moveX + dw + (moveY + dh) * width] = Color.rgb(r, g, b);
                }
            }
        }
    }
    bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
    return bitmap;
}
Bitmap#copyPixelsToBuffer / Bitmap#copyPixelsFromBuffer の方が速いらしいので書き換えてみた。 本当にこの書き方であっているか自信がない。そしてループ処理が遅すぎて微々たる差しかないように感じた。
public static Bitmap mosaicBuffer(Bitmap bitmap){
    int dot = 16;
    int width = bitmap.getWidth();
    int height = bitmap.getHeight();
    int square = dot * dot;
    int[] pixels = new int[width * height];
    IntBuffer buffer = IntBuffer.wrap(pixels);
    buffer.position(0);
    bitmap.copyPixelsToBuffer(buffer);
    // ピクセルデータ分ループ
    for(int w = 0, widthEnd = width / dot; w < widthEnd; w++){
        for(int h = 0, heightEnd = height / dot; h < heightEnd; h++){
            // ドットの中の平均値を使う
            int r = 0;
            int g = 0;
            int b = 0;
            int moveX = w * dot;
            int moveY = h * dot;
            for(int dw = 0; dw < dot; dw++){
                for(int dh = 0; dh < dot; dh++){
                    int dotColor = pixels[moveX + dw + (moveY + dh) * width];
                    r += Color.red(dotColor);
                    g += Color.green(dotColor);
                    b += Color.blue(dotColor);
                }
            }
            r = r / square;
            g = g / square;
            b = b / square;
            for(int dw = 0; dw < dot; dw++){
                for(int dh = 0; dh < dot; dh++){
                    pixels[moveX + dw + (moveY + dh) * width] = Color.rgb(r, g, b);
                }
            }
        }
    }
    buffer.position(0);
    bitmap.copyPixelsFromBuffer(buffer);
    return bitmap;
}
遅すぎるので[Android]Camera#addCallbackBuffer と Camera#setPreviewCallbackWithBuffer を使うで 書いたように RenderScript で実装するか OpenCV for Android を使うしかないのかもしれない。

参考

Bitmap.setPixel()をsetPixels()に変えたら3倍速くなったよ | Android Techfirm Lab 楓 software: getPixels と copyPixelsToBuffer

2014/06/24

[Android]Camera#addCallbackBuffer と Camera#setPreviewCallbackWithBuffer を使う

Camera#addCallbackBuffer(byte[])
Camera#setPreviewCallbackWithBuffer(Camera.PreviewCallback)ですが、
使い方がわかりにくかったのでメモしておきます。

まず準備として以下の記述を消します。
自分で Canvas に描画するので必要がないのですね。
mCamera.setPreviewDisplay(holder);

そして全体は以下のような感じで実装します。
private Camera mCamera;
private int mPictureOrientation; // デバイスの向きから計算した写真の向き
private Camera.Size mPreviewSize; // Preview サイズ
private Camera.Size mScaledPreviewSize; // Preview サイズを縦横比を維持しつつ画面サイズに拡大したサイズ
private byte[] mCallbackBuffer;

private void startPreview(){
    int size = mPreviewSize.width * mPreviewSize.height * ImageFormat.getBitsPerPixel(mCamera.getParameters().getPreviewFormat()) / 8;
    mCallbackBuffer = new byte[size];
    mCamera.addCallbackBuffer(mCallbackBuffer);
    mCamera.setPreviewCallbackWithBuffer(this);
}

private void stopPreview(){
    mCamera.setPreviewCallbackWithBuffer(null);
}

@Override
public void onPreviewFrame(byte[] data, Camera camera){
    // 画像変換処理
    Bitmap bitmap = BitmapUtils.createBitmapFromYuv(mContext, data, mPreviewSize.width, mPreviewSize.height);
    bitmap = BitmapUtils.rotate(bitmap, mPictureOrientation);
    bitmap = BitmapUtils.resize(bitmap, mScaledPreviewSize.width, mScaledPreviewSize.height);
    // 描画処理
    Canvas canvas = mHolder.lockCanvas();
    if(canvas != null){
        canvas.drawBitmap(bitmap, 0, 0, null);
        mHolder.unlockCanvasAndPost(canvas);
    }
    // 次のフレームのコールバックの設定
    mCamera.addCallbackBuffer(mCallbackBuffer);
}
BitmapUtils.createBitmapFromYuv などは wada811/AndroidLibrary@wada811からです。
本当は Bitmap を毎フレーム生成するのは嫌なんですが
YUV データを Bitmap#setPixels を通して Bitmap に変換する方法がよくわからず…。
JavaFilter#decodeYUV420SP · youten/YUV420SP を使えばいけるかもしれない。
youten さんが書かれている通り、速度は出ないので RenderScript を使いたいところ。

やってみた

private Camera mCamera;
private int mPictureOrientation; // デバイスの向きから計算した写真の向き
private Camera.Size mPreviewSize; // Preview サイズ
private Camera.Size mScaledPreviewSize; // Preview サイズを縦横比を維持しつつ画面サイズに拡大したサイズ
private byte[] mCallbackBuffer;
private int[] mRgbDatas;
private Bitmap mPreviewBitmap;

private void startPreview(){
    mRgbDatas = new int[mPreviewSize.width * mPreviewSize.height];
    mPreviewBitmap = BitmapUtils.createNewBitmap(mPreviewSize.width, mPreviewSize.height);
    int size = mPreviewSize.width * mPreviewSize.height * ImageFormat.getBitsPerPixel(mCamera.getParameters().getPreviewFormat()) / 8;
    mCallbackBuffer = new byte[size];
    mCamera.addCallbackBuffer(mCallbackBuffer);
    mCamera.setPreviewCallbackWithBuffer(this);
}

private void stopPreview(){
    mCamera.setPreviewCallbackWithBuffer(null);
}

@Override
public void onPreviewFrame(byte[] data, Camera camera){
    // 画像変換処理
    BitmapUtils.decodeYUV420SP(mRgbDatas, data, mPreviewSize.width, mPreviewSize.height);
    mPreviewBitmap.setPixels(mRgbDatas, 0, mPreviewSize.width, 0, 0, mPreviewSize.width, mPreviewSize.height);
    Bitmap bitmap = BitmapUtils.rotate(mPreviewBitmap, mPictureOrientation);
    bitmap = BitmapUtils.resize(bitmap, mScaledPreviewSize.width, mScaledPreviewSize.height);
    // 描画処理
    Canvas canvas = mHolder.lockCanvas();
    if(canvas != null){
        canvas.drawBitmap(bitmap, 0, 0, null);
        mHolder.unlockCanvasAndPost(canvas);
    }
    // 次のフレームのコールバックの設定
    mCamera.addCallbackBuffer(mCallbackBuffer);
}
BitmapUtils#decodeYUV420SP も遅かったですが、
BitmapUtils#rotate と BitmapUtils#resize で Bitmap の生成をやってしまっているので更に遅かったです。

参考

#13 やってみよう画像処理『カメラのbyte列をいじる』(Androidで) | tech.kayac.com - KAYAC engineers' blog

2014/06/23

[Git]リモートブランチを全て pull する

Git のリモートブランチを全て pull したいことがあっても
一つのブランチしか pull できません。
一つ一つブランチを指定するのは面倒です。
そんな時は以下のスクリプトを実行すると
origin の全てのブランチを pull することができます。

#!/bin/bash
for branch in `git branch -a | grep remotes | grep -v HEAD | grep -v master `; do
   git branch --track ${branch#remotes/origin/} $branch
done

参考: Piotr Yordanov : git clone all remote branches locally

2014/06/21

Eclipse のローカルヒストリの開き方、あるいは find -exec の使い方

Eclipse のローカルヒストリは便利ですね。
ちょっとしたお試しで変更して戻せて、使っていない人はいなんじゃないかと思います。
便利すぎてちょっとした変更を Git などでバージョン管理しておらず、
Eclipse が落ちるなどしてローカルヒストリが辿れなくなると悲惨です。

調べてみるとローカルヒストリの保存場所がありました。
FAQ Where is the workspace local history stored? - Eclipsepedia
cd WORKSPACE/.metadata/.plugins/org.eclipse.core.resources/.history/
開いたら履歴ファイルを find で検索・絞り込みつつ、 open で開きまくります。
find . -type f -exec open {} \;
便利です。

参考
findのexecが便利 - マツモブログ

2014/06/11

Git 2.0 で pull の Fast-Forward を有効にして Homebrew で update したいじゃん?でも今までの git merge --no-ff でマージコミットしてるせいで失敗するじゃん?困るじゃん?

Homebrew の更新の度にマージコミットで中断されてめんどくさかったので
マージコミットしないようにして中断されないようにしました。
Git 2.0 がリリースされて pull が Fast-Forward にできるようになったおかげで
Homebrew の更新を監視する作業がなくなります。やったー。
[Git]GitのpullをFast-Forward onlyにする | DevAchieve で書いたように
git config --global pull.ff only で設定を変更しました。

merge の Fast-Forward は無効で、pull の Fast-Forward は強制にしました。
[merge]
    ff = false
[pull]
    ff = only

これで Homebrew の更新 brew bundle ~/Brewfile の内部の git pull でマージコミットのために
Homebrew の更新が中断されなくなるはずです。

よーし、更新するぞー!と以下のコマンドを実行!
brew bundle ~/Brewfile
fatal: Not possible to fast-forward, aborting.
Error: Failure while executing: git pull -q origin refs/heads/master:refs/remotes/origin/master
Error: Command failed: L2:brew update
はい、明らかに git pull でエラーになりました。
今までマージコミットしていたから Fast-Forward で pull できなくなっているんですね。

Homebrew をアンインストールしてイントールし直すしかないかなぁと思ったけど
Homebrew で入れていたものまでアンインストールされそうです。
参考: いつか見た惑星: Homebrewをアンインストールする

他にも見ていると .git を削除していたのを見つけて解決しました。
参考: Homebrew をアンインストールする方法 - present
Git の問題なので .git を削除するだけでなんとかなりそうだと。

cd `brew --prefix`
rm -rf .git
brew bundle ~/Brewfile
ということで Homebrew の履歴もスッキリして動くようになりました。

Homebrew-cask をインストールしていて Homebrew-cask のコミットもマージコミットしていたら
以下の様なエラーが出ているかもしれません。
fatal: Not possible to fast-forward, aborting.
Error: Failed to update tap: caskroom/cask
fatal: Not possible to fast-forward, aborting.
Error: Failed to update tap: caskroom/versions
以下のコマンドを実行して再インストールしてあげれば履歴が綺麗になって動きます。
brew uninstall brew-cask
brew untap caskroom/cask
brew untap caskroom/versions
brew tap caskroom/cask
brew tap caskroom/versions
brew install brew-cask
tap/untap のくだりはいらないかもしれませんが確認していないので一応記載。

追記

brew uninstall brew-cask
brew untap caskroom/cask
brew untap caskroom/versions
上記のコマンドを実行すると
brew bundle ~/Brewfile を実行した際に Homebrew-cask でインストールしたアプリケーションが
すでにインストールされていてエラーになります。

以下のコマンドで .git だけ削除したほうがいいかもしれません。
rm -rf /usr/local/Library/Taps/caskroom/homebrew-cask/.git
rm -rf /usr/local/Library/Taps/caskroom/homebrew-versions/.git
試してないので誰か同じ境遇の人で試した人がいたら結果を教えて下さい。

追記2

.git だけ削除するのはダメなようです。
あと、最新版の Homebrew で brew bundle が使えなくなったので
仕方ないのでシェルスクリプトにしました。
dotfiles/brew-update at master · wada811/dotfiles