トップ | puarts.com
メモ  |  制作記  |  開発記  |  日常の記録  |  デジタルコンテンツ制作  |  ファイアーエムブレム  |  ゲーム  |  C/C++  |  C#  |  PC/ソフトウェア  |  Web 開発  |  スクリプト言語  |  その他プログラミング  |  
「画像処理」に関連する記事一覧

0  1  

顕著性マップ(Saliency Map)でウンダムの目立ち度を計る

2012/09/02

顕著性マップ(Saliency Map)と呼ばれるイメージの中の目立つ度合いを表したマップを生成するプログラムを研究で使えそうだったので作りました。ソースコードを公開しているサイトなどがいくつかあったのですぐに作れました。ありがたいです。

作ったプログラムでお台場合衆国 めちゃイケサービスエリアにいたウンダムの画像の顕著性マップを生成してみます。

元画像

顕著性マップ

人間が見てもわかりますが、やはりこの画像の中ではウンダムがかなり目立っていることが顕著性マップからもわかります。

その他にもいくつかの画像で試してみましたが、いい感じのものもあれば、微妙なものもありました。結局パラメータ調整次第ということになるので、利用する用途に応じてパラメータを調整する必要がありそうで、もう少し汎用性を高めるためにはもう一工夫必要そうです。

A model of saliency-based visual attention for rapid scene analysis
http://papers.klab.caltech.edu/241/1/121.pdf

Algorithm Site - 顕著性マップ
http://fussy.web.fc2.com/algo/algo12-3.htm

Akisato Web Site Annex - Saliency mapをOpenCVで実装する
http://pub.ne.jp/akisato/?entry_id=3970170

ヒストグラムから山を抽出して画像領域分割

2011/12/31
研究で高速で視差マップから手前の物体のみを自動で抽出したかったので、思いつきだけで領域分割をするプログラムを作りました。

ぱっと思い浮かんだ簡単なアルゴリズムはヒストグラムの山の部分を切り取ってくる手法。もう少し具体的に言うとヒストグラムからある閾値以上の輝度が密集している部分を物体として分割するアルゴリズム。

ヒストグラムを作るプログラムは私の個人ライブラリに既にあったので、それを使ってすぐにこの手法を実装できると思っていたのですが、山であると判断するためのヒストグラムの高さの閾値の設定や、輝度の密集度の閾値をどう定義するかというところで結構悩みました。

結局、閾値に関しては画像によりけりというところがあり、様々な画像に対して自動で最適な閾値を見つけるのは結構難しそうでした。最終的に自動でというのはやめて、以前から開発しているオリジナル動画編集ソフトのプラグインとして作成することでパラメータを変えたらインタラクティブに結果がプレビューされるようにして、ユーザが最適な閾値を容易にみつけられるようにしました。

GUIはこんな感じです。


以下の画像はこの領域分割プログラムのテスト結果です。上が元画像で下がこのプラグインで手前の物体を抽出した結果です。





ただ島になっている輝度を抜き出しているだけなので、通常のカラー画像や連続的に繋がっている画像にはまったく効果はありませんが、深度マップや視差マップのようにある程度物体ごとに同じ輝度が集中している画像にはそれなりに効果があります。

輝度の連続性+領域の連続性も考慮したらもっと精細な領域分割ができそうですが、今必要なものはおおまかな分類だけなので、とりあえず速度重視でこのままで研究を進めようと思います。

修士論文の締め切りが迫っているこの時期にまだ研究用のプログラム作って実験をしているというのはかなり遅れていると思います。正月は研究&論文作成の日々になりそうです。

位相限定相関法を用いたステレオマッチング

2011/12/25
メリークリスマス!クリスマスですが、研究の話です。

半年前くらいに作ったPOC(位相限定相関法)ベースのステレオマッチングのプログラムが重くてトライアンドエラーをするのがしんどかったので、無駄をなくすために最近空いている時間を費やして1から作り直していました。

