MENU閉じる

HEXA BLOG

プログラム

HEXA BLOGプログラム2017.8.22

32とか16とかその7

こんにちは。

 

シュンスケです

 

前回でGAの処理が確認出来たので、いよいよ減色に取り入れて、

誤差拡散を超えてみようと思います

 

まずは、前回も記載した方針の通り、修正を行っていきます。

.使用する色を1チャンネル16色に制限する

.現在開始画像が完全にランダムな絵だったのを、誤差拡散等で

 既にキレイな状態を出発点にする

.評価式にPSNRを使用しているのを、SSIMに変更する

.今までの突然変異だと変化が大きすぎて細やかな改善に向かないので、

 ごく一部の変異に修正

.改善画像出力機能の作成

 

評価式SSIMは、PSNRよりも人間の認識に近い画像評価方法として出てきたもので、

画像の性質次第では事前にガウシアンフィルタによるノイズ除去を行うようです

が、減色の性質や今回のテスト画像の性質から無しで行ってみようと思います。

また、今回はチャンネル単位でSSIMを算出してその平均を画像の評価としています

が、横1ライン毎の各チャンネルで算出したものの平均にするなど、細かく分ける

のも有効そうです

 

では、実行結果。

まずはツールの様子

20170823_ga_window

 

そして、作成された画像

20170823_ga_org

20170823_ga_new

上が誤差拡散、下がそれを元に改善した画像

 

変わっ……った

 

確かに数値は上がっているのですが、元が良いのもあり正直わかりません

 

わかりやすいように、差分画像を作成しました。

(差分0を0xffとしてわかりやすくしています)

 

20170823_ga_diff

 

こう見ると、まばらにでも思ったより多くの箇所が改善されている結果、

評価値の向上に繋がっているようです

後は、どういった画像に使用するのか、使える時間はどのくらいかで

調整をしていけば、少なくとも SSIM基準による画質向上は見込めそうですが、

なかなか実感できる程を目指すのは難しいようです。

誤差拡散のお手軽さを考えると、この一線を超えるかは、 どこまでこだわるか

との相談になりそうですね

 

というわけで長きに渡って減色について続けてきたシリーズですが、

ここで一旦の区切りとします。

 

最後に今回のソースコードを貼っておきます。

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;
using System.IO;
using System.Runtime.InteropServices;

namespace Pic32bitTo16bitGA
{
	class Program : Form
	{
		struct Gene
		{
			// 2点交叉
			public static void cross(ref Gene a, ref Gene b)
			{
				if( a.buf == null || b.buf == null ) return;

				var len = a.buf.Length;
				var r = new Random();
				var index1 = r.Next(0, len);
				var index2 = index1;
				if( index1 > len / 2 ) {
					index1 = r.Next(0, index2);
				}
				else {
					index2 = r.Next(index1 + 1, len);
				}
				for( int i = index1; i <= index2; ++i ) {
					var tmp = a.buf[i];
					a.buf[i] = b.buf[i];
					b.buf[i] = tmp;
				}
			}

			// SSIM算出
			private static double calcSSIM(byte[] x, byte[] y, int start, int end, int offset)
			{
				if( x == null || y == null || x.Length != y.Length || x.Length == 0 ) return 0;

				// 事前計算
				double c1 = (0.01 * 255) * (0.01 * 255);
				double c2 = (0.03 * 255) * (0.03 * 255);
				double avgX = 0, avgY = 0;	//!< 平均
				double sdX = 0, sdY = 0;	//!< 標準偏差
				double cov = 0;				//!< 共分散
				double len = 0;
				for( int i = start; i < end; i += offset, ++len ) {
					avgX += x[i];
					avgY += y[i];
				}
				avgX /= len;
				avgY /= len;
				len = 0;
				for( int i = start; i < end; i += offset, ++len ) {
					double dX = x[i] - avgX;
					double dY = y[i] - avgY;
					sdX += dX * dX;
					sdY += dY * dY;
					cov += dX * dY;
				}
				sdX = Math.Sqrt(sdX / len);
				sdY = Math.Sqrt(sdY / len);
				cov /= len;

				// SSIM算出
				return ((2 * avgX * avgY + c1) * (2 * cov + c2)) / ((avgX * avgX + avgY * avgY + c1) * (sdX * sdX + sdY * sdY + c2));
			}

