プログラミング雑記

プログラミングが趣味のだらだら学生。自作したプログラムの紹介やいまハマってることなんかを適当に書いていったりいかなったり

モザイクを外すプログラムを作る

きっかけ

先日友人と話していたときのことですが、その友人が突然「モザイクをはずすのは男の夢だよな!」と熱く語り出したのです。友人の方は随分とメートルが上がっていたようでノリノリに話していたのですが、聞いているこちらはまだ酔いがあまりまわっていなかったので割と恥ずかしいおもいをしました。

まあしかし、彼とはながい付き合いなので彼の夢をかなえてあげようとモザイクを外すプログラムを作りました。皆さんにも是非共有しようと思い、今回投稿した次第です。

これを読めばモザイクをはずしつつ、Processingでの画像を扱うプログラムの基本が分かる、かもしれません。

実行結果

まずは百聞は一見にしかずということで、どういう風に動作するのか見ていきましょう。

f:id:kirari0831:20160104001834g:plain

このようにして丸で囲まれた部分のみモザイクが外れるようになっています。

ちなみに元画像はボム兵の可愛らしい画像。今回の説明ではこの画像を使用していきます(ファイル名:bomuhei.jpg)。

f:id:kirari0831:20160104001819j:plain

モザイク処理をかけるとこんな感じ。

f:id:kirari0831:20160104002014j:plain

実装手順

今回も使用するプログラミング言語Processing(ver3.0)です。Processingがない場合はリンク先からDownloadをクリックし、適宜インストールしてください。

画像を取り込む

始めに今回のプログラムで使用したい画像を以下の要領でスケッチ(Processingのソースコード)上にドラッグアンドドロップしてください。

f:id:kirari0831:20151225220333g:plain

画像を読み込み表示する

それでは今取り込んだ画像をロードし表示までしてみましょう。
画像を読み込むのに使う関数がloadImage()です。この関数の引数に読み込みたい画像のファイル名を与えてやると画像を読み込むことが出来ます。先程述べたように、事前にスケッチに画像を取り込んでおけば画像の絶対パス相対パスを考える必要はなく、ただファイル名を指定するだけで読み込むことができます。ちなみにProcessingでは画像クラスをPImageといいます。
画像の表示に使う関数がimage()です。第1引数に表示したい画像(PImage)を、第2引数と第3引数には画像を表示したい場所(int)を指定します。デフォルトでは、表示させる場所というのは画像の左上の座標のことを言います。

以下は画像を読み込み表示するだけのプログラムです。

PImage originalImage;
void setup() {
    originalImage = loadImage("bomuhei.jpg");   //画像の読み込み。読み込みたいファイル名を指定。
    surface.setResizable(true);
    surface.setSize(originalImage.width, originalImage.height); //画像サイズに合わせて画面サイズを変更
}
void draw(){
    image(originalImage,0,0);   //読み込んだ画像の表示    
}

画像をぼかす

次にモザイクをかけていきましょう。Processingではなんと1行で画像にモザイクをかけることが可能です!そんな便利関数がfilter()です。引数に画像処理をしたい種類を与えてやるとその画像処理をしてくれる便利な関数です。今回はモザイク(と言うかぼかし?)をかけたいのでBLURを与え、第2引数にぼかし具合を与えてやります。BLUR以外にも何個かあるので興味があるかたはprocessingのリファレンスを見て下さい。

PImage originalImage,mosaicImage;

void setup() {
    originalImage = loadImage("bomuhei.jpg");   //画像の読み込み
    mosaicImage = loadImage("bomuhei.jpg");   //モザイクかける用
    mosaicImage.filter(BLUR, 20);   //画像をぼかす。第2引数は適宜変更してください。
    surface.setResizable(true);
    surface.setSize(originalImage.width, originalImage.height); //画像サイズに合わせて画面サイズを変更
}
void draw(){
    image(mosaicImage,0,0);   //読み込んだ画像の表示
}

画像のぼかしをはずす

最後に本番、画像のぼかしをはずしてあげちゃいましょう。
やり方として二通り上げておきます。一つ目に紹介する方はcopy()を使う方法です。一言でいうと、画像の一部を取り出してきて、それを画面に貼りつける関数です。第1引数にコピーしてきたい元画像、第2,3引数にコピー元の画像の始点、第4,5引数にさっき指定した始点からの幅と高さ、第6,7引数にコピーした画像を貼りたい場所の始点、第8,9引数にいま指定した始点からの幅と高さを指定してやると画像の一部の切り出しが可能です(あ~長かった)。これまた便利な関数で使い方を覚えておくと色んな場面で使うことができるので、今回はあまり詳しく説明しませんが是非知っておくと良いでしょう。

PImage mosaicImage,originalImage;
void setup() {
    originalImage = loadImage("bomuhei.jpg");
    mosaicImage = loadImage("bomuhei.jpg");
    mosaicImage.filter(BLUR,20);
    surface.setResizable(true);
    surface.setSize(originalImage.width, originalImage.height);
}
void draw() {
    image(mosaicImage,0,0);
    if(!(mouseX == 0 || mouseX == width || mouseY == 0 || mouseY == height)){
        clearRectangle();
    }
}
int rectWidth = 200;    //ぼかしをはずす四角形の一辺
void clearRectangle(){
    //以下ぼかし範囲の調整
    int startX = mouseX - rectWidth/2;
    int startY = mouseY - rectWidth/2;
    int endX = startX + rectWidth;
    int endY = startY + rectWidth;
    if(startX < 0){
        startX = 0;
    }
    if(startY < 0){
        startY = 0;
    }
    if(endX >= width){
        endX = width-1;
    }
    if(endY >= height){
        endY = height-1;
    }
    //ぼかしをはずしたい範囲の元画像を取得し貼り付ける
    copy(originalImage,startX,startY,endX-startX,endY-startY,startX,startY,endX-startX,endY-startY);
}

ぼかしの外し方を丸型に

無事モザイクをはずすことができました!ただ、ぼかしの外し方が四角形なのは気に喰わないので丸型にします。
copy関数では四角形でしか画像を切り出して来れないので、元画像から1ピクセルずつ色を取得してきて画面のピクセルを更新していきます。get()関数を用いることで指定したピクセルの色を取得できます。例えばimg.get(50,100);とすれば、画像imgの横50px,縦100px目の色を取得してくることができます。set()を使えば指定したピクセルの色を更新することができます。先程のget()と合わせて用いset(50,100,img.get(50,100));こうすると画面上の50px,100pxの色を画像の色に置き換えることが可能です。ただし、Processingでピクセル操作を行った場合は必ずupdatePixels();を行ってください。更新が反映されないので要注意!

PImage mosaicImage,originalImage;

void setup() {
    originalImage = loadImage("bomuhei.jpg");
    mosaicImage = loadImage("bomuhei.jpg");
    mosaicImage.filter(BLUR,20);
    surface.setResizable(true);
    surface.setSize(originalImage.width, originalImage.height);
}
void draw() {
    image(mosaicImage,0,0);
    if(!(mouseX == 0 || mouseX == width || mouseY == 0 || mouseY == height)){
        clearCircle();  //丸型
        // clearRectangle();    //四角型
    }
}

int rectWidth = 200;
void clearRectangle(){
    int startX = mouseX - rectWidth/2;
    int startY = mouseY - rectWidth/2;
    int endX = startX + rectWidth;
    int endY = startY + rectWidth;
    if(startX < 0){
        startX = 0;
    }
    if(startY < 0){
        startY = 0;
    }
    if(endX >= width){
        endX = width-1;
    }
    if(endY >= height){
        endY = height-1;
    }
    copy(originalImage,startX,startY,endX-startX,endY-startY,startX,startY,endX-startX,endY-startY);
}
int r = 200;    //ぼかしをはずす円の直径
void clearCircle(){
    int startY = mouseY - r/2;
    for(int y = startY; y < startY+r; y++){
        if(y >= 0 && y < height){
            int h = abs(y-mouseY);
            int w = int(sqrt(r/2*r/2-h*h))*2;
            int startX = mouseX - w/2;
            for(int x = startX; x < startX+w; x++){
                if(x >= 0 && x < width){
                    set(x,y,originalImage.get(x,y));    //(x,y)座標の色を元画像のものにする
                }
            }
        }
    }
    updatePixels(); //ピクセル操作を行った場合は必ず実行する
}

void keyPressed(){
    if(keyCode == UP){  //上矢印キーをおすと円がでかくなるよ
        r += 50;
    }
    else if(keyCode == DOWN){   //下矢印キーを押すとちいさくなるよ
        if(r > 0){
            r -= 50;
        }
    }
}

おまけ:画像のサイズを変える

おまけで最後に一つ。元画像の大きさそのままに表示しようとすると大きすぎて画面に収まらない場合はresize()を使いましょう。こいつがまた役に立つやつで一行で画像の大きさを自由にしてくれます。img.resize(200,300);とやれば幅200px、高さ300pxにしてくれますし、とくにすごいのが与える引数の一方を0にすると、画像の縦横比をそろえたまま指定の大きさにしてくれます。たとえば、img.resize(0,600);のように幅を0とすると縦横比を保ったまま幅を600pxにしてくれます。
これを用いて以下のようにしてあげると大きすぎる画像を良い感じにしてくれると思います。それにしてもJavaだと画像の大きさ一つ変えるのにも色々と準備が必要だったのですが、Processingだと一発ですよね。本当にありがたい。

PImage originalImage;
void setup() {
    originalImage = loadImage("bomuhei.jpg");   //画像の読み込み
    // if(originalImage.width > displayWidth || originalImage.height > displayHeight){  //こちらは御好みでコメントアウトしてください。画像の大きさが画面サイズを超えるかどうか判定する
    if(originalImage.width > originalImage.height){
        originalImage.resize(600,0);
    }else{
        originalImage.resize(0,600);
    }
    // }
    surface.setResizable(true);
    surface.setSize(originalImage.width, originalImage.height); //画像サイズに合わせて画面サイズを変更
}
void draw(){
    image(originalImage,0,0);   //読み込んだ画像の表示    
}

まとめ

Processingは便利ですね、これの一言につきます。こんかい見ていったとおりProcessingで画像をいじるのは難しくありません。今回扱った関数について詳しくは公式リファレンスにのっていますのでそれぞれ軽く調べてみてくだい。自分の思ったことが簡単んに実現できるかもしれません。ではみなさん、好きにこのプログラムを使ってくださいな。あっ、結局モザイクはずさないで元画像引っ張てきてるだけじゃんってのは無しの方向でご勘弁を…()

f:id:kirari0831:20160104002254j:plain