最近傍法による輝度変換を用いて拡大したグレースケールの精度を比較してみた

大学の科目の一つである「ロボットビジョン」の自由レポート課題で行った比較の結果が面白かったので共有したいと思って記事にすることにしました(今回はそのうちの最近傍法に絞りました)。初投稿のため至らない点もあるかと思いますがよろしくお願いします。
#輝度変換とは
輝度変換とは、グレースケールにおいて非格子点の輝度値を定めることです。これにはいくつかの方法がありますが、今回はそのうちの最近傍法の三種類について比較していきます。
# 今回使用する最近傍法
最近傍法にもいくつか種類がありますが、今回は
### 非格子点(u0,v0)に最も近いu-v座標系での格子点の輝度値を(u0,v0)における値とする方法(以下方法1と呼びます)
$$
g(u_0,v_0)=f([u_0+0.5],[v_0+0.5])
$$

```python:プログラム
#非格子点に最も近いu-v座標系での格子点の輝度値を代入する方法
def nnalg_1(filepath):
	img = cv2.imread(filepath) #画像読み込み
	gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) #グレースケールに変換
	img2 = np.zeros((img.shape[0]*3,img.shape[1]*3),dtype=np.uint8) #拡大画像を格納する配列を作成
	for i in range(img.shape[1]*3):
		for j in range(img.shape[0]*3):
			#四捨五入
			if i/3 - round(i/3) < 0.5:
				wid = math.floor(i/3)
			else:
				wid = math.ceil(i/3)
			if j/3 - round(j/3) < 0.5:
				hei = math.floor(j/3)
			else:
				hei = math.ceil(j/3)
			#配列のインデックスが溢れないように調整
			if wid >= gray_img.shape[1]:
				i_wid = gray_img.shape[1] - 1
			else:
				i_wid = wid
			if hei >= gray_img.shape[0]:
				i_hei = gray_img.shape[0] - 1
			else:
				i_hei = hei
			img2[j,i] = gray_img.item(i_hei,i_wid) #対応する値を代入
	cv2.imwrite("gray1.png",img2) #画像として出力
```

### 最大値0次補間(以下方法2と呼びます)
$$
g(u_0,v_0)=max(f(u,v),f(u+1,v),f(u,v+1),f(u+1,v+1))
$$

```python:プログラム
#最大値0次補間
def nnalg_2(filepath):
	img = cv2.imread(filepath) #画像読み込み
	gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) #グレースケールに変換
	img2 = np.zeros((img.shape[0]*3,img.shape[1]*3),dtype=np.uint8) #拡大画像を格納する配列を作成
	#代入された値をもとに配列を計算
	for i in range(img.shape[1]*3):
		for j in range(img.shape[0]*3):
			#maxの式のインデックスが溢れないように調整
			lef = int(round(i/3))
			down = int(round(j/3))
			if lef >= img.shape[1]-1:
				lef = img.shape[1]-2
			if down >= img.shape[0]-1:
				down = img.shape[0]-2
			hoge = max(gray_img.item(down,lef),gray_img.item(down+1,lef),gray_img.item(down,lef+1),gray_img.item(down+1,lef+1))
			img2[j,i] = np.uint8(hoge) #対応する値を代入
	cv2.imwrite("gray2.png",img2) #画像として出力
```

### 最小値0次補間(以下方法3と呼びます)
$$
g(u_0,v_0)=min(f(u,v),f(u+1,v),f(u,v+1),f(u+1,v+1))
$$