今までFFTにはFFTWというライブラリを使用していたのですが、自分でFFTも作ることにしました。FFTのプログラムを書く時点からステレオマッチング用にコードを最適化を行ってゆき、それに加えてOpenMPによる並列化を加えたところ、10倍くらい高速になったのでトライアンドエラーがさくさくになりました。

ということで下記にブロック相関法ベースのステレオマッチング手法との比較を載せます。

こちらが比較に使う元画像です。



こちらがブロック相関を用いたステレオマッチングにより復元したモデルです。ピクセル単位でしか視差を計算できないので、段々ができてしまっています。


そして、こちらがPOCを用いたステレオマッチングで復元したモデルです。サブピクセルレベルで視差が計算できるので、ブロック相関法のものより細かいディティールまでしっかり復元できています。


こちらはPOCベースのステレオマッチングで復元したモデルに元画像をテクスチャとして投影マッピングではりつけたものです。


他にもいろいろなサンプルで試してみたいです。後、動画でも試してみたいです。

参考文献
http://w2.gakkai-web.net/gakkai/ieice/vol1no1pdf/vol1no1_030.pdf

C++ 線形(Bilinear)補間で画像を拡大するサンプル

2011/12/18
現在開発中の画像エフェクト系のソフトウェアで画像の拡大機能を今までOpenCVを使ってとりあえずで済ませていたのですが、画像の型が限定されてしまうのが嫌だったので、いい加減自分で作ろうと思い、Bilinearで画像を拡大するテンプレート関数を書いてみましたのでソースコードを載せます。

アルゴリズムは単純なのですぐに書けると思いきや、拡大した結果がだいたいPhotoshopと同じになるものはすぐにできたものの、微妙に位置やスケールが違ったので、それが同じになるまではかなり苦労しました。拡大前の(0,0)座標が拡大後も(0,0)になるように考えていたのが誤りで、紙に書いて整理していてようやくそうでないことに気づきました。先入観とは恐ろしい。こんな単純なプログラムに一日もかかってしまうとは。まぁ、なんとか完成してよかったです。

ただ、頭がこんがらがったときにわかりやすくするために、無駄なことをやっているので、高速にするためにはもっと整理しないといけませんが、とにかく自分で書いたものを使っているというのが後々の最適化において重要なことになってくるはず。とりあえず、速度的に不満が出るまではこのコードを使っていこうと思います。



