# 人工智慧深度學習概論與應用 HW1 重點整理
[ToC]
## 7 image point processing
1. Invert: $255-x$
2. Darken: $x-128$
3. Lighten: $x+128$
4. Lower Contrast: $x/2$
5. Raise Contrast: $x*2$
6. Non-linear Lower Contrast: $(x/255)^{1/3}*255$
7. Non-linear Raise Contrast: $(x/255)^{2/3}*255$
### skill - np.Array 常數運算
``` python
# 加法
np.array([0, 1, 2, 3]) + 10
> array([10, 11, 12, 13])
# 減法
np.array([10, 11, 12, 13]) - 10
> array([0, 1, 2, 3])
# 乘法
np.array([0, 2, 4, 6]) * 2
> array([ 0, 4, 8, 12])
# 除法 (注意此處會轉換為 float64 型別)
np.array([0, 2, 4, 6]) / 2
> array([0., 1., 2., 3.])
# 指數
np.array([1, 2, 3, 4]) ** 2
> array([ 1, 4, 9, 16])
```
### skill - np.Array 型別轉換
由作業範例可以知道,圖片讀取預設型別為 `uint8`,即 8-bits 的無號整數,下方舉例:
8-bits 無號整數 1111 1111 為 255
8-bits 有號整數 1111 1111 為 -1
因此若今天將一值為 255 的 pixel 上加 1,目標應為得到 256,可能再收斂回 255
但根據無號數的運算,加一後變成 (1) 0000 0000,即為 0,會造成與目標極大的差距
因此可以以下方法進行轉換:
```python
# 圖片讀取預設為 uint8
A = np.array([1, 2, 3, 4], dtype='uint8')
A
> array([1, 2, 3, 4], dtype=uint8)
# 運算時擴大整數容許範圍進行超過 0~255 運算
A = A.astype(dtype='int32')
A
> array([1, 2, 3, 4], dtype=int32)
# 或轉換為浮點數
A = A.astype(dtype='float32')
A
> array([1., 2., 3., 4.], dtype=float32)
# 記得輸出圖片(plt.imshow)時要轉換回 uint8
A = A.astype(dtype='uint8')
A
> array([1, 2, 3, 4], dtype=uint8)
```
### skill - np.Array 範圍擷取
由上方可知,在最後要把數值轉換為 `uint8` 型別,且數值範圍為 0~255,經上方計算後由於我們讓數值大小範圍擴大,可能出現範圍外的數值,這裡最簡單的方式就是
1. 小於 0 -> 寫 0
2. 大於 255 -> 寫 255
因此可以以下方式實作:
```python
A = np.array([256, 255, 0, -1], dtype='int32')
A
> array([256, 255, 0, -1], dtype='int32')
# np.clip(src, lower_bound, upper_bound)
A = np.clip(A, 0, 255)
A
> array([255, 255, 0, 0], dtype='int32')
```
### skill - np.Array 圖片顯示
* 單一圖片顯示
```python
plt.imshow(image)
plt.show()
```
* 多圖片顯示
```python
fig = plt.figure(figsize=(height, width))
axes = fig.subplots(2, 2)
axes[0][0].imshow(img1)
axes[0][1].imshow(img2)
axes[1][0].imshow(img3)
axes[1][1].imshow(img4)
plt.show()
```
* 其他常用設定
```python
# 灰階
plt.imshow(image, cmap='gray')
# 設定標題
plt.title(title)
axes[i][j].set_title(title)
# 關閉座標軸
axes[i][j].set_axis_off()
# 儲存圖片
fig.savefig('pic.png')
```
:::info
可依需求在官方 document 找到更多可用調整函式
:::
## Gaussian Filter
1. 取得相對應大小的 Gaussian Kernel,$x$ 及 $y$ 的值以中心點為 $(0, 0)$
* Sigma:

* Kernel:

* 可得到如下 kernel

