はじめに
カメラなどで撮った自然画像を絵画のように変えてみたいことがあります。著名な市販レタッチソフトや、他の有償無償の画像ソフトにもこの種の機能が含まれています。絵画調化専門のソフトもあります。ここでは比較的単純な手法で、油彩画に似た絵画調の画像を作ってみました。
- 完成版のアプレットを見る
対象読者
レタッチソフトで写真を絵画風に加工する仕組みに興味を持ち、自分で絵画調化ソフトを作ってみたい人。
必要な環境
J2SE 5.0を使っていますが、それ以前のバージョンでも大丈夫です。
絵画調化とは
フォトレタッチソフトには、アーティスティック・フィルタ(Artistic Filter)と呼ぶ、写真を芸術的な感じに変換する機能があります。一般的には、このような処理を「絵画調化」といいます。
絵画調化には、ソフトを使いながらインタラクティブに、制作者の意図に沿った完成度の高い作品を生成する方法(参考資料1. 2. 3. など)と、パラメータを与えるだけのソフトによる完全な自動生成があります。
絵画調化ソフトは奥が深く、SIGGRAPH(米国情報処理学会Association for Computing MachineryのSpecial Interest Group on Computer Graphics )などに多くの論文が発表されています。これらのソフトは、原画像を解析し、絵画調化の際のストローク・タッチなどを最適化し、原画像を加工します。目的とする絵画には、油彩画、水彩画、ペン画などがあり、それぞれ異なった手法が用いられます。
簡単な方法は「減色」と「非線形平滑化」
水彩画や油彩画は、絵の具を用いて筆で描きます。絵の具の本数は限られていますので、それらを混ぜ合わせたとしても、使われる色の種類はあまり多くありません。この理由で、まず、減色することが考えられます。rgbで言えば、階調を減らすことです。
筆には、ある程度の大きさがあり、あまり細かい部分は忠実に描写できません。絵は省略することでもあり、ボカしのテクニックも使われています。そこで、平滑化することが考えられます。しかし、単なる平滑化ですと、原画像の僅かな変化部分も、それなりに結果に反映される欠点がありますので、非線形の平滑化手法(最頻値フィルタやメディアンフィルタなど)を用いてその感じを出します。
プログラムの構成
プログラムは、次の部分からなっています。
- 原画像を読み込み、画面の左に表示します。
- 原画像を拡張します。
- 絵画調化を行います。
- ヒストグラムを採るためのカウンタをクリアします。
- 1階調当りのrgb値の幅を
base
と呼び、steps
から計算します。 - 原画像の階調を減らしながらヒストグラムを採ります。
- 頻度が最大となるrgb値を求める。
- 上記rgb値で描画します。
counter[k][l][m]
の定義で兼用しています。添字K
、l
、m
の大きさは、それぞれ減色化の階調steps
にします。base=256/steps
のように整数間除算を行うと、端数が切り捨てになるので、base=(int)Math.ceil(256.0/steps)
で切り上げています。切り捨てをすると、red1/base
などが4になる恐れがあり、実行時エラーが発生します。steps
とbase
の関係は下表の通りです。減色化の階調(steps) | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
階調当りの幅(base) | 128 | 86 | 64 | 52 | 43 | 37 | 32 |
x
方向(横方向)とy
方向(縦方向)を、それぞれ-region/2
からregion/2
まで変化させて(region=3
のときは、-1
から1
まで、region=5
のときは、-2
から2
まで)、注目するピクセルの周辺のrgb値に対応するカウンタをインクリメントします。base
で整数間除算し、カウンタの添字klm
値に用いることにより行います。図2に、その関係を示します。図ではsteps=4
を用い(base=64
となります)、例としてredを取り上げていますが、green、blueについても、同様に行います。counter[k][l][m]
に対して、klm
値を、それぞれ0
からsteps-1
の間でスキャンし、その最大値を求めます。そのときのklm
値をk_max
、l_max
、m_max
とします。red1=k_max*base+base/2
steps=4(base=64)
の場合について、k_max
からred1
が求まり、減色化が行われる例を示しています。l_max
からgreen1
、m_max
からblue1
への変換も同様に行います。プログラム
import java.applet.Applet; import java.awt.*; import java.awt.image.*; import java.awt.event.*; public class Artistic extends Applet implements ItemListener,ActionListener{ static final int WIDTH=320; static final int HEIGHT=240; static final int SIZE=76800; static final int X0=10; static final int Y0=50; Choice choice1,choice2,choice3; Button button1,button2,button3,button4; //デフォルト値の設定 String filename="gent.jpg"; //原画像のファイル名 int steps=5; //減色化の階調 int region=3; //最頻値取得の範囲 int brightness=0; //明るさの補正値 public void init(){ //-------------------- チョイス関係準備 ---------------------- choice1=new Choice(); choice1.add("(原画像の指定)"); choice1.add("biei.jpg"); choice1.add("cheskykrumlov.jpg"); choice1.add("gent.jpg"); choice1.add("kanazawa.jpg"); choice1.add("praha.jpg"); choice1.add("xprovence.jpg"); choice1.addItemListener(this); add(choice1); choice2=new Choice(); choice2.add("(減色化の階調)"); choice2.add("2階調"); choice2.add("3階調"); choice2.add("4階調"); choice2.add("5階調"); choice2.add("6階調"); choice2.add("7階調"); choice2.add("8階調"); choice2.addItemListener(this); add(choice2); choice3=new Choice(); choice3.add("(最頻値取得の範囲)"); choice3.add("3x3"); choice3.add("5x5"); choice3.addItemListener(this); add(choice3); //--------------------- ボタン関係準備 ----------------------- button1=new Button("実 行"); button1.addActionListener(this); add(button1); button2=new Button("明るく"); button2.addActionListener(this); button2.setBackground(new Color(240,240,240)); add(button2); button3=new Button("暗 く"); button3.addActionListener(this); button3.setBackground(new Color(92,92,92)); button3.setForeground(Color.white); add(button3); button4=new Button("標 準"); button4.addActionListener(this); add(button4); } //------------------- チョイス関係メソッド ---------------------- public void itemStateChanged(ItemEvent ie){ if(choice1.getSelectedIndex()!=0) filename=choice1.getSelectedItem(); if(choice2.getSelectedIndex()!=0) steps=choice2.getSelectedIndex()+1; if(choice3.getSelectedIndex()==1) region=3; //3x3 if(choice3.getSelectedIndex()==2) region=5; //5x5 } //-------------------- ボタン関係メソッド ----------------------- public void actionPerformed(ActionEvent ae){ if(ae.getSource()==button1) repaint(); if(ae.getSource()==button2){ brightness+=20; //明るくする repaint(); } if(ae.getSource()==button3){ brightness-=20; //暗くする repaint(); } if(ae.getSource()==button4){ brightness=0; //標準に戻す repaint(); } } public void paint(Graphics g){ Image img_src; int[][] pixels=new int[HEIGHT+4][WIDTH+4]; //画像ファイルを読み込み、Image画像img_srcにする img_src=readImageFile(filename); //画像img_srcを左に表示する g.drawImage(img_src,X0,Y0,this); //Image画像img_srcの四辺を拡張し、 //二次元ピクセルデータに変換する create2DPixelsDataExpanded(img_src,pixels); //二次元ピクセルデータを用いて絵画調化し、右に描画する drawArtistic(pixels,steps,region,brightness,X0+WIDTH+20,Y0); //明るさ補正brightnessを右下に表示する g.drawString("明るさ補正= "+String.valueOf(brightness), X0+WIDTH+20,Y0+HEIGHT+30); } //絵画調化するメソッド private void drawArtistic(int[][] _pixels, int _steps, int _region, int _brightness, int x0, int y0){ int i,j,k,l,m; int red,green,blue,red1,green1,blue1; Color color; int base=(int)Math.ceil(256.0/_steps); Graphics g=getGraphics(); for(j=2;j<HEIGHT+2;j++) for(i=2;i<WIDTH+2;i++){ //counter[][][]を設定し、ゼロにクリアする int[][][] counter=new int[_steps][_steps][_steps]; //counterによりヒストグラムを取得する for(k=-_region/2;k<=_region/2;k++) for(l=-_region/2;l<=_region/2;l++){ color=new Color(_pixels[j+l][i+k]); red=color.getRed()+_brightness; green=color.getGreen()+_brightness; blue=color.getBlue()+_brightness; if(red>255) red=255; if(green>255) green=255; if(blue>255) blue=255; if(red<0) red=0; if(green<0) green=0; if(blue<0) blue=0; counter[red/base][green/base][blue/base]++; } //最大頻度を求めて、そのrgb値を求める int counter_max=0; int k_max=0,l_max=0,m_max=0; for(k=0;k<_steps;k++) for(l=0;l<_steps;l++) for(m=0;m<_steps;m++){ if(counter[k][l][m]>counter_max){ counter_max=counter[k][l][m]; k_max=k; l_max=l; m_max=m; } } red1=k_max*base+base/2; green1=l_max*base+base/2; blue1=m_max*base+base/2; g.setColor(new Color(red1,green1,blue1)); g.drawRect(x0+i-2,y0+j-2,0,0); } } //Image画像imgの四辺を拡張し、 //二次元ピクセルデータに変換するメソッド public void create2DPixelsDataExpanded (Image img, int[][] pixels){ int i,j; int[] rgb=new int[SIZE]; //画像imgを一次元RGBデータrgb[]にする PixelGrabber grabber=new PixelGrabber(img,0,0,WIDTH,HEIGHT,rgb,0,WIDTH); try{ grabber.grabPixels(); }catch(InterruptedException e){} //一次元RGBデータrgb[]を二次元ピクセルデータ //pixels[][]の中心に設定する int n=0; for(j=0;j<HEIGHT;j++) for(i=0;i<WIDTH;i++) pixels[j+2][i+2]=rgb[n++]; //上辺をコピーする for(j=2;j<=3;j++) for(i=2;i<WIDTH+2;i++) pixels[3-j][i]=pixels[j][i]; //下辺をコピーする for(j=HEIGHT+1;j>=HEIGHT;j--) for(i=2;i<WIDTH+2;i++) pixels[2*HEIGHT+3-j][i]=pixels[j][i]; //左辺をコピーする for(i=2;i<=3;i++) for(j=0;j<HEIGHT+4;j++) pixels[j][3-i]=pixels[j][i]; //右辺をコピーする for(i=WIDTH+1;i>=WIDTH;i--) for(j=0;j<HEIGHT+4;j++) pixels[j][2*WIDTH+3-i]=pixels[j][i]; } //画像ファイルを読み込みImageクラスの画像にするメソッド public Image readImageFile(String _filename){ Image img=getImage(getDocumentBase(),_filename); MediaTracker mtracker=new MediaTracker(this); mtracker.addImage(img,0); try{ mtracker.waitForAll(); }catch(Exception e){} return img; } }
プログラムの使い方
アプレットを実行すると、上部に左から[原画像のファイル名][減色化の階調][最頻値取得の範囲]の各ドロップダウン選択ボックスと、[実行][明るく][暗く][標準]ボタンが表示され、下には左に原画像、右に絵画調化した画像が表示されます。
絵画調化した画像の下には、その時の明るさ補正量が表示されます。
最初は、デフォルトの原画像とデフォルトの条件(階調は[5階調]、最頻値取得範囲は[3 x 3])での絵画調化画像が表示されますので、選択ボックスで任意の原画像を選び、階調や範囲を変えて[実行]ボタンをクリックして下さい。[明るく][暗く]などの変更は、選択ボックスの条件を変えても設定が残っています。明るさをデフォルト状態に戻したい場合は、[標準]ボタンをクリックして下さい。
原画像によって、最適の階調があります。一般に多階調の方がきれいに見えますが、明るさを調整しながら階調を下げると、面白い効果が得られる場合があります。最頻値を求める範囲を大きく([5 x 5])取ると、表現が荒っぽくなり、絵画らしく見えますが、細かい描写は失われます。
プログラムの実行結果
下図で左上から、「原画像ファイル名」「減色のための階層数」「最頻値を求める周辺データの範囲」が示されています。[実行]ボタンをクリックすると、以上の選択された条件で絵画調化が行われますが、暗いと思われたので、[明るく]を一回クリックしてあります。右下の表示で、明るさ補正がされていることが分かります。
まとめ
著名な市販ソフトや専用ソフトは、かなりの歴史があり、高度の技術によって、非常に良くできています。素人が簡単に真似できるものではありません。しかし、それらを実際に使ってみると、表現がややオーバーであったり、原画像を軽視していたりして、必ずしも満足な結果が得られません。
ここで紹介した手法は簡単過ぎて、ひいき目に見ても、ようやく「絵画調かな」という程度ですが、自分で作った喜びが味わえます。これを出発点にして、さらなる改良をされることを望みます。
参考資料
この記事は、筆者のウエブサイト『Visual C++ 6.0を用いた易しい画像処理(7)-画像の絵画調化(油絵化の場合)-(ただし、現在はVisual C++ 2005 Express Edition 版に改定)』を改良し、Java言語に書き直して、分かりやすく加筆したものです。
最初のきっかけとなった文献は、
- C Magazine 1993年7月号 『特集:グラフィックを操る画像処理プログラミング Part2.フィルタリング』 堀江郁弥 著、ソフトバンク パブリッシング、1993年7月
です。市販ソフトを使ってインタラクティブに絵画調化する手法については、
- 『写真を素材にPhotoshopで描くデジタル絵画』 HAL_・桑島幸男・甲田正治・岩渕泰治・酒井和男・かめむらしゅんじ 著、パレット 編、毎日コミュニケーションズ、2003年7月
- 『写真を素材に始めるデジタル水彩画入門―PhotoshopやPainterで描く7人の作家による水彩画の世界』 藤田修一・小野寺浩二・細井敏昭・田宮彩・城谷俊也・横井由美子・渡部政人 著、パレット 編、毎日コミュニケーションズ、2004年7月
- 『フォトショップ美術部 趣味の絵画』 古岡ひふみ 著、エムディエヌコーポレーション、2004年1月
などがあります。