template<typename T> void wsp::img::ScaleImageBilinear(

                const T *in_src, int src_width, int src_height, int num_channels,

                                        T *o_dst,         int dst_width, int dst_height

){

        float x_src_step = 1.0/(float)(src_width);

        float y_src_step = 1.0/(float)(src_height);

        float x_dst_step = 1.0/(float)(dst_width);

        float y_dst_step = 1.0/(float)(dst_height);

        float zero_xpos_src = x_src_step/2.0;

        float zero_xpos_dst = x_dst_step/2.0;

        float zero_ypos_src = y_src_step/2.0;

        float zero_ypos_dst = y_dst_step/2.0;

        

        {

                int x, y, c;

                int i_s0, i_s1, i_s2, i_s3, i_d;

                int yd, xd, ys, xs;

                float x_coef0, x_coef1, y_coef0, y_coef1;

                float x_src_f, y_src_f, x_dst_f, y_dst_f;

        

                y_src_f=zero_ypos_src;

                y_dst_f=zero_ypos_dst;

                for(yd=0, ys=0; yd<dst_height; ++yd, y_dst_f+=y_dst_step){

                        if(y_src_f+y_src_step <= y_dst_f){

                                y_src_f+=y_src_step;

                                ++ys;

                        }

                        y_coef1 = (y_dst_f-y_src_f)/y_src_step;

                        y_coef0 = 1.0f - y_coef1;

                        x_dst_f=zero_xpos_dst;

                        x_src_f=zero_xpos_src;

                        for(xd=0, xs=0; xd<dst_width; ++xd, x_dst_f+=x_dst_step){

                                if(x_src_f+x_src_step <= x_dst_f){

                                        x_src_f+=x_src_step;

                                        ++xs;

                                }

                                if(         zero_xpos_src<=x_dst_f && x_dst_f<1.0f-zero_xpos_src

                                        && zero_ypos_src<=y_dst_f && y_dst_f<1.0f-zero_ypos_src)

                                {

                                        x_coef1 = (x_dst_f-x_src_f)/x_src_step;

                                        x_coef0 = 1.0f - x_coef1;

                                        i_d = (yd*dst_width+xd)*num_channels;

                                        i_s0 = ((ys)*src_width+(xs))*num_channels;

                                        i_s1 = i_s0 + num_channels;

                                        i_s2 = i_s0 + src_width*num_channels;

                                        i_s3 = i_s1 + src_width*num_channels;

                                                

                                        for(c=0; c<num_channels; ++c){

                                                //** compute linear interpolation

                                                o_dst[i_d+c] = static_cast<T>(

                                                        (in_src[i_s0+c]*x_coef0 + in_src[i_s1+c]*x_coef1)*y_coef0

                                                        + (in_src[i_s2+c]*x_coef0 + in_src[i_s3+c]*x_coef1)*y_coef1

                                                );

                                        }

                                }

                        }

                }

        }

        //** copy corner

        {

                int x, y, c, i_dst0, i_src0, i_dst1, i_src1;

                int x_copy_top, x_copy_tail, y_copy_top, y_copy_tail;

                float y_dst_f, x_dst_f;

        

                //** compute position of source of copy        

                for(y_copy_top=0, y_dst_f=zero_ypos_dst; y_dst_f<zero_ypos_src; ++y_copy_top, y_dst_f+=y_dst_step){}

                for(x_copy_top=0, x_dst_f=zero_xpos_dst; x_dst_f<zero_xpos_src; ++x_copy_top, x_dst_f+=x_dst_step){}

                x_copy_tail = dst_width - 1 - x_copy_top;

                y_copy_tail = dst_height - 1 - y_copy_top;

                printf("ytop:%d, xtop:%d, ytail:%d, xtail:%d\n", y_copy_top, x_copy_top, y_copy_tail, x_copy_tail);

        

                //** copy horizontal lines in corner

                y_dst_f = zero_ypos_dst;

                for(y=0; y_dst_f<zero_ypos_src; ++y, y_dst_f+=y_dst_step){

                        for(x=0; x<dst_width; ++x){        

                                i_dst0 = (y*dst_width+x)*num_channels;

                                i_src0 = (y_copy_top*dst_width+x)*num_channels;

                                i_dst1 = ((dst_height-y-1)*dst_width+x)*num_channels;

                                i_src1 = (y_copy_tail*dst_width+x)*num_channels;

                                for(c=0; c<num_channels; ++c){

                                        //** copy top x corner

                                        o_dst[i_dst0+c] = o_dst[i_src0+c];        

                                        //** copy bottom x corner

                                        o_dst[i_dst1+c] = o_dst[i_src1+c];

                                }

                        }

                }

                //** copy vertical lines in corner

                x_dst_f = zero_xpos_dst;

                for(x=0; x_dst_f<zero_xpos_src; ++x, x_dst_f+=x_dst_step){

                        for(y=0; y<dst_height; ++y){        

                                i_dst0 = (y*dst_width+x)*num_channels;

                                i_src0 = (y*dst_width+x_copy_top)*num_channels;

                                i_dst1 = ((y+1)*dst_width-x-1)*num_channels;

                                i_src1 = (y*dst_width+x_copy_tail)*num_channels;

                                for(c=0; c<num_channels; ++c){

                                        //** copy left y corner

                                        o_dst[i_dst0+c] = o_dst[i_src0+c];        

                                        //** copy right y corner

                                        o_dst[i_dst1+c] = o_dst[i_src1+c];

                                }

                        }

                }

        }

}


C++ 最近傍探索法による画像拡大縮小サンプル

2011/12/15
最近傍探索(Nearest Neighbor Search)法で画像をスケーリングするtemplate関数のサンプルコードを載せます。

最適化を試みましたが、結果的にコンパイラの自動最適化に速度が勝てなかったので、最適化前のコードを載せます。最も効率の良い書き方があるかもしれません。

