DeepLab学習用画像の要件

#概要
DeepLabで独自のモデルを学習させようとする場合に必要な学習用画像の要件をまとめる。
当記事では学習結果に影響を及ぼす画像の質やラベルマスクの精度までは言及しない。

#前提
当記事では、DeepLabv3+においてPASCAL VOC 2012のデータセットに準じた画像セットを用意し、deeplab/datasets/build_voc2012_data.py でTFRecordに変換して学習に供する方針の下での解説をおこなっている。

#必要な画像の種類
1つのシーンについて2種類の画像を用意する。

* 元画像
* ラベル画像 …元画像の中の識別したい領域を既定の色で塗りつぶした画像

この2種類を1セットとして、複数セットの画像を用意してデータセットを構成する。
※動作確認だけなら10セットもあれば用は足りる

----
元画像:
![00001.jpg](https://qiita-image-store.s3.amazonaws.com/0/252248/fc0b0579-447c-f4b8-3e49-aaf803149b00.jpeg)

----
ラベル画像(モノクロ):よく見るとナンバープレートと車体は別の色(=別領域)となっている
![00001.png](https://qiita-image-store.s3.amazonaws.com/0/252248/1a44d582-9229-c003-2b14-46d452b86f25.png)

----
10セットの画像で2000回の学習結果:物体の重みづけはハードコーディングで変更してある
![000004_prediction.png](https://qiita-image-store.s3.amazonaws.com/0/252248/377d0c03-ef1d-1190-0a1b-9b231496aa2b.png)

#画像のサイズ
* 任意でよい
* 元画像とラベル画像は同じサイズでなければならない

#元画像のフォーマット
* jpegまたはpng

build_voc2012_data.py の --image_format オプションでファイルの拡張子を指定する。実際のフォーマットと無関係な拡張子でも構わない。

※build_voc2012_data.py のソースを見るとjpegでなければ学習できないように見えるが実際にはpngも利用できる。

#ラベル画像のフォーマット
* png
* カラー画像でよい …読み込み時にモノクロに変換される
* アルファが含まれてもよい。ただし半透明な領域が重なった場合、色成分が混合されるので意図した画素値を得られない …背景だけを透明にした場合は問題は生じず、判りやすい画像になるのでお勧め
* モノクロに変換された時の画素の輝度値が0,1,2,3...のように識別物体のインデックスを表さなければならない。あるいは画素の色がPASCAL VOC 2012の既定の色群に従っていなければならない …読み込み時に画素値がインデックス番号を表す輝度値に変換される

##ラベル画像の留意点
###マスクのエッジにアンチエイリアスがかかっていてはならない
領域は厳密な画素値によって識別されるので、エッジが中間色でスムージングされているとその部分は正しく認識されない。
OK:
![edge_ok.png](https://qiita-image-store.s3.amazonaws.com/0/252248/cfcef3c7-df09-a932-b95e-c0ebe931926b.png)

NG:
![edge_ng.png](https://qiita-image-store.s3.amazonaws.com/0/252248/a8936745-919a-0e8d-720c-98b7cc9d4099.png)

###モノクロ画像を用意する場合
* RGBの各成分が同一値の画素で構成された24ビットフルカラー画像を用意する
* rgb(n,n,n) の画素値の領域が識別物体インデックス=nの領域として扱われる
* rgb(0,0,0) は通常、背景色として利用される。これは計算の上で無視されるのではなく、背景領域として分類されているという意味合いを持つ
* rgb(255,255,255) は計算上無視される
    * 255(ignore_label値) は deeplab/datasets/segmentation_dataset.py 内で設定する。変更可能

###カラー画像を用意する場合
* PASCAL VOC 2012既定の色で識別領域をマスクした画像を用意する
* 色の規定は [PASCAL VOC 2012のセグメンテーションで使用されているカラーマップ](https://qiita.com/mine820/items/725fe55c095f28bffe87) を参照のこと
* 最終的にはモノクロ画像と同一の形態に変換されるが、目視しやすく、かつビジュアライズしたときに出力画像がこの色で塗分けられるので何かと便利がよい

![voc2012.png](https://qiita-image-store.s3.amazonaws.com/0/252248/f397d7ad-1f64-cade-8426-c697a71ff83c.png)

###境界線の有無
PASCAL VOC2012 のラベル画像には無効色で物体の境界線が描かれているが、必須ではない。
学習結果に与える影響は未検証だが、境界線によって塗りつぶされるような小さな物体を無視するには都合がよいかもしれない。

#学習時の画像の扱い
* crop_sizeより大きな画像の場合、crop_sizeで指定された範囲が __切り出されて__ 利用される …リサイズされるわけではない
* crop_sizeに満たないサイズの画像は不足領域が平均画素値(元画像)/ignore_label値(ラベル画像)でパディングされる 
* deeplab/train.py のパラメータ min_resize_value(オプション:max_resize_value,resize_factor) が設定されている場合、crop前にリサイズされる
* crop領域はランダムに設定される …crop_size以下の画像は常に全体が利用される
* crop前にランダムな比率でリサイズされる …比率はパラメータ min_scale_factor, max_scale_factor, scale_factor_step_size で決定される
* ランダムに左右がフリップされる …あらかじめ __左右反転した画像を用意する必要はない__

#おまけ:画像生成ツール
ラベル画像を生成する簡易なツールを用意した。
単一のHTMLファイルとして構成されているので下のソースをコピペしてローカルで利用できる。
Windows10 + Inkscapeで作成したSVGファイル + Chromeで動作を確認した。
__FireFoxでは__意図した結果が得られない(ラベル画像がアンチエイリアスされてしまう)ので__利用できない__。
※あまりデバッグしていないのでしばらくすると落ちたりするので注意
![svgtodataset.png](https://qiita-image-store.s3.amazonaws.com/0/252248/91df2422-a6bc-81d1-f45d-07e0f0b48479.png)

###使い方
* 元画像を張り付けて、その上にパスでマスクを描画したSVGファイルを用意
* SVGのキャンバスサイズは元画像に一致させておく
* このツールでそのSVGファイルを読ませると、元画像のjpgとラベル画像のモノクロpngが落ちてくる …どちらもそのまま利用できる
* ラベル画像の背景領域は透明化されているが、そのままで画素値rgb(0,0,0)=黒として扱われる
* マスクの色を大雑把に8種類に識別して輝度0～7のモノクロに変換している …識別物体種類は背景含めて8種類が上限

|インデックス|色|
|---|---|
|0|マスクなしまたは黒|
|1|赤|
|2|緑|
|3|黄|
|4|青|
|5|紫|
|6|水|
|7|白|
RGBの輝度値128をその原色の有効無効の閾値としている。
rgb(127,0,0)…黒として扱われる
rgb(128,0,0)…赤として扱われる

```html:svg_to_dataset.html
<!DOCTYPE html>
<html> 
<head> 
<meta charset="UTF-8" />
<title>Semantic-Segmentation Dataset SVGファイル変換</title> 
</head> 
<body onLoad="start()">
<h3>Semantic-Segmentation Dataset SVGファイル変換</h3>
<p style="display:inline-block">[<a id="linkImage">Download Image</a>] </p>
<p style="display:inline-block">[<a id="linkSeg">Download Segmentation</a>] </p>
<input type="file" accept="image/svg+xml" onChange="onFileSelect(this.files)"/>
<object id="svg" type="image/svg+xml" width="100%" height="100%"></object>

<script>

svgName="";

//起動時の処理
function start() {
}

//SVG読み込み時の処理
function onLoadSvg() {
	var svg = document.getElementById("svg");
	var svgDoc = svg.getSVGDocument();
	//画像をクリックで背景有無を切り替える
	svgDoc.addEventListener('click', function (event) {switchBgVisibility();},false);
	
	//マスクの色を#000000～#070707に変更
	var pathes = svgDoc.getElementsByTagName("path");
	for (var i=0;i<pathes.length;i++) {
		var color="#FFFFFF";
		var path=pathes[i];
		var rgb=path.style.fill;
		if(rgb.startsWith("rgb(")) {
			rgb=rgb.substring(4,rgb.length-1);
			var rgbAry=rgb.split(",");
			var rgbBit=0;
			if(parseInt(rgbAry[0])>=128) rgbBit|=1;
			if(parseInt(rgbAry[1])>=128) rgbBit|=2;
			if(parseInt(rgbAry[2])>=128) rgbBit|=4;
			var rgbHex=("0"+rgbBit.toString(16)).substring(-2);
			color="#"+rgbHex+rgbHex+rgbHex;
		}
		
		path.style.fill=color;
		path.style.fillOpacity='1.0';
	}
	
	setupDownloadLink();
}

//SVGファイルを開く
function onFileSelect(files) {
	clearDownloadLink();

	var svg = document.getElementById("svg");
	var svgDoc = svg.getSVGDocument();

	var file=files[0];
	svgName=file.name.replace(".svg","");
	
	
	blobURL = URL.createObjectURL(file);
	//画像読み込み完了後
	svg.addEventListener('load', function () {
		//消さないと残って何度も呼ばれる
		svg.removeEventListener('load',arguments.callee,false);
		//File/BlobオブジェクトにアクセスできるURLを開放
		URL.revokeObjectURL(blobURL);
		onLoadSvg();
    },false);
    
	svg.data=blobURL;
}

//背景表示の切り替え
function switchBgVisibility() {
	var svg = document.getElementById("svg");
	var svgDoc = svg.getSVGDocument();
	var img = svgDoc.getElementsByTagName("image");
	var disp=img[0].getAttribute("visibility");
	if(disp=='visible' || !disp) img[0].setAttribute("visibility","hidden");
	else img[0].setAttribute("visibility","visible");
}

//ダウンロード関連
function clearDownloadLink(bgVisible) {
	document.getElementById("linkImage").removeAttribute('href');
	document.getElementById("linkSeg").removeAttribute('href');
}

function setupDownloadLink() {
	createDownloadLink(true);
	createDownloadLink(false);
}

function createDownloadLink(isBackground) {
	var mimeType=isBackground ? "image/jpeg":"image/png"
	var fileName=isBackground ? svgName+".jpg":svgName+".png";
	var linkTagId=isBackground ? "linkImage":"linkSeg";
	var linkTag=document.getElementById(linkTagId);
		
	var svgDoc = document.getElementById("svg").getSVGDocument();
	var org = svgDoc.getElementsByTagName("svg")[0];
	
	var svg=org.cloneNode(true);
	
	//画像/マスクの表示/非表示
	var visible=isBackground;
	for(var e=0;e<2;e++) {
		var elm=["image","path"][e];
		var node = svg.getElementsByTagName(elm);
		for(var i=0;i<node.length;i++) {
			if(visible) node[i].setAttribute("visibility","visible");
			else node[i].setAttribute("visibility","hidden");
		}
		visible=!visible;
	}
	
	var svgData = new XMLSerializer().serializeToString(svg);
	var canvas = document.createElement("canvas");
	
	var w = svg.width.baseVal.value;
	var h = svg.height.baseVal.value;

	canvas.width = w;
	canvas.height = h;

	var ctx = canvas.getContext("2d");
	var image = new Image;
	image.onload = function(){
		ctx.fillStyle = "#00000000";
		ctx.fillRect(0,0,canvas.width,canvas.height);
	    ctx.drawImage( image, 0, 0 );
		
		canvas.toBlob(function(blob) {
			var a = document.createElement("a");
			a.download = fileName;
			a.href = URL.createObjectURL(blob);
			a.target = '_blank';
			a.click();
			
		    linkTag.href = URL.createObjectURL(blob);
		    linkTag.setAttribute("download", fileName);
			
			URL.revokeObjectURL(blob);
		}, mimeType, 1.0);
	}
	
	image.src = "data:image/svg+xml;charset=utf-8;base64," + btoa(unescape(encodeURIComponent(svgData)));
}

</script>

</body> 
</html>
```