2. 以 kernel 對整張圖做卷積,即沿 $x$ 及 $y$ 每次位移一 pixel 求內積和成為新的 $(r,g,b)$ 值,當所有像素點皆重新運算後,則為完成 Guassian Blur 後的新圖。
3. Gaussian Blur 的概念可想像為將一個 pixel 周圍 $ksize*ksize$ 的像素點皆賦予一定權重(越靠近中央越重要,權重越大),並藉由加權求和使每個像素點新值皆提高和周遭像素的關聯,降低梯度,以達到模糊的效果。
4. 作業中 Convolution 的部分一個 if/else,第一個 case 是針對 border 的額外處理,第二個則是正常一個 kernel 可完整覆蓋對應圖塊的情況。
### skill - np.array 初始宣告
```python
# 以 list 型別創建
np.array([0, 0, 0, 0])
# 給定 size, 以 0 做初始值
np.zeros((h, w)) # 2-dimension
np.zeros((h, w, c)) # 3-dimension
# 給定 size, 以 1 做初始值
np.ones((h, w)) # 2-dimension
np.ones((h, w, c)) # 3-dimension
```
### skill - python 整數整除語法
```python
# 普通除法
print(9 / 2)
> 4.5
# 整除除法
print(9 // 2)
> 4
```
### skill - BGR/RGB 通道轉換
```python
# 先 split 再 merge
b,g,r = cv2.split(image)
image = cv2.merge([r,g,b])
# 直接使用內建語法交換
image = image[:,:,::-1]
```
## Downsampling
Downsampling 是為了把大圖片資訊縮小,減少資訊量以利計算。
最基本的 downsampling 方式即是將 $size*size$ 的數個 pixel 以其中一個作為代表像素點,並濃縮為 $1*1$ 大小,以下為範例圖片:
| 1 | 2 | 3 | 4 | 5 | 6 |
| --- | --- | --- | --- | --- | --- |
| 7 | 8 | 9 | 10 | 11 | 12 |
| 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 |
| 25 | 26 | 27 | 28 | 29 | 30 |
| 31 | 32 | 33 | 34 | 35 | 36 |
若以每 $2*2$ 大小取左上像素點為新像素點,即可 downsampling 為 $3*3$ 圖片如下:
| 1 | 3 | 5 |
| --- | --- | --- |
| 13 | 15 | 17 |
| 25 | 27 | 29 |
### skill - round / floor / ceil
```python
from math import floor, ceil
# 四捨五入
round(4.5)
> 5
# 無條件捨去
floor(4.5)
> 4
# 無條件進入
ceil(4.3)
> 5
```
## Upsampling
Upsampling 和 downsampling 相反,當一圖片尺寸過小時,可藉此手法將其放大,本次作業中又以兩種方式實作。
1. Nearest Neighbor Interpolation
此方式較為簡單,即是上方 downsampling 的反向,若一 $3*3$ 區塊被濃縮為 $1*1$ 的一值,則將其放大為 $3*3$ 並全部都給定該值,以下為範例圖片:
| 1 | 3 | 5 |
| --- | --- | --- |
| 13 | 15 | 17 |
| 25 | 27 | 29 |
將上圖以兩倍重新放大為 $6*6$ 如下:
| 1 | 1 | 3 | 3 | 5 | 5 |
| --- | --- | --- | --- | --- | --- |
| 1 | 1 | 3 | 3 | 5 | 5 |
| 13 | 13 | 15 | 15 | 17 | 17 |
| 13 | 13 | 15 | 15 | 17 | 17 |
| 25 | 25 | 27 | 27 | 29 | 29 |
| 25 | 25 | 27 | 27 | 29 | 29 |
2. Bicubic Interpolation
原理詳見[論文](https://ieeexplore.ieee.org/document/1163711),助教補充 ppt 中有提供,以下直接敘述實作概念。


* 由於計算時需要使用以放大後各座標於原圖對應座標為中心的 $4*4$ 圖塊(中心點外圍 $5*5$ 的座標點),因此須先將原圖向外做 2 個 pixel 的 padding
* 接著建立一個三層的 nested loop,分別為 RGB 三個通道、大圖的 X 方向整數座標、大圖的 Y 方向整數座標
* 計算當前大圖的 X, Y 座標分別對應至原圖的座標
```python
x = X / scale + 2
y = Y / scale + 2
```
* 以 $(-1,-1),(0, 0),(1,1),(2,2)$ 由原圖座標對應的 pixel 起點進行偏移,並計算該點與對應原圖座標的距離,以下用範例解釋:
| 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| --- | --- | --- | --- | --- | --- | --- |
| 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 0 | 0 | **1** | 3 | 5 | 0 | 0 |
| 0 | 0 | 13 | 15 | 17 | 0 | 0 |
| 0 | 0 | 25 | 27 | 29 | 0 | 0 |
| 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 0 | 0 | 0 | 0 | 0 | 0 | 0 |
上圖為經過 padding 後的原圖,可以看到值為 1 的該 pixel 左上座標為 $(2, 2)$,假設欲放大八倍則會對應至新圖的 $(0,0)$,即 $(0/8+2, 0/8+2)$。同理,值為 3 的 pixel 左上座標於舊圖中為 $(2, 3)$,即 $(0/8+2, 8/8+2)$,此處可看出,在八倍大的新圖中,八個像素長等同於舊圖中的一個像素,換句話說,此法便是以原本的一個像素距離分為八段,並分別計算異值變為八個漸進像素,以降低像素間的梯度大小。
如下圖為新圖的最左上角,圖中的藍點在新圖中座標為 $(0,1)$,對應回原圖則為 $(2.125,2)$,而圖中整個紅框範圍皆會對應到原圖值為 1 的像素,因此除右側及下方邊線上座標外,其餘座標點的位移起始點皆會由原圖的像素座標 $(2,2)$ 開始。

以上可解釋如何從新圖中的座標換算至舊圖,接著重點來了,上方所述的偏移起點會是該對應原圖座標中的像素起點(左上),如新圖中的 $(0,0)$ 至 $(7,7)$ 座標會對應至原圖中 $(2,2)$ 至 $(2.875,2.875)$ 座標,並且都位於值為 1 的像素上,因此位移起點皆為原圖的 $(2,2)$ 座標。承上,可得四個目標點 $(1,1)$、$(2,2)$、$(3,3)$、$(4,4)$,接著再依序計算目標點與欲計算點圓點座標$X$、$Y$方向距離,可得 $[x1, x2, x3, x4]$ 及 $[y1, y2, y3, y4]^T$,再套用上方 u(s) 的公式並將 $a$ 帶 -0.5,即可求得矩陣乘式中的前後兩矩陣。
```python
x1 = abs(x - (floor(x) - 1))
x2 = abs(x - (floor(x) + 0))
x3 = abs(x - (floor(x) + 1))
x4 = abs(x - (floor(x) + 2))
.
.
.
```
* 最後 f 矩陣則為 $(x-1,y-1)$ 至 $(x+2,y+2)$ 的方形圖塊,將三者做矩陣乘法運算後最終會得一值,將其收斂至 0~255 後即為該計算像素點的新值。
### [矩陣相乘參考資料](https://blog.csdn.net/zenghaitao0128/article/details/78715140)
## Canny Edge Detector
共拆分為五個步驟:
1. Gaussian Filter: 使圖相模糊化,降低像素梯度以求邊緣偵測最佳化
2. Sobel Operator: 計算梯度及方向,分離水平及垂直邊緣後再予以整合
3. Non-maximum Suppression: 取梯度方向上最大值像素,將輪廓精簡化
4. Threshoding Edges: 用像素值將現有輪廓分為 strong、weak 兩種,並刪除過小、不重要輪廓
5. Hysteresis: 找出與 strong edge 相連的所有輪廓,並刪除其他輪廓
## Gaussian Filter (1-2)
:::info
請直接複製 HW1-1 的 code 過來,並作適當更改即可
:::
:::danger
須注意此處圖片為灰階,不同於彩色圖片為三通道,此圖片陣列一個像素只以一個值代替。
:::
## Sobel Operator

索伯運算子用於計算一 $3*3$ 圖塊中梯度變化,若卷積結果值越大,該圖塊中梯度則較明顯且有 edge 存在,且由於其值較大,在結果的圖片中 edge 的部分會呈現相對高亮白色。
* 根據 sample code 定義的 `iterate_regions` 函式,可知道已有處理好的 $3*3$ 圖塊 `img_region` 陣列,可參照 [矩陣相乘參考資料](https://blog.csdn.net/zenghaitao0128/article/details/78715140) 直接使用 `*` 進行卷積並求和
```python
(Sx * img_region).sum()
(Sy * img_region).sum()
```
* 求方向可參考本連結:[arctan() vs arctan2()](https://geo.libretexts.org/Courses/University_of_California_Davis/UCD_GEL_56_-_Introduction_to_Geophysics/Geophysics_is_everywhere_in_geology.../zz%3A_Back_Matter/Arctan_vs_Arctan2)
## Non-maximum Supression
在 Sobel Operator 運算中,除了計算出各像素點上梯度量,同時也求得該梯度方向。而在一條輪廓線的垂直切面上可能有數個像素點具有相同梯度方向,但卻有不同輪廓強度。因此 non-maximum supression 便是根據各個像素點的梯度方向,找出強度最強的像素點保留,使得最後的輪廓線更細更精準。
* 在 sobel 的部分中,已對 angle 取絕對值並收斂在 0~$pi$ 之間,即 0~180度,因此四個 case 條件可使用各像素的徑度分出所屬方向
* 確認方向後,該方向會有對應的兩側像素點,此時若當前像素點非三者最大值,則可將其設為 0 省略,否則保留
## Threshoing Edges
此時我們已經有精簡的輪廓線,接著便是以兩個閥值判定各像素點是 strong 還是 weak edge,條件如下:
1. pixel >= maximum: Strong edge,值設為 maximum
2. minimum <= pixel < maximum: Weak edge,值設為 minumum
3. pixel < minimum: 捨棄,設為 0
## Hysteresis
基本概念用兩句話概括:
1. 在 threshoing 中判定為 strong edge 的繼續保持為 strong edge
2. 有和 strong edge 相連的 weak edge 列為 strong edge,否則捨棄
* 實作上可先宣告一個和圖片大小相同的 `visited` 矩陣,初始為 0,訪問過後設為正值。以及另一個容器用於存取待訪問的像素點,以下簡稱為「待辦」
* 先使用兩層 nested loop 去跑所有像素點,若遇到已訪問或非 strong edge的則跳過 (continue)
* 若遇到屬於 strong edge 的像素點,則檢查其四周是否存在 weak & strong edge 像素點,若有則將其加入待辦
* 用一個 while 迴圈持續執行至待辦清單為空,並且待辦中所有像素點都設為 strong edge 且處理周圍像素點方式等同 strong edge
* 待辦為空後回到第二點的迴圈中繼續找 strong edge 直到圖片處理完畢