# 109-2 加課 OpenCV
###### tags:`python lecture` `i2trc2`
# What is OpenCV?
Open Source Computer Vision Library (OpenCV),是一個跨平台的電腦視覺庫。以 C++ 撰寫,包含許多複雜的影像處理演算法,可以用來處理 2D 影像。(3D 影像處理可以參考 [OpenGL](https://zh.wikipedia.org/wiki/OpenGL)) ,OpenCV 提供許多不同語言的介面,例如:Python, Java, MATLAB 等
:::warning
- 不重新 [造輪子](https://zh.wikipedia.org/wiki/%E9%87%8D%E9%80%A0%E8%BD%AE%E5%AD%90),學習使用現有的模組 (library/module)
- 跟 [document](https://docs.opencv.org/master/d6/d00/tutorial_py_root.html) 成為好朋友
:::
### Install
```bash
pip install opencv-python
```
## 基本操作
1. import
要用 module 之前就要先 import ,就跟 C++ 要 include 類似
```python=
import cv2
```
3. 讀圖片 `mat = cv2.imread(filename)` [reference](https://docs.opencv.org/master/d4/da8/group__imgcodecs.html#ga288b8b3da0892bd651fce07b3bbd3a56)
注意檔名 (filename) 要包含路徑喔!
:::warning
#### 檔案系統中檔名的意義
一個完整的檔案名稱,包含檔案的**位置**、**檔案名稱**及**附檔名**,例如:
```
/home/vincent/lecture/test.py # 絕對路徑
./lecture/test.py # 相對路徑
lecture/test.py # 相對路徑
```
如果不特別標示的話一般來說就是指相對路徑,也就是 `./` 可以省略。
- `/`: 根目錄 (root)
- `./`: 目前目錄
- `../`: 上一層目錄
- `~/`: 家目錄 (home)
:::
2. 印圖片出來`cv2.imshow(title, mat)` [reference](https://docs.opencv.org/master/d7/dfc/group__highgui.html#ga453d42fe4cb60e5723281a89973ee563)
執行完這行後, openCV 會建立一個
- 讓視窗可以自由縮放大小 `cv2.namedWindow('th1', cv2.WINDOW_NORMAL)`
- 等待與讀取使用者按下的按鍵,設定為 0 就表示持續等待至使用者按下按鍵為止 `cv2.waitKey(0)`
- 關閉所有 OpenCV 的視窗`cv2.destroyAllWindows()`
:::danger
### imshow 在 colab 中的問題
Colab 中封鎖了 `cv2.imshow()` 的使用,因為它會導致 Jupyter session crash:
![](https://i.imgur.com/OBn6rnA.png)
因此他告訴你要另外用一個叫做 `cv2_imshow()` 的東西取代
:::
### 讀進來的圖片的本質長什麼樣子?
試試看:
```python=
print(mat.shape)
print(type(mat))
```
- 圖片由那些維度所構成?
- 圖片是陣列? 是幾維陣列?
- 每個像素的數值範圍是多少?
<height, width, channel>
### 練習:
- 試著將 colab 掛載 (mount) 到你自己的雲端上 ([教學](https://hackmd.io/GGtwQ-rnTZiIrTYQkuxggw?both#%E9%80%A3%E7%B5%90-google-drive))
- 安裝 opencv python module
執行指令用驚嘆號
```bash
!pip install opencv-python
```
- 然後顯示出一張圖片 (自己找一張照片存到雲端上你創的新資料夾)
P.S. 建議用英文檔名
- import
- imread
- imshow
- 試著回答上面的問題,你讀進來的圖片長什麼樣子?
## 色域轉換 (converting color space)
1. 色域轉換 `mat = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)`
- 參數改成其他的可以轉換成其他顏色空間,轉換表: [參考資料](https://docs.opencv.org/3.4/d8/d01/group__imgproc__color__conversions.html)
- EX: `cv2.COLOR_BGR2HSV` 代表從 BGR 轉換成 HSV
2. 轉灰階
- EX. `mat = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)`
- 可以注意一下 `mat` 的 shape 發生了什麼變化
3. 二值化 `cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)`
二值化也有好幾種方法,可以參考 [這裡](https://docs.opencv.org/master/d7/d4d/tutorial_py_thresholding.html)
## 切割圖片 (cropping)
圖片其實就是一個多維陣列 (numpy array),因此就跟 numpy array 一樣,可以用 indexing 的方式取值
簡單的例子:
```python=
arr = np.arange(10)
print(arr[5:10])
# [5 6 7 8 9]
```
試試看:
```python=
arr = np.arange(100).reshape((10, 10))
print(arr)
print(arr[5:10])
print(arr[4:7, 4:7])
```
### 練習
在你剛剛讀進來的圖片中,正中間取一個寬度 `w`, 高度 `h` 的長方形,並且顯示出來。
Tips:
- 你需要知道在 X、Y 方向的圖片起點跟終點座標,如何計算?
![](https://i.imgur.com/TIJRFn9.png)
## 遮罩 (Masking)
只取出你要的部分
這個在很多應用上常常會需要,你只想要圖片中的某個東西,其他全部變成黑色或填成白色之類的,總之你只想要對某個部分做處理。
通常對於這樣的需求,我們會先準備一個跟原本的 `mat` 相同大小的遮罩 (mask),然後利用這個遮罩對影像做各種操作,例如說
- And 運算: example
```python=
img = cv2.imread('./input.bmp')
mask = np.zeros(img.shape[:2], dtype=np.uint8)
mask = cv2.rectangle(mask, (0, 0), (30, 30), 255, cv2.FILLED)
# mask[0:30, 0:30] = 255 # This also works!
res = cv2.bitwise_and(img, img, mask=mask)
cv2_imshow(mask)
cv2_imshow(res)
```
- [drawing a rectangle](https://docs.opencv.org/master/d6/d6e/group__imgproc__draw.html#ga07d2f74cadcf8e305e810ce8eed13bc9)
## 基本影像處理
### 模糊化 [Smoothing Image](https://docs.opencv.org/master/d4/d13/tutorial_py_filtering.html)
各種模糊處理主要的差異在 Kernel 不一樣,不同的 Kernel 會有不同的特性。
- Averaging
[`dst = cv.blur( src, ksize[, dst[, anchor[, borderType]]] )`](https://docs.opencv.org/master/d4/d86/group__imgproc__filter.html#ga8c45db9afe636703801b0b2e440fce37)
Example: 用大小為 5x5 的 kernel 做 average smoothing
```python=
blur = cv.blur(img, (5,5))
```
- Gaussian Blurring (Gaussian Distribution)
[`dst = cv.GaussianBlur( src, ksize, sigmaX[, dst[, sigmaY[, borderType]]] )`](https://docs.opencv.org/master/d4/d86/group__imgproc__filter.html#gaabe8c836e97159a9193fb0b11ac52cf1)
什麼是 Gaussian Distribution 咧? 又稱為常態分佈,安捏懂了拔
![](https://www.boost.org/doc/libs/1_49_0/libs/math/doc/sf_and_dist/graphs/normal_pdf.png)
- kernel size 必須是**正**整數且為**奇數**
kernel 的標準差
- sigmaX: X 方向標準差
- sigmaY: 若沒有給,則預設跟 sigmaX 一樣
- 若 sigmaX 及 sigmaY 皆為 0 則由電腦自行計算
Example:
```python=
blur = cv.GaussianBlur(img,(5,5),0)
```
> Gaussian blurring is highly effective in removing Gaussian noise from an image.
- Median Blurring (啊就取中間值啦)
計算在 kernel 範圍內的 中間值,並用該值取代該點。
[`dst = cv.medianBlur( src, ksize[, dst] )`](https://docs.opencv.org/master/d4/d86/group__imgproc__filter.html#ga564869aa33e58769b4469101aac458f9)
- kernel size 必須是**正**整數且為**奇數**
> This is highly effective against **salt-and-pepper noise** in an image.
- Bilateral Filtering
一個能夠濾掉雜訊又能保留邊緣特徵的演算法
[`dst = cv.bilateralFilter( src, d, sigmaColor, sigmaSpace[, dst[, borderType]] )`](https://docs.opencv.org/master/d4/d86/group__imgproc__filter.html#ga9d7064d478c95d60003cf839430737ed)
### 幾何轉換 [Geometric Transformations of Images](https://docs.opencv.org/master/da/d6e/tutorial_py_geometric_transformations.html)
- 比例縮放 (Scale)
[`dst = cv.resize( src, dsize[, dst[, fx[, fy[, interpolation]]]] )`](https://docs.opencv.org/master/da/d54/group__imgproc__transform.html#ga47a974309e9102f5f08231edc7e7529d)
- dsize: 給定要 resize 的大小
- fx, fy: scale 的比例,EX. 0.5 表示每隔兩個像素材取值
- interpolation: 插值的方法 [列表](https://docs.opencv.org/master/da/d54/group__imgproc__transform.html#ga5bb5a1fea74ea38e1a5445ca803ff121)
Example:
```python=
res = cv.resize(img,(2*width, 2*height), interpolation = cv.INTER_CUBIC)
```
- 旋轉 (rotate)
[`dst = cv2.cv.rotate( src, rotateCode[, dst] )`](https://www.geeksforgeeks.org/python-opencv-cv2-rotate-method/)
```python=
rot = cv2.rotate(img, cv2.ROTATE_90_CLOCKWISE)
```
:::info
一般來說,大部分的旋轉縮放都可以用 [`warpAffine()`](https://docs.opencv.org/master/da/d54/group__imgproc__transform.html#ga0203d9ee5fcd28d40dbc4a1ea4451983) 做到,只要提供一個 2x3 的 transformation matrix,這個 matrix 包含 translation 及 rotation 的資訊。
$\rightarrow$ [完整的解釋](https://docs.opencv.org/master/d4/d61/tutorial_warp_affine.html)
Example:
```python=
rot_mat = cv.getRotationMatrix2D( center, angle, scale )
warp_rotate_dst = cv.warpAffine(warp_dst, rot_mat,
(warp_dst.shape[1], warp_dst.shape[0]))
```
甚至還可以改變視角 [`warpPerspective()`](https://docs.opencv.org/master/da/d54/group__imgproc__transform.html#gaf73673a7e8e18ec6963e3774e6a94b87)
其實還有很多神奇的 function 你可以自己去 [挖掘](https://docs.opencv.org/master/da/d54/group__imgproc__transform.html)
:::
### 形態學(Morphology)應用
- 侵蝕(Erosion)
![](https://sites.google.com/a/ms.ttu.edu.tw/cse2012dance-robot/_/rsrc/1472870795599/yan-jiu-cheng-guo/opencv-ruan-ti-she-ji/qin-shi-yu-peng-zhang/Noname.jpg?height=235&width=400)
- 膨脹(Dilation)
![](https://sites.google.com/a/ms.ttu.edu.tw/cse2012dance-robot/_/rsrc/1472870793636/yan-jiu-cheng-guo/opencv-ruan-ti-she-ji/qin-shi-yu-peng-zhang/Noname9.jpg?height=231&width=400)
- 關閉(Closing)
先做膨脹,再做侵蝕,填補洞
![](https://i.imgur.com/5gzmWVp.png)
- 開啟(Opening)
先做侵蝕,再做膨脹,把太小的弄不見
![](https://i.imgur.com/xLItyLm.png)
```python=
kernel = np.ones((5,5), np.uint8) # kernel
dilation_img = cv2.dilate(img, kernel, iterations = 1) # dilation 運算
erosion_img = cv2.erode(img, kernel, iterations = 1) # erosion 運算
opening = cv.morphologyEx(img, cv.MORPH_OPEN, kernel) # open
closing = cv.morphologyEx(img, cv.MORPH_CLOSE, kernel) # close
```
[reference1](https://sites.google.com/a/ms.ttu.edu.tw/cse2012dance-robot/yan-jiu-cheng-guo/opencv-ruan-ti-she-ji/qin-shi-yu-peng-zhang), [reference2](https://medium.com/%E9%9B%BB%E8%85%A6%E8%A6%96%E8%A6%BA/%E5%BD%A2%E6%85%8B%E5%AD%B8-morphology-%E6%87%89%E7%94%A8-3a3c03b33e2b), [其他的型態學運算](https://docs.opencv.org/master/d9/d61/tutorial_py_morphological_ops.html)
## 找邊界
在很多應用中,常常會需要用到影像的邊界資訊, openCV 當然也有提供相對應的 function,關於 contours [這邊](https://docs.opencv.org/master/d4/d73/tutorial_py_contours_begin.html) 有完整的說明
- [`contours, hierarchy = cv.findContours( image, mode, method[, contours[, hierarchy[, offset]]])`](https://docs.opencv.org/master/d3/dc0/group__imgproc__shape.html#gadf1ad6a0b82947fa1fe3c3d497f260e0)
- 偵測到的 contour 會以 list of points 的型式儲存在 `contours` 裡面
- `hierarchy` 用來記錄找到的 contours 之間的關係,並且如何記錄會根據你給的 method 的方法不同而不同, [這裡](https://docs.opencv.org/master/d9/d8b/tutorial_py_contours_hierarchy.html) 有詳細的解釋。
hierarchy 是一個 2-D array ,格式是這樣:
[[Next, Previous, First_Child, Parent]...]
- `RETR_LIST`: 找出所有的 contours ,並且忽略 hierarchy 的差異
- `RETR_EXTERNAL`: 只看最外層的
- `RETR_CCOMP`:
- `RETR_TREE`
- 要不要簡化找到的點
- `cv.CHAIN_APPROX_NONE`: 不簡化
- `cv.CHAIN_APPROX_SIMPLE`: 簡化重複的點跟線
- [`cv2.drawContours()`](https://docs.opencv.org/master/d6/d6e/group__imgproc__draw.html#ga746c0625f1781f1ffc9056259103edbc)
- [`retval = cv.boundingRect( array )`](https://docs.opencv.org/master/d3/dc0/group__imgproc__shape.html#ga103fcbda2f540f3ef1c042d6a9b35ac7)
找到可以包住給定的 contour 的矩形
- [`retval = cv.contourArea( contour[, oriented] )`](https://docs.opencv.org/master/d3/dc0/group__imgproc__shape.html#ga2c759ed9f497d4a618048a2f56dc97f1)
計算給定的 contour 面積 (回傳浮點數)
Example:
```python=
contours, hierarchy = cv2.findContours(img,
cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
# Draw all contours
# -1 signifies drawing all contours
cv2.drawContours(img, contours, -1, (0, 255, 0), 3)
```