			// コンストラクタ
			public Gene(int w, int h)
			{
				buf = new byte[w * h * PIXEL_BYTES];
				ssim = 0;
			}

			// 白で埋める
			public void fillWhite()
			{
				if( buf == null ) return;

				// 白色を埋める
				for( int i = 0; i < buf.Length; ++i ) {
					buf[i] = 0xff;
				}
			}

			// SSIM算出
			public void calcSSIM(byte[] org)
			{
				ssim = 0;
				if( org == null || buf == null || org.Length != buf.Length || org.Length == 0 ) return;

				var ssimR = calcSSIM(org, buf, 0, org.Length, 4);
				var ssimG = calcSSIM(org, buf, 1, org.Length, 4);
				var ssimB = calcSSIM(org, buf, 2, org.Length, 4);
				var ssimA = calcSSIM(org, buf, 3, org.Length, 4);

				// SSIM算出
				ssim = (ssimR + ssimG + ssimB + ssimA) / 4.0;
			}

			// 突然変異
			public void mutation()
			{
				if( buf == null ) return;

				var r = new Random();
				var index = r.Next(0, buf.Length);
				var c = r.Next(0, RGBA4444_COLORS.Length);
				buf[index] = RGBA4444_COLORS[c];
			}

			// 値を設定
			public void set(Gene g)
			{
				set(g.buf);
				ssim = g.ssim;
			}

			// バッファを元に値を設定
			public void set(byte[] src)
			{
				if( src == null ) return;

				if( buf == null || buf.Length != src.Length ) {
					buf = new byte[src.Length];
				}

				Array.Copy(src, buf, buf.Length);
			}

			public byte[]	buf;
			public double	ssim;
		}

		// 16bitカラーテーブル
		private static readonly byte[] RGBA4444_COLORS = new byte[] {
			0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff
		};
		private const int PIXEL_BYTES				= 4;								//!< 1ピクセルあたりのバイト数

		// エントリポイント
		static void Main(string[] args)
		{
			// 元画像パス入力受付
			Console.WriteLine("please input org picture path...");
			var orgPath = Console.ReadLine();

			// ファイル存在確認
			if( !File.Exists(orgPath) ) {
				Console.WriteLine("file not exists.");
				return;
			}

			// 画像読み込み
			byte[] orgBuf;
			int orgW;
			int orgH;
			if( readImg(out orgBuf, out orgW, out orgH, orgPath) ) {
				Console.WriteLine("org picture load failed.");
				return;
			}

			// 開始画像パス入力受付
			Console.WriteLine("please input start picture path...");
			var startPath = Console.ReadLine();

			// ファイル存在確認
			if( !File.Exists(startPath) ) {
				Console.WriteLine("file not exists.");
				return;
			}

			// 画像読み込み
			byte[] startBuf;
			int startW;
			int startH;
			if( readImg(out startBuf, out startW, out startH, startPath) ) {
				Console.WriteLine("start picture load failed.");
				return;
			}

			// サイズ確認
			if( orgW != startW ||
				orgH != startH ||
				orgBuf.Length != startBuf.Length ) {
				Console.WriteLine("org picture size and start picture size are different.");
				return;
			}

			// 出力先パスを作成
			var dstPath = orgPath.Substring(0, orgPath.Length - Path.GetFileName(orgPath).Length) + Path.GetFileNameWithoutExtension(orgPath) + "_new" + Path.GetExtension(orgPath);

			// フォーム開始
			Application.Run(new Program(orgBuf, startBuf, orgW, orgH, dstPath));
		}

