PythonのライブラリであるOpenCVを用いた中心差分による画素値ごとの微分

# 目次
[はじめに](#はじめに)
[実行環境](#実行環境)
[画像の画素値へのアクセス方法](#画像の画素値へのアクセス方法)
[画素値の演算方法](#画素値の演算方法)
[ソースコード](#ソースコード)
[結果](#結果)
[特徴](#特徴)
[まとめ](#まとめ)

# はじめに
前回の記事では、**数値微分の中で最も精度が高いのは中心差分らしい**ということが分かりました。
今回は、**中心差分を用いて画像の画素値ごとに中心差分を施していき`OpenCV`を用いて表示**させようと思います。また、**`matplotlib`を用いてRGBのそれぞれのヒストグラムを表示**させようと思います。

:::note
分かりやすくするために<ins>**BGRでの実装では注目色以外の色の値を0に**</ins>しています
:::

------

前回の記事はこちら

https://qiita.com/blueman/items/542064585cd44f894494


-------------------

よろしければこちらもどうぞ!!

<details><summary>マイページについて</summary>

:::note info
主に<ins>**Pythonについての記事**</ins>を投稿してます!!
:::

[<font color="blue"><ins>**マイページはこちら**</ins></font>](https://qiita.com/blueman,"マイページ")

----------------------

</details>

<details><summary>X(Twitter)について</summary>

:::note info
<ins>**Qiitaの新着記事の通知**</ins>や<ins>**おススメの理工学書の紹介**</ins>をツイートしてます!!
:::
[<font color="blue"><ins>**X(Twitter)はこちら**</ins></font>](https://twitter.com/0ca00118726208m,"X(Twitter)")

--------------------------------

</details>

<details><summary>Zennについて</summary>

:::note info
主に<ins>**自分の書いたQiitaの記事の転載**</ins>と<ins>**Pythonを使っていて遭遇したエラーの対処法のような記事**</ins>を投稿していく予定です!!
:::
[<font color="blue"><ins>**Zennはこちら**</ins></font>](https://zenn.dev/blueman,"Zenn")

--------------------------------

</details>


# 実行環境
実行環境は次の通りです。

<details><summary>実行環境</summary>

- **環境**
    -  **`Windows 10`**
    -  **`Python 3.10.5`**
- **ライブラリ**
    -  **`OpenCV 4.8.0`**
    -  **`matplotlib 3.6.1`**

--------
</details>

画像はこちらを使います。

<details><summary>使用した画像</summary>

![使用した画像](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3198998/5e429b72-b88c-77b5-0768-bd12b436ea37.jpeg "使用した画像")

</details>

画像の引用元はこちらです

http://www.ess.ic.kanagawa-it.ac.jp/app_images_j.html


# 画像の画素値へのアクセス方法

https://biotech-lab.org/articles/872

#### BGRでの画素値へのアクセス方法
上の参照元によると、
> 画像の画素値へのアクセスは**画像の配列に<font color="red">画像の$y$座標を第１引数、画像の$x$座標を第２引数、整数値を第3引数</font>に指定**すると**BGRそれぞれの画素値が<font color="red">整数値</font>で返される**

らしいのでその方法でRGBの画素値へアクセスします。
ちなみに、第３引数に指定する整数値は次の通りです。
<details><summary>第３引数の整数値の説明</summary>

<table>
  <caption><strong>整数値の説明</strong></caption>
  <thead>
    <tr>
      <th style="text-align:center">整数値</th><th style="text-align:center">色</th>
    </tr>
  </thead>
  <tr>
    <td style="text-align:center">0</td> <td style="text-align:center">青(B)</td>
  </tr>
  <tr>
    <td style="text-align:center">1</td><td style="text-align:center">緑(G)</td>
  </tr>
  <tr>
    <td style="text-align:center">2</td><td style="text-align:center">赤(R)</td>
  </tr>
</table>

</details>

#### グレースケールでの画素値へのアクセス方法

>また、グレースケール画像では<font color="red">**第１引数に画像の$y$座標、第２引数に画像の$x$座標**</font>を指定すると**画素値が<font color="red">整数値</font>で返される**

らしいのでその方法で<ins>**グレースケールの画素値へアクセス**</ins>します。

:::note
画素値は<ins>**0~255**</ins>で返されます
:::

# 画素値の演算方法
[画像の画素値へのアクセス方法](#画像の画素値へのアクセス方法)で紹介した方法で<ins>**画像の画素値を取得**</ins>します。

その次に、前回の記事で紹介した<ins>**中心差分を求める式**</ins>である下の式

```math
f'(x)\approx \frac{f(x+h)-f(x-h)}{2h}
```

の$x$の部分を<ins>**画素値**</ins>に置き換えて演算します。

<details><summary>具体例</summary>

:::note

例えば、<ins>**青(B)**</ins>の場合は

```math
f'(img[y,x,0])= \frac{f(img[y,x,0]+h)-f(img[y,x,0]-h)}{2h}
```
として演算します。

:::

</details>

今回は、$f(x)=x^2$としました。

<details><summary>理由</summary>

:::note

$f(x)=x^2$とすることで、$f'(x)=2x$となるので<font color="red">**微分した式が線形**</font>となり考えやすくなるためです。
:::

</details>

以上のことから、<ins>**画像の画素値の中心差分を用いた式**</ins>は次のように書き換えられます。

```math
f'(img[y,x,0])= \frac{(img[y,x,0]+h)^2-(img[y,x,0]-h)^2}{2h} \label{a}\tag{1}
```

$(1)$式と同様に考えると、<ins>**ほかの色についても実装できる**</ins>ので$(1)$式の$img[y,x,0]$の部分を<ins>**それぞれの色の引数に変えて実装**</ins>しました。

# ソースコード
下にソースコードを示します。おそらく[実行環境](#実行環境)で示した環境では動くはず。

<details><summary>ソースコード(青(B)の処理)</summary>

```python:B_img_transform.py
import cv2 #OpenCVをインポート
import matplotlib.pyplot as plt #matplotlibをpltという名前でインポート

h = 10**(-4) #中心差分のhの定義
blue_val=[] #青(B)の画素値を格納する配列の定義
green_val=[] #緑(G)の画素値を格納する配列の定義
red_val=[] #赤(R)の画素値を格納する配列の定義

img = cv2.imread('lena_std.jpg') #画像の読み込み

for i in range(img.shape[0]): #画像のy座標iを画像の高さの分imgの配列の第１引数に入力
	for j in range(img.shape[1]): #画像のx座標jを画像の幅の分imgの配列の第２引数に入力
		img[i,j,0] = ((img[i,j,0]+h)**2-(img[i,j,0]-h)**2)/(2*h) #画像上にあるすべての青(B)の画素値に中心差分を施す
		img[i,j,1]=0 #画像上にあるすべての緑(G)の画素値を0にする
		img[i,j,2]=0 #画像上にあるすべての赤(R)の画素値を0にする
		
		blue_val.append(img[i,j,0]) #blue_valに新しくimg[i,j,0]の計算結果を代入
		green_val.append(img[i,j,1]) #green_valに新しく値0を代入
		red_val.append(img[i,j,2]) #red_valに新しく値0を代入

cv2.imshow("tyuusinsabun",img) #処理結果を出力
cv2.waitKey() #window closeボタンが押されるまで待機

plt.figure(tight_layout=True) #ヒストグラムが重ならないように調整

plt.subplot(221,title="Blue") #2×2のグラフ領域の左上に青(B)のヒストグラムを表示
plt.hist(blue_val,ec='black') #青(B)のヒストグラムのグラフの枠線を黒(black)に設定
plt.subplot(222,title="Green") #2×2のグラフ領域の右上に緑(G)のヒストグラムを表示
plt.hist(green_val,ec='black') #緑(G)のヒストグラムのグラフの枠線を黒(black)に設定
plt.subplot(223,title="Red") #2×2のグラフ領域の左下に赤(R)のヒストグラムを表示
plt.hist(red_val,ec='black') #赤(R)のヒストグラムのグラフの枠線を黒(black)に設定
plt.show() #全体のヒストグラムを表示
```

------------------

</details>

<details><summary>ソースコード(緑(G)の処理)</summary>

```python:G_img_transform.py
import cv2 #OpenCVをインポート
import matplotlib.pyplot as plt #matplotlibをpltという名前でインポート

h = 10**(-4) #中心差分のhの定義
blue_val=[] #青(B)の画素値を格納する配列の定義
green_val=[] #緑(G)の画素値を格納する配列の定義
red_val=[] #赤(R)の画素値を格納する配列の定義

img = cv2.imread('lena_std.jpg') #画像の読み込み

for i in range(img.shape[0]): #画像のy座標iを画像の高さの分imgの配列の第１引数に入力
	for j in range(img.shape[1]): #画像のx座標jを画像の幅の分imgの配列の第２引数に入力
		img[i,j,0]=0 #画像上にあるすべての青(B)の画素値を0にする
		img[i,j,1] = ((img[i,j,1]+h)**2-(img[i,j,1]-h)**2)/(2*h) #画像上にあるすべての緑(G)の画素値に中心差分を施す
		img[i,j,2]=0 #画像上にあるすべての赤(R)の画素値を0にする
		
		blue_val.append(img[i,j,0]) #blue_valに新しく値0を代入
		green_val.append(img[i,j,1]) #green_valに新しくimg[i,j,1]の計算結果を代入
		red_val.append(img[i,j,2]) #red_valに新しく値0を代入

cv2.imshow("tyuusinsabun",img) #処理結果を出力
cv2.waitKey() #window closeボタンが押されるまで待機

plt.figure(tight_layout=True) #ヒストグラムが重ならないように調整

plt.subplot(221,title="Blue") #2×2のグラフ領域の左上に青(B)のヒストグラムを表示
plt.hist(blue_val,ec='black') #青(B)のヒストグラムのグラフの枠線を黒(black)に設定
plt.subplot(222,title="Green") #2×2のグラフ領域の右上に緑(G)のヒストグラムを表示
plt.hist(green_val,ec='black') #緑(G)のヒストグラムのグラフの枠線を黒(black)に設定
plt.subplot(223,title="Red") #2×2のグラフ領域の左下に赤(R)のヒストグラムを表示
plt.hist(red_val,ec='black') #赤(R)のヒストグラムのグラフの枠線を黒(black)に設定
plt.show() #全体のヒストグラムを表示
```

-----------------

</details>

<details><summary>ソースコード(赤(R)の処理)</summary>

```python:R_img_transform.py
import cv2 #OpenCVをインポート
import matplotlib.pyplot as plt #matplotlibをpltという名前でインポート

h = 10**(-4) #中心差分のhの定義
blue_val=[] #青(B)の画素値を格納する配列の定義
green_val=[] #緑(G)の画素値を格納する配列の定義
red_val=[] #赤(R)の画素値を格納する配列の定義

img = cv2.imread('lena_std.jpg') #画像の読み込み

for i in range(img.shape[0]): #画像のy座標iを画像の高さの分imgの配列の第１引数に入力
	for j in range(img.shape[1]): #画像のx座標jを画像の幅の分imgの配列の第２引数に入力
		img[i,j,0]=0 #画像上にあるすべての青(B)の画素値を0にする
		img[i,j,1]=0 #画像上にあるすべての緑(G)の画素値を0にする
		img[i,j,2] = ((img[i,j,2]+h)**2-(img[i,j,2]-h)**2)/(2*h) #画像上にあるすべての赤(R)の画素値に中心差分を施す
		
		blue_val.append(img[i,j,0]) #blue_valに新しく値0を代入
		green_val.append(img[i,j,1]) #green_valに新しく値0を代入
		red_val.append(img[i,j,2]) #red_valに新しくimg[i,j,2]の計算結果を代入

cv2.imshow("tyuusinsabun",img) #処理結果を出力
cv2.waitKey() #window closeボタンが押されるまで待機

plt.figure(tight_layout=True) #ヒストグラムが重ならないように調整

plt.subplot(221,title="Blue") #2×2のグラフ領域の左上に青(B)のヒストグラムを表示
plt.hist(blue_val,ec='black') #青(B)のヒストグラムのグラフの枠線を黒(black)に設定
plt.subplot(222,title="Green") #2×2のグラフ領域の右上に緑(G)のヒストグラムを表示
plt.hist(green_val,ec='black') #緑(G)のヒストグラムのグラフの枠線を黒(black)に設定
plt.subplot(223,title="Red") #2×2のグラフ領域の左下に赤(R)のヒストグラムを表示
plt.hist(red_val,ec='black') #赤(R)のヒストグラムのグラフの枠線を黒(black)に設定
plt.show() #全体のヒストグラムを表示
```

-------------------------

</details>

<details><summary>ソースコード(グレースケールの処理)</summary>

```python:grayscale_img_transform.py
import cv2 #OpenCVをインポート
import matplotlib.pyplot as plt #matplotlibをpltという名前でインポート

h = 10**(-4) #中心差分のhの定義
grayscale_val=[] #グレースケールの画素値を格納する配列の定義

img = cv2.imread('lena_std.jpg') #画像の読み込み
img_2 = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) #読み込んだ画像をグレースケールに変換

for i in range(img_grayscale.shape[0]): #画像のy座標iを画像の高さの分imgの配列の第１引数に入力
	for j in range(img_grayscale.shape[1]): #画像のx座標jを画像の幅の分imgの配列の第２引数に入力
		img_grayscale[i,j] = ((img_grayscale[i,j]+h)**2-(img_grayscale[i,j]-h)**2)/(2*h) #画像上にあるすべてのグレースケールの画素値に中心差分を施す
		
		grayscale_val.append(img_grayscale[i,j]) #graycale_valに新しくimg[i,j]の計算結果を代入

cv2.hconcat("tyuusinsabun",[img_2,img_grayscale]) #処理結果を出力
cv2.waitKey() #window closeボタンが押されるまで待機

plt.hist(grayscale_val,ec='black') #グレースケールのヒストグラムを表示
plt.show() #グレースケールのヒストグラムを表示
```

----------------------------------

</details>

:::note warn
<ins>**画像のサイズ・解像度**</ins>、<ins>**パソコンのスペック**</ins>によっては<font color="red"><ins>**処理が遅い**</ins></font>場合があります!!
:::

:::note alert
<ins>**ヒストグラム以外のグラフを表示**</ins>させると<font color="red">**処理の過負荷**</font>によりエラーになる恐れがあります
:::

# 結果
まず、画像の処理結果が

<details><summary>青(B)の処理結果</summary>

![青(B)の処理結果(画像)](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3198998/13cabedd-fa4c-0732-9bb8-a0ee5a9a22d8.jpeg "青(B)の処理結果(画像)")

-------------------------------

</details>

<details><summary>緑(G)の処理結果</summary>

![緑(G)の処理結果(画像)](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3198998/20637978-a05b-d83b-f3ca-8540e12a71d1.jpeg "緑(G)の処理結果(画像)")

---------------------------

</details>

<details><summary>赤(R)の処理結果</summary>

![赤(R)の処理結果(画像)](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3198998/1a4b8e65-6f21-d2ce-77c4-97f311e00ce8.jpeg "赤(R)の処理結果(画像)")

-----------------------------

</details>

<details><summary>グレースケールの処理結果</summary>

:::note
左が<ins>**オリジナルのグレースケール画像**</ins>
右が<ins>**変換後の画像**</ins>です。
:::

<img width="200" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3198998/b7ed1027-3bda-b4d1-30d1-d86ff66da73b.jpeg" alt="グレースケール画像"><img width="200" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3198998/af9ee12c-d29d-6db2-4930-8189ff8317e7.jpeg" alt="グレースケールの処理結果(画像)">

------------------

</details>

次に、それぞれのヒストグラムが
<details><summary>青(B)のヒストグラム</summary>

![青(B)のヒストグラム](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3198998/18947c15-5dd1-3235-a899-374ceb002fbb.png "青(B)のヒストグラム")

--------------------

</details>

<details><summary>緑(G)のヒストグラム</summary>

![緑(G)のヒストグラム](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3198998/c87feb40-b6c5-8852-a36d-4beb52bfb38b.png "緑(G)のヒストグラム")

-----------------

</details>

<details><summary>赤(R)のヒストグラム</summary>

![赤(R)のヒストグラム](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3198998/4241d40a-b779-dfb7-77dc-066c0bc485d6.png "赤(R)のヒストグラム")

-------------------------

</details>

<details><summary>グレースケールのヒストグラム</summary>

:::note
左が<ins>**オリジナルのグレースケールのヒストグラム**</ins>
右が<ins>**変換後のグレースケールのヒストグラム**</ins>です。
:::

<img width="200" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3198998/f9d8602c-1c67-0e3c-f86c-5d496a6fe44c.png" alt="グレースケール(オリジナル)のヒストグラム">
<img width="200" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3198998/c8acd63b-c9c7-834a-dc29-2e1f608efafd.png" alt="グレースケール(変換後)のヒストグラム">

--------------

</details>

となります。

# 特徴
画像処理で微分と聞くと<ins>**微分フィルタ**</ins>を思い浮かべられる方が多いと思いますが、<ins>**微分フィルタでは[隣り合う画素の差分を取る](https://www.momoyama-usagi.com/entry/info-img03#4_1)ことで微分を表現**</ins>しています。
この手法では、<ins>**画素ごとに中心差分を取る**</ins>ことで<font color="red"><ins>**画像の画素単体での微分**</ins></font>を行うことができます。

<details><summary>手法についての特徴</summary>

<table>
  <caption><strong>手法の比較</strong></caption>
  <thead>
    <tr>
      <th style="text-align:center">従来</th> <th style="text-align:center">この手法</th>
    </tr>
  </thead>
  <tr>
    <td style="text-align:center"><strong><font color="red">隣り合う画素</font></strong>の差分を取る</td> <td style="text-align:center"><strong><font color="red">画素ごと</font></strong>の中心差分を計算</td>
  </tr>
</table>

</details>

<details><summary>ヒストグラムについての特徴</summary>

:::note
各処理結果のヒストグラムを見ると、ヒストグラムの先端が<ins>**2次関数で近似できそう**</ins>なところがあります!!
:::

</details>

<details><summary>処理画像についての特徴</summary>

:::note
処理結果の画像を見ると、ところどころ<ins>**共通な部分**</ins>がありそうです!!
:::

</details>

# まとめ
今回は、Pythonのライブラリである<font color="red"> **`OpenCV`** </font>を用いて<font color="red">**RGB画像やグレースケール画像の画像上のすべての画素値を取得**</font>しました。また、それらの画素値を用いて<font color="red">**画素値ごとの中心差分を行うことで画像の微分を実装**</font>しました。
そして、<font color="red">**画像のRGBの画素値やグレースケールの画素値からヒストグラムを作成**</font>しました。
この記事が実際に役に立つかどうかは分かりませんが、誰かの役に立ってくれると嬉しいです。
記事を執筆する余力があれば、<ins>次回も記事を投稿する予定</ins>です。
次回の予定としては、[動画から手の姿勢を推論するライブラリ](https://note.com/npaka/n/nbe59c36b3d49)である<font color="red"> **`MediaPipe Hands`** </font>を用いて**指の先端部分をマーク**することができたのでそのことに関しての記事を投稿する予定です。