引数
in_src:  元画像データの1次元配列
src_width:  元画像の横解像度
src_height:  元画像の縦解像度
num_channels:  画像のチャンネル数
o_dst:  出力画像データの1次元配列
dst_width:  出力画像の横解像度
dst_height:  出力画像の縦解像度



template<typename T> void ScaleImageNearestNeighbor(

         const T *in_src, int src_width, int src_height, int num_channels,

                                                              T *o_dst,         int dst_width, int dst_height

){

         float scale_w = src_width/static_cast<float>(dst_width);

         float scale_h = src_height/static_cast<float>(dst_height);

         int dst_size = dst_width * dst_height * num_channels;

         int src_size = src_width * src_height * num_channels;

        

         {

                  int x, y, c, i_dst, i_src;

                  int yw_src, yw_dst;

                  //** unoptimized code ----------------------- 

                  for(y=0; y<dst_height; ++y){

                           yw_src = static_cast<int>(y*scale_h)*src_width;

                           yw_dst = y*dst_width;

                           for(x=0; x<dst_width; ++x){

                                    i_dst = (yw_dst + x) * num_channels;

                                    i_src = (yw_src + static_cast<int>(x*scale_w)) * num_channels;

                                    for(c=0; c<num_channels; ++c){

                                             o_dst[i_dst+c] = in_src[i_src+c];

                                    }

                           }

                  }

         }

}


Java 画像データ用クラス

2011/12/08
(2018/11/13 更新)

普段Javaはあまり使わないのですが、大学の授業の関係で久しぶりに触りました。

いつもはC++で画像処理をやっているのですが、Javaで画像を扱おうとするとどんな感じになるのかと疑問に思ったので、簡単な画像データ用のクラスを作ってみました。

以下が作成したクラスです。機能的には画像用メモリの確保とRGBAデータのget & setだけです。


/*!
 *  WspImage is a class which access to some of manipulation of image
*/
public class WspImage
{
    private int width_=0, height_=0;
    private int[] rgba_data_=null;
    
    //** Constructors ------------------- 
    WspImage(int width, int height){
        Resize(width, height);
    }

    //** Accessors ---------------------- 
    int width(){ return width_; }
    int height(){ return height_; }
    int[] rgba_data(){ return rgba_data_; }
    int GetR(int index)
    { return (rgba_data_[index] & 0xff000000)>>24; }
    int GetG(int index)
    { return (rgba_data_[index] & 0x00ff0000)>>16; }
    int GetB(int index)
    { return (rgba_data_[index] & 0x0000ff00)>>8; }
    int GetA(int index)
    { return (rgba_data_[index] & 0x000000ff); }
    
    int GetR(int x, int y){ return GetR(y*width_+x); }
    int GetG(int x, int y){ return GetG(y*width_+x); }
    int GetB(int x, int y){ return GetB(y*width_+x); }
    int GetA(int x, int y){ return GetA(y*width_+x); }

    //** Mutators ----------------------- 
    void Resize(int width, int height){
        rgba_data_ = new int[width*height];
        width_ = width;
        height_ = height;
    }
    void SetR(int r, int index)
    { rgba_data_[index] = (rgba_data_[index] & 0x00ffffff) + (r<<24); }
    void SetG(int g, int index)
    { rgba_data_[index] = (rgba_data_[index] & 0xff00ffff) + (g<<16); }
    void SetB(int b, int index)
    { rgba_data_[index] = (rgba_data_[index] & 0xffff00ff) + (b<<8); }
    void SetA(int a, int index)
    { rgba_data_[index] = (rgba_data_[index] & 0xffffff00) + a; }
    
    void SetR(int r, int x, int y){ SetR(r, y*width_+x); }
    void SetG(int g, int x, int y){ SetG(g, y*width_+x); }
    void SetB(int b, int x, int y){ SetB(b, y*width_+x); }
    void SetA(int a, int x, int y){ SetA(a, y*width_+x); }
    
};

