HEXA BLOG

プログラム

HEXA BLOGプログラム2017.1.11

32とか16とかその6

こんにちは。

 

シュンスケです

 

前回、どれがキレイ(元画像に近い)なのかをプログラムで判断できれば、

数あるパターンから最適なものが見つけられるのではといった話がありました

 

そこで、今回のアプローチは、GA

 

ガーディアンエンジェルではありません。

ギャラクシーエンジェルでもありません。

 

Genetic Algorithmつまり、「遺伝的アルゴリズム」です

 

大まかに説明すると、とある解を求めるにあたって、生物の進化のように、

解候補たちを組み替えながら、正解に近いものは残して、遠いものを淘汰し、

時には突然変異を起こすといった感じで、多くのパターンからより良いものを

導き出す処理方法です

 

と、ここまで来て気付く方も多いと思いますが、正解に近いかどうか、それが今回でいうと、

元画像に近いかどうかということです。

 

その比較方法でお手軽なのがPSNR値を使うものなようなので、今回はそうします。

(ただし、人間の目を相手にするとなると、問題は多いようです)

 

そして今回、果敢にも全ピクセルの全チャンネルに関して、組み換え対象として

実装しました。

つまり、膨大なパターンがあるということです

 

さて、以下実装です。

  1. using System;
  2. using System.Drawing;
  3. using System.Drawing.Imaging;
  4. using System.Windows.Forms;
  5. using System.IO;
  6. using System.Runtime.InteropServices;
  7.  
  8. namespace Pic32bitTo16bitGA
  9. {
  10. class Program : Form
  11. {
  12. struct Gene
  13. {
  14. public static void cross(ref Gene a, ref Gene b)
  15. {
  16. if( a.buf == null || b.buf == null ) return;
  17.  
  18. // 2点交叉
  19. var r = new Random();
  20. var index1 = r.Next(0, (a.buf.Length - 1) / PIXEL_BYTES);
  21. var index2 = r.Next((index1 + 1) / PIXEL_BYTES, b.buf.Length / PIXEL_BYTES);
  22. for( int i = index1; i < index2; ++i ) {
  23. var tmp0 = a.buf[i + 0];
  24. var tmp1 = a.buf[i + 1];
  25. var tmp2 = a.buf[i + 2];
  26. var tmp3 = a.buf[i + 3];
  27. a.buf[i + 0] = b.buf[i + 0];
  28. a.buf[i + 1] = b.buf[i + 1];
  29. a.buf[i + 2] = b.buf[i + 2];
  30. a.buf[i + 3] = b.buf[i + 3];
  31. b.buf[i + 0] = tmp0;
  32. b.buf[i + 1] = tmp1;
  33. b.buf[i + 2] = tmp2;
  34. b.buf[i + 3] = tmp3;
  35. }
  36. }
  37.  
  38. // コンストラクタ
  39. public Gene(int w, int h)
  40. {
  41. buf = new byte[w * h * PIXEL_BYTES];
  42. psnr = -1;
  43. }
  44.  
  45. // ランダムで埋める
  46. public void fillRand()
  47. {
  48. // ランダムで色を埋める
  49. var r = new Random();
  50. r.NextBytes(buf);
  51. }
  52.  
  53. // PSNRを算出して保持
  54. public void calcPSNR(byte[] org)
  55. {
  56. psnr = -1;
  57. if( org == null || buf == null || org.Length != buf.Length || org.Length == 0 ) return;
  58.  
  59. // MSE算出
  60. ulong mse = 0;
  61. for( int i = 0; i < org.Length; ++i ) {
  62. long d = (buf[i] - org[i]);
  63. mse += (ulong)(d * d);
  64. }
  65. mse /= (ulong)org.Length;
  66. if( mse <= 0 ) return;
  67.  
  68. // PSNR算出
  69. psnr = (10 * Math.Log10((double)(255 * 255) / (double)mse));
  70. }
  71.  
  72. // 突然変異
  73. public void mutation()
  74. {
  75. if( buf == null ) return;
  76.  
  77. var r = new Random();
  78. var index = r.Next(0, buf.Length / PIXEL_BYTES);
  79. buf[index + 0] = (byte)r.Next(0, 256);
  80. buf[index + 1] = (byte)r.Next(0, 256);
  81. buf[index + 2] = (byte)r.Next(0, 256);
  82. buf[index + 3] = (byte)r.Next(0, 256);
  83. }
  84.  
  85. // 値を設定
  86. public void set(Gene g)
  87. {
  88. if( g.buf == null ) return;
  89.  
  90. if( buf == null || buf.Length != g.buf.Length ) {
  91. buf = new byte[g.buf.Length];
  92. }
  93.  
  94. Array.Copy(g.buf, buf, buf.Length);
  95. psnr = g.psnr;
  96. }
  97.  
  98. public byte[] buf;
  99. public double psnr;
  100. }
  101.  
  102. private const int PIXEL_BYTES = 4; //!< 1ピクセルあたりのバイト数
  103.  
  104. // エントリポイント
  105. static void Main(string[] args)
  106. {
  107. // 入力受付
  108. Console.WriteLine("please input picture path...");
  109. var srcPath = Console.ReadLine();
  110.  
  111. // ファイル存在確認
  112. if( !File.Exists(srcPath) ) {
  113. Console.WriteLine("file not exists.");
  114. return;
  115. }
  116.  
  117. // 画像読み込み
  118. byte[] buf = null;
  119. int width = 0;
  120. int height = 0;
  121. using( var img = Image.FromFile(srcPath, true) as Bitmap ) {
  122. if( img == null ) {
  123. Console.WriteLine("file type is not support.");
  124. return;
  125. }
  126. // メモリに保持
  127. var dat = img.LockBits(new Rectangle(Point.Empty, img.Size), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
  128. width = img.Width;
  129. height = img.Height;
  130. buf = new byte[width * height * PIXEL_BYTES];
  131. Marshal.Copy(dat.Scan0, buf, 0, buf.Length);
  132.  
  133. // 解放
  134. img.UnlockBits(dat);
  135. }
  136. if( buf == null ) {
  137. Console.WriteLine("buf load failed.");
  138. return;
  139. }
  140.  
  141. // フォーム開始
  142. Application.Run(new Program(ref buf, width, height));
  143. }
  144.  
  145. // コンストラクタ
  146. public Program(ref byte[] buf, int w, int h)
  147. {
  148. if( buf == null ) return;
  149.  
  150. _org = buf;
  151. _buf = new byte[buf.Length];
  152. _img = new Bitmap(w, h);
  153.  
  154. // 第1世代生成
  155. for( int i = 0; i < _curGen.Length; ++i ) {
  156. _curGen[i] = new Gene(w, h);
  157. _curGen[i].fillRand();
  158. }
  159.  
  160. // フォームのサイズ設定
  161. ClientSize = new Size(w, h * 2);
  162.  
  163. // フォント作成
  164. _font = new Font(Font.Name, 10);
  165. _fontBrush = new SolidBrush(ForeColor);
  166.  
  167. // 描画更新開始
  168. _sec = 0;
  169. _refreshTimer.Interval = 1000;
  170. _refreshTimer.Tick += new EventHandler(refresh);
  171. _refreshTimer.Start();
  172.  
  173. // 処理更新開始
  174. _updateTimer.Interval = 1;
  175. _updateTimer.Tick += new EventHandler(update);
  176. _updateTimer.Start();
  177. }
  178.  
  179. // 描画時処理
  180. protected override void OnPaint(PaintEventArgs e)
  181. {
  182. base.OnPaint(e);
  183. if( _img == null ) return;
  184.  
  185. // 元画像描画
  186. if( _org != null ) {
  187. var newDat = _img.LockBits(new Rectangle(Point.Empty, _img.Size), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
  188. Marshal.Copy(_org, 0, newDat.Scan0, _org.Length);
  189. _img.UnlockBits(newDat);
  190. e.Graphics.DrawImage(_img, 0, 0);
  191. }
  192.  
  193. // 生成画像描画
  194. if( _buf != null ) {
  195. var newDat = _img.LockBits(new Rectangle(Point.Empty, _img.Size), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
  196. Marshal.Copy(_buf, 0, newDat.Scan0, _buf.Length);
  197. _img.UnlockBits(newDat);
  198. e.Graphics.DrawImage(_img, 0, _img.Size.Height);
  199. }
  200.  
  201. // フレーム数表示
  202. e.Graphics.DrawString("sec : " + _sec, _font, _fontBrush, 0, 0);
  203.  
  204. // 世代数表示
  205. e.Graphics.DrawString("gen : " + _genCnt, _font, _fontBrush, 100, 0);
  206. }
  207.  
  208. // 描画更新
  209. private void refresh(object sender, EventArgs e)
  210. {
  211. // 保持したトップを描画バッファに設定
  212. if( _topGen.buf != null ) {
  213. Array.Copy(_topGen.buf, _buf, _buf.Length);
  214. }
  215.  
  216. // 画面をリフレッシュ
  217. ++_sec;
  218. Invalidate();
  219. }
  220.  
  221. // 処理更新
  222. private void update(object sender, EventArgs e)
  223. {
  224. // 評価値算出
  225. for( int i = 0; i < _curGen.Length; ++i ) {
  226. _curGen[i].calcPSNR(_org);
  227. }
  228.  
  229. // 評価値でソート
  230. Array.Sort(_curGen, (a, b) => {
  231. if( a.psnr == b.psnr ) return 0;
  232.  
  233. return (b.psnr > a.psnr) ? 1 : -1;
  234. });
  235.  
  236. // 1位を保持
  237. _topGen.set(_curGen[0]);
  238.  
  239. // 上位いくつかはそのまま残す
  240. var newIndex = 0;
  241. for( int i = 0; i < 2; ++newIndex, ++i ) {
  242. _nextGen[newIndex].set(_curGen[i]);
  243. }
  244.  
  245. while( newIndex < _nextGen.Length ) {
  246. // 交叉対象1を選出(上位が選ばれやすいように)
  247. _cross1Buf.set(_curGen[0]);
  248. for( int i = 1; i < _curGen.Length; ++i ) {
  249. var rate = _selectRnd.Next(0, 100);
  250. if( rate < 10 ) {
  251. _cross1Buf.set(_curGen[i]);
  252. break;
  253. }
  254. }
  255.  
  256. // 交叉対象2を選出(上位が選ばれやすいように)
  257. _cross2Buf.set(_curGen[1]);
  258. for( int i = 2; i < _curGen.Length; ++i ) {
  259. var rate = _selectRnd.Next(0, 100);
  260. if( rate < 10 ) {
  261. _cross2Buf.set(_curGen[i]);
  262. break;
  263. }
  264. }
  265.  
  266. // 交叉
  267. Gene.cross(ref _cross1Buf, ref _cross2Buf);
  268. _nextGen[newIndex].set(_cross1Buf);
  269. ++newIndex;
  270. if( _nextGen.Length <= newIndex ) break;
  271.  
  272. _nextGen[newIndex].set(_cross2Buf);
  273. ++newIndex;
  274. if( _nextGen.Length <= newIndex ) break;
  275.  
  276. // 突然変異対象を選出
  277. var mutIndex = _selectRnd.Next(0, _curGen.Length);
  278. _nextGen[newIndex].set(_curGen[mutIndex]);
  279. _nextGen[newIndex].mutation();
  280. ++newIndex;
  281. if( _nextGen.Length <= newIndex ) break;
  282.  
  283. // そのままコピー
  284. var copyIndex = _selectRnd.Next(0, _curGen.Length);
  285. _nextGen[newIndex].set(_curGen[copyIndex]);
  286. ++newIndex;
  287. if( _nextGen.Length <= newIndex ) break;
  288. }
  289.  
  290. // 次世代を現世代に移す
  291. for( int i = 0; i < _nextGen.Length; ++i ) {
  292. _curGen[i].set(_nextGen[i]);
  293. }
  294. ++_genCnt;
  295. }
  296.  
  297. private Bitmap _img = null;
  298. private byte[] _org = null;
  299. private byte[] _buf = null;
  300.  
  301. private Timer _refreshTimer = new Timer();
  302. private Timer _updateTimer = new Timer();
  303. private Font _font = null;
  304. private Brush _fontBrush = null;
  305. private int _sec = 0;
  306. private int _genCnt = 0;
  307.  
  308. private Gene[] _curGen = new Gene[64];
  309. private Gene[] _nextGen = new Gene[64];
  310. private Random _selectRnd = new Random();
  311. private Gene _cross1Buf = new Gene();
  312. private Gene _cross2Buf = new Gene();
  313. private Gene _topGen = new Gene();
  314. }
  315. }

 

このままでは処理速度に難がありそうですが、実行してみます

 

ウィンドウの中で、上が目指す画像で、下が完全ランダム値からはじめて、

最終的には同じ絵になって欲しい部分です。

20170111_01

はじめの方

20170111_02

 

約50分後

 

う、うーん…ダメだ

さすがにパターンが多すぎたようです。

それに、交叉や突然変異の確率、評価処理もかなー、 見直しが必要そうです。

というか、そもそも全ピクセルランダムスタートで辿り着けるのか。。。

 

ただ、薄っすらと緑っぽく近づいてきてはいるので、アプローチの方向はまだ捨てずに

おこうと 思いますが、今回はここまで

 

では

RECRUIT

大阪・東京共にスタッフを募集しています。
特にキャリア採用のプログラマー・アーティストに興味がある方は下のボタンをクリックしてください

RECRUIT SITE 

S