		// 画像読み込み
		static bool readImg(out byte[] dst, out int width, out int height, string srcPath)
		{
			dst = null;
			width = 0;
			height = 0;
			using( var img = Image.FromFile(srcPath, true) as Bitmap ) {
				if( img == null ) {
					Console.WriteLine("file type is not support.");
					return true;
				}
				// メモリに保持
				var dat = img.LockBits(new Rectangle(Point.Empty, img.Size), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
				width = img.Width;
				height = img.Height;
				dst = new byte[width * height * PIXEL_BYTES];
				Marshal.Copy(dat.Scan0, dst, 0, dst.Length);

				// 解放
				img.UnlockBits(dat);
			}
			return false;
		}

		// コンストラクタ
		public Program(byte[] org, byte[] start, int w, int h, string dstPath)
		{
			if( org == null || start == null || dstPath == null ) return;

			_org = org;
			_start = start;
			_img = new Bitmap(w, h);
			_dstPath = dstPath;

			// 第1世代生成
			_startGen.set(_start);
			_startGen.calcSSIM(_org);
			for( int i = 0; i < _curGen.Length; ++i ) {
				_curGen[i] = new Gene(w, h);
				_curGen[i].set(_startGen);
			}

			// フォームのサイズ設定
			ClientSize = new Size(w, h * 2);

			// フォント作成
			_font = new Font(Font.Name, 10);
			_fontBrush = new SolidBrush(ForeColor);

			// 出力ボタン作成、登録
			_outBtn = new Button();
			_outBtn.Location = new Point(300, 0);
			_outBtn.Size = new Size(50, 20);
			_outBtn.Text = "出力";
			_outBtn.Click += new EventHandler(onClickOutBtn);
			Controls.Add(_outBtn);

			// 描画更新開始
			_sec = 0;
			_refreshTimer.Interval = 1000;
			_refreshTimer.Tick += new EventHandler(refresh);
			_refreshTimer.Start();

			// 処理更新開始
			_updateTimer.Interval = 1;
			_updateTimer.Tick += new EventHandler(update);
			_updateTimer.Start();
		}

		// 描画時処理
		protected override void OnPaint(PaintEventArgs e)
		{
			base.OnPaint(e);
			if( _img == null ) return;

			// 元画像描画
			if( _org != null ) {
				var newDat = _img.LockBits(new Rectangle(Point.Empty, _img.Size), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
				Marshal.Copy(_org, 0, newDat.Scan0, _org.Length);
				_img.UnlockBits(newDat);
				e.Graphics.DrawImage(_img, 0, 0);
			}

			// 生成画像描画
			if( _topGen.buf != null ) {
				var newDat = _img.LockBits(new Rectangle(Point.Empty, _img.Size), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
				Marshal.Copy(_topGen.buf, 0, newDat.Scan0, _topGen.buf.Length);
				_img.UnlockBits(newDat);
				e.Graphics.DrawImage(_img, 0, _img.Size.Height);
			}

			// フレーム数表示
			e.Graphics.DrawString("sec : " + _sec, _font, _fontBrush, 0, 0);

			// 世代数表示
			e.Graphics.DrawString("gen : " + _genCnt, _font, _fontBrush, 0, 10);

			// SSIM表示
			e.Graphics.DrawString("start ssim : " + _startGen.ssim, _font, _fontBrush, 100, 0);
			e.Graphics.DrawString("top   ssim : " + _topGen.ssim, _font, _fontBrush, 100, 10);
		}

		// 描画更新
		private void refresh(object sender, EventArgs e)
		{
			// 画面をリフレッシュ
			++_sec;
			Invalidate();
		}

		// 出力ボタン押下時処理
		private void onClickOutBtn(object sender, EventArgs e)
		{
			if( _img == null || _dstPath == null ) return;

			// トップの画像を書き出し
			// 出力画像作成
			var newImg = new Bitmap(_img.Width, _img.Height);
			var newDat = newImg.LockBits(new Rectangle(Point.Empty, newImg.Size), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
			Marshal.Copy(_topGen.buf, 0, newDat.Scan0, _topGen.buf.Length);
			newImg.UnlockBits(newDat);
			newDat = null;

			// 保存
			newImg.Save(_dstPath);
			newImg.Dispose();
			newImg = null;
		}

		// 処理更新
		private void update(object sender, EventArgs e)
		{
			// 次世代初期化
			for( int i = 0; i < _nextGen.Length; ++i ) {
				_nextGen[i].fillWhite();
			}

			// 評価値算出
			for( int i = 0; i < _curGen.Length; ++i ) {
				_curGen[i].calcSSIM(_org);
			}

			// 評価値でソート
			Array.Sort(_curGen, (a, b) => {
				if( a.ssim == b.ssim ) return 0;

				return (b.ssim > a.ssim) ? 1 : -1;
			});

			// 1位を保持
			_topGen.set(_curGen[0]);

			// 上位いくつかはそのまま残す
			var newIndex = 0;
			for( int i = 0; i < 2 && i < _nextGen.Length; ++newIndex, ++i ) {
				_nextGen[newIndex].set(_curGen[i]);
			}

			while( newIndex < _nextGen.Length ) {
				// 交叉対象1を選出(上位が選ばれやすいように)
				_cross1Buf.set(_curGen[0]);
				for( int i = 1; i < _curGen.Length; ++i ) {
					var rate = _selectRnd.Next(0, 100);
					if( rate < 10 ) {
						_cross1Buf.set(_curGen[i]);
						break;
					}
				}

				// 交叉対象2を選出(上位が選ばれやすいように)
				_cross2Buf.set(_curGen[1]);
				for( int i = 2; i < _curGen.Length; ++i ) {
					var rate = _selectRnd.Next(0, 100);
					if( rate < 10 ) {
						_cross2Buf.set(_curGen[i]);
						break;
					}
				}

				// 交叉
				Gene.cross(ref _cross1Buf, ref _cross2Buf);
				_nextGen[newIndex].set(_cross1Buf);
				++newIndex;
				if( _nextGen.Length <= newIndex ) break;

				_nextGen[newIndex].set(_cross2Buf);
				++newIndex;
				if( _nextGen.Length <= newIndex ) break;

				// 突然変異対象を選出
				var mutIndex = _selectRnd.Next(0, _curGen.Length);
				_nextGen[newIndex].set(_curGen[mutIndex]);
				_nextGen[newIndex].mutation();
				++newIndex;
				if( _nextGen.Length <= newIndex ) break;

				// そのままコピー
				var copyIndex = _selectRnd.Next(0, _curGen.Length);
				_nextGen[newIndex].set(_curGen[copyIndex]);
				++newIndex;
				if( _nextGen.Length <= newIndex ) break;
			}

			// 次世代を現世代に移す
			for( int i = 0; i < _nextGen.Length; ++i ) {
				_curGen[i].set(_nextGen[i]);
			}
			++_genCnt;
		}

		private Bitmap	_img			= null;
		private byte[]	_org			= null;
		private byte[]	_start			= null;
		private string	_dstPath		= null;

		private Timer	_refreshTimer	= new Timer();
		private Timer	_updateTimer	= new Timer();
		private Button	_outBtn			= null;
		private Font	_font			= null;
		private Brush	_fontBrush		= null;
		private int		_sec			= 0;
		private int		_genCnt			= 0;

		private Gene[]	_curGen			= new Gene[128];
		private Gene[]	_nextGen		= new Gene[128];
		private Random	_selectRnd		= new Random();
		private Gene	_cross1Buf		= new Gene();
		private Gene	_cross2Buf		= new Gene();
		private Gene	_topGen			= new Gene();
		private Gene	_startGen		= new Gene();
	}
}

 

では

RECRUIT

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

RECRUIT SITE