この画像用データクラスを作ってJavaとC++の違いから感じた利点・欠点を以下に挙げます。

  • 符号なしbyteデータ型がJavaにはないのでintなどで代用する必要がある
  • オペレータオーバーロードがJavaはできないので要素アクセスに[]が使えないのが不便
  • JavaはGCのおかげでメモリ開放操作が不要なのが楽
  • Javaは引数の既定値の設定ができないのが不便
  • Javaは1ファイルにクラス1つなので小さいクラスも別ファイルに書かなければならない

思ったより実装の違いは多くありませんでしたが、やはりメモリ開放を考えなくていいのはかなり楽です。でも、このメモリ操作をいかに効率よくやるのかという部分も慣れてくるとプログラミングの楽しみになってくるんですけどね。

ついでに、あまり役には立たないですが、RGBAひとつのデータだけを扱うデータクラスも載せておきます。


public class WspRgba
{
    private int data_;
    
    WspRgba(int r, int g, int b, int a){
        data_ = (r<<24) | (g<<16) | (b<<8) | a;
    }
    
    //** accessors
    int data(){ return data_; }
    int r(){ return ((data_ & 0xff000000)>>24);}
    int g(){ return ((data_ & 0x00ff0000)>>16);}
    int b(){ return ((data_ & 0x0000ff00)>>8);}
    int a(){ return ((data_ & 0x000000ff));}
    
    //** mutators
    void set_r(int r){ data_ = (data_ & 0x00ffffff) + (r<<24); }
    void set_g(int g){ data_ = (data_ & 0xff00ffff) + (g<<16); }
    void set_b(int b){ data_ = (data_ & 0xffff00ff) + (b<<8); }
    void set_a(int a){ data_ = (data_ & 0xffffff00) + a; }
};

最初はこのWspRgbクラスを要素にした画像クラスを作ろうと思ったのですが、Javaだとクラスで配列を作ろうとすると強制的にポインタの配列になってしまい、要素ひとつずつに別個メモリ確保が必要になってしまい、効率を悪くなってしまうので素直にint配列にしました。

エッジ抽出を数回適用するとおもしろい絵になる?

2011/10/10
たまたまFractal Noise画像にSobel Filterを何回か適用していたら、おもしろい絵になったので、これを応用してムンクの叫びみたいのを作ってみました。我ながらちょっと不気味です。


POCステレオマッチング 普通の顔と光を投影した顔の深度計算結果の比較

2011/07/14
位相限定相関(POC)法という高精度な位置ずれ検出アルゴリズムを用いたステレオマッチングのプログラムを作ってみたので、この手法のステレオマッチングで普通の顔と光を投影した顔の深度計算精度の比較実験を行ってみました。

ちょっと実写でサンプルをとるのが面倒だったのでひとまずCGで顔モデルを作って、そこにテクスチャを投影マッピングで投影したものを実写で顔に光を投影したものと仮定して比較用で使います。

計算ができているかできていないかを判定する基準としてはPOCのピーク値を用いました。信頼度が低ければPOCのピーク値が低く、信頼度が高ければピークも高くという風に信頼度を決めて、閾値で切りました。

まずは普通に2視点からレンダリングした 顔の画像から深度を計算してみます。




全然駄目です。目、鼻、口、輪郭の辺りしか深度を計算することはできませんでした。
やる前からは予想はつきましたが、頬や額などの肌色が連続するような領域は特徴がないので、相関計算がうまくいかないです。

次に顔モデルにテクスチャを正面から投影してレンダリングした2視点の画像から深度計算を試みてみます。




おぉ、正確さは別として、ほぼ顔全体の深度が計算できた。
顔の内部に計算ができない小さなエリアが若干ありましたが周囲の深度から補間で補えるレベルだったので補間しました。

ということで、顔の深度を計算するときは光を投影するというのはかなり有効みたいです。まだ実写で試してないですが。

そのうち学校のプロジェクタを借りて何か模様を投影したものを撮影して、それを使って深度計算でもやってみようと思います。

可変ダイナミックレンジによる独自画像圧縮形式

2010/07/11

私は今、研究で負の値を必要とするハイダイナミックレンジな特殊なマップを必要としています。

一般的な画像でも2の補数を負の値と考えれば、負の値を格納することは可能です。

