---
# System prepended metadata

title: 人工智慧深度學習概論與應用 HW1 重點整理

---

# 人工智慧深度學習概論與應用 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：
        ![](https://i.imgur.com/VULM9Wo.png)
    * Kernel：
        ![](https://i.imgur.com/swndawH.png)
    * 可得到如下 kernel
        ![](https://i.imgur.com/3ZUT9fb.png)

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 中有提供，以下直接敘述實作概念。
    
    ![](https://i.imgur.com/pfKHJIl.png)
    ![](https://i.imgur.com/NU1VPqk.png)
    
    * 由於計算時需要使用以放大後各座標於原圖對應座標為中心的 $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)$ 開始。
        
        ![](https://i.imgur.com/jeJGXXb.jpg)

        
        以上可解釋如何從新圖中的座標換算至舊圖，接著重點來了，上方所述的偏移起點會是該對應原圖座標中的像素起點(左上)，如新圖中的 $(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

![](https://i.imgur.com/9gegwuQ.png)

索伯運算子用於計算一 $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 直到圖片處理完畢