```python:プログラム
#最小値0次補間
def nnalg_3(filepath):
	img = cv2.imread(filepath) #画像読み込み
	gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) #グレースケールに変換
	img2 = np.zeros((img.shape[0]*3,img.shape[1]*3),dtype=np.uint8) #拡大画像を格納する配列を作成
	#代入された値をもとに配列を計算
	for i in range(img.shape[1]*3):
		for j in range(img.shape[0]*3):
			#minの式のインデックスが溢れないように調整
			lef = int(round(i/3))
			down = int(round(j/3))
			if lef >= img.shape[1]-1:
				lef = img.shape[1]-2
			if down >= img.shape[0]-1:
				down = img.shape[0]-2
			hoge = min(gray_img.item(down,lef),gray_img.item(down+1,lef),gray_img.item(down,lef+1),gray_img.item(down+1,lef+1))
			img2[j,i] = np.uint8(hoge) #対応する値を代入
	cv2.imwrite("gray3.png",img2) #画像として出力
```
の三つの方法を比較していきます。
#比較方法
![IMG_0230.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1091686/819fa92a-b0c3-acc3-d2e4-ed51c02478be.jpeg)

上の美味しそうなカヌレの画像をグレースケール化したものと、さらに1/3のサイズにしたものを準備します。その後、各最近傍法を用いて1/3画像を三倍のサイズに輝度変換し、各ピクセルの輝度値が元画像のグレースケールとどの程度ずれているかを比較します。定量的に調べるために、今回はズレが大きいほどスコアに大きな数を足していき、最終的なスコアの小ささで元画像との一致度合を調べました(初期スコアは0)。その際、

*　輝度が完全一致なら+0
* 誤差が+-3以内なら+1
* 誤差が+-5以内なら+3
* 誤差がそれより大きいなら+10

としました。
プログラムは以下のようなものを用いました。

```python:プログラム
#評価決め
def checkScore(filepath_1,filepath_2):
	img_1 = cv2.imread(filepath_1)
	img_2 = cv2.imread(filepath_2)
	gray_img_1 = cv2.cvtColor(img_1, cv2.COLOR_BGR2GRAY)
	gray_img_2 = cv2.cvtColor(img_2, cv2.COLOR_BGR2GRAY)
	score_sum = 0
	score_good = 0 #完全一致
	score_better = 1 #誤差が+-3以内
	score_safe = 3 #誤差が+-5以内
	score_bad = 10 #それ以外
	if img_1.shape[0] != img_2.shape[0] or img_1.shape[1] != img_2.shape[1]:
		print("サイズが違うよ")
		print("img_1:" + str(img_1.shape[0]) + str(img_1.shape[1]))
		print("img_2:" + str(img_2.shape[0]) + str(img_2.shape[1]))
	else:
		for i in range(img_1.shape[1]):
			for j in range(img_1.shape[0]):
				hoge = abs(gray_img_1.item(j,i) - gray_img_2.item(j,i))
				if hoge == 0:
					score_sum += score_good
				elif hoge <= 3:
					score_sum += score_better
				elif hoge <= 5:
					score_sum += score_safe
				else:
					score_sum += score_bad
	print("スコアは,,,")
	print(score_sum)
```

#結果
### 方法1
変換後の画像は以下のようになりました。![gray1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1091686/34afbe98-d260-66e5-c49e-6d2192e91717.png)

この時、算出されたスコアは24691013でした。
### 方法2
変換後の画像は以下のようになりました。
![gray2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1091686/7fe56275-9f58-4a8d-4c67-fbb345afa06e.png)
この時、算出されたスコアは40479050でした。
### 方法3
変換後の画像は以下のようになりました。
![gray3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1091686/faa79d19-e347-2d9e-d9b5-03a2b0050c92.png)
この時、算出されたスコアは40480824でした。
### 総括
スコア的に方法1が圧倒的に優れていることがわかりました。
しかし、元画像とズレてはいるものの、方法2の変換で生成された画像ではカヌレが元画像よりキラキラしており、美味しそうに見えます。結局色は主観的なものに過ぎないので、あまり元画像に囚われず、変換したい対象によって変換方法を選択する方が良いのかなーなんて思いました。
#まとめ
今回は最近傍方による輝度変換を用いて拡大したグレースケールの精度を比較してみました。また機会がありましたら線形補間法も加えた比較などもできたらと思います。ではでは(_ _)