HDRIなどを除く一般的な画像形式は1画素辺り1byteのデータ(256階調)を格納できるので、負の値と正の値を格納するときは次のように考えることができます。


8  → 0000 0000 0000 0000 0000 0000 0000 0100
-8 → 1111 1111 1111 1111 1111 1111 1111 1000
 

 ただし、この方法で負の値を格納すると-128から127までの値しか保存することはできません。
これではダイナミックレンジが狭すぎるので、今まではただ単にテキストデータで1画素ごとに保存していました。例えば、

200
-30
4
40
12
-7
30

 といったように改行を区切り文字にして、画素値をテキスト形式で保存していきました。

ですが、大量のシーケンスを書き出すと相当なデータ量となってしまうため、ファイルシステムを圧迫してしまいます。

そこで、このデータを独自圧縮形式で圧縮するプログラムを作ることにしました。


アルゴリズムは単純です。使用している領域のみ取り出してバイナリ形式で保存するというだけです。
つまり、ダイナミックレンジがフレキシブルな画像を作るのです。


具体的には次のようにします。

画像中の最小値Dminと最大値Dmaxを探索し、次式で必要なデータ幅Rを求めます。

R = Dmax - Dmin
 

Rに必要なビット幅を1画素分の視差データの格納単位としてデータを保存します。

画像のヘッダーには画像解像度と1画素辺りのデータ格納単位、画素値の最大値と最小値を記録しておき、読み込みのときにそれらのデータを元に画像を復元します。

ダイナミックレンジは画像により次のように変更することができます。

1bit → 2階調
2bit → 4階調
3bit → 8階調
4bit → 16階調
5bit → 32階調
6bit → 64階調
7bit → 128階調
8bit → 256階調
9bit → 512階調
...

 例えば、Dmax =10、Dmin =-10だった場合、Rは20となり。 必要なビット幅は5bit(32階調)となります。

ちなみに、テキストだと1文字当たり1byte(8bit)のデータを割り当てることになるので、

文字数×8bit

のデータ量が必要になります。

例えば、もしある画素に格納されている値が200だとしたら、3文字+区切り文字が必要なので31bit使用することになります。
これでは無駄がかなり多いです。

またpgmのようにバイナリ形式で保存したとしても、ダイナミックレンジが8bit固定になってしまい、8bit未満しか使っていない場合に無駄が出ます。
その上、一般的な画像形式では1チャンネル辺り256階調以上の階調数には対応できません。


この圧縮を行えば、画像に応じた無駄のないダイナミックレンジを利用でき、かつ可変ダイナミックレンジが8bitを超える画像の保存にも対応してくれるという利点があります。


ある意味、HDRIとも言えます。


実際にプログラムを作成して利用してみると、今までのテキストデータのときよりもデータを1/3以下に削減することができました。


このように、一般的に利用されいている画像形式は全てのシチュエーションにおいて万能ではないため、目的によっては単純なアルゴリズムで最適化したやった独自のデータ形式を扱う方が、より効果を発揮する場合があるのです。


Maya Image-based Modeling用プラグインを開発中

2010/07/04

複数枚の2次元画像から3Dモデルを作るようなMayaのPlug-inを開発中です。

イメージベースドモデリングをやってみたいのです。

今、とりあえず深度情報から3次元モデルを起こすような簡単なプラグインを作りました。

これがもとの深度です。

face_depth.jpg


そして、これが深度から作った3次元モデルです。

 face_depthMesh.jpg

 深度情報が離散データなので、サーフェイスがぼこぼこしてしまっています。

離散データであり、かつ縦横軸の解像度に比べ、深度方向の解像度が足りていないためにこのようになってしまいっています。

すなわち、深度方向の解像度を補間して縦横の解像度と同じレベルまで引き上げる処理が必要になります。

次はそれを実装しようと思います。

0  1  

にほんブログ村 ゲームブログ ファイアーエムブレムへ にほんブログ村 デザインブログ コンピュータグラフィックスへ

0.0355 sec

Copyright(C)2006-2018 wsp All Rights Reserved