# 前言
RCNN開創了物件偵測的先鋒,接續又出現了Fast RCNN與Faster RCNN,但以上的演算法具有以下缺點:使用選擇性搜索(Selective Search)、不屬於端到端的演算法、使用多種模型等。而在2016年,[YOLO(You Only Look Once)](https://www.cv-foundation.org/openaccess/content_cvpr_2016/papers/Redmon_You_Only_Look_CVPR_2016_paper.pdf)的問世解決了上述提到的問題。
原本的物件偵測任務皆是透過分類器進行分類,但在YOLO中則是將其視為回歸任務。YOLO將輸入影像切割成```SxS```的grid cell,並從這些grid cell中找出邊界框(Bounding Box)並計算出類別的機率。整個過程只使用了一個神經網路,因此可視為一個End-to-End的演算法。
這樣的架構使整個偵測過程十分快速,可達到即時每秒45幀。而另外一個較小型的Fast YOLO不僅可達到每秒155幀的執行速度,mAP ( Mean Average Precision ) 也是其他即時物件偵測系統的兩倍。之後作者也發表了一系列的YOLO模型(v2, v3,...)。本篇文章不會深入介紹其背後架構與技術,而是專注於介紹如何將YOLO應用於實際技術中。如對YOLO有興趣的人可以參考:[YOLO演進](https://medium.com/ching-i/yolo演進-2-85ee99d114a1)、[一文弄懂YOLO算法](https://zhuanlan.zhihu.com/p/52661111)、[圖解一階段物件偵測算法](https://youtu.be/sq_OfIhb5Oc?si=_DSNkeWUBJBch63T)。
# 說明
**本篇文章的目的:**
- 透過LabelImg標註自己的資料,並製作訓練集
- 使用custom dataset建立打瞌睡偵測模型
**How it works**
- Install and Import Dependencies
- Load Model
- Make Detections
- Real Time Detections
- Collect Images
- LabelImg
- Train from Scratch
- Testing
## Install and Import Dependencies
第一步要先安裝YOLO,我們使用的是ultralytics所釋出的[YOLO v5](https://github.com/ultralytics/yolov5.git)。但為了避免干擾到原本的環境,建議可以先建立一個虛擬環境,有以下兩種方法:
- 第一種:Python
```python=
# 建立虛擬環境
python -m venv <venv name>
# 啟動環境
source <venv name>/bin/activate
```
- 第二種:anaconda
```python=
# 建立虛擬環境
conda create --name <venv name>
# 啟動環境
conda activate <venv name>
```
安裝虛擬環境後,就可以安裝YOLO囉
```command=
!git clone https://github.com/ultralytics/yolov5
```
安裝YOLO v5所需的環境與套件
```=
!pip3 install -r ./yolov5/requirements.txt
```
接著就可以```import```套件了
```python=
import torch
import numpy as np
import matplotlib.pyplot as plt
import cv2
```
## Load Model
第二步就是要載入YOLO模型
```python=
model = torch.hub.load("ultralytics/yolov5", "yolov5s")
```
- [torch hub](https://pytorch.org/hub/)存有許多官方與非官方所開發的(預訓練)模型,也可以發表自己的模型
可以將模型的架構印出來,如下圖所示
```python=
model
```
==YOLO模型架構==

## Make Detections
載入模型後,就可以載入影像來做測試,確認模型能夠正常運作
```python=
img = "https://media.cntraveler.com/photos/53e2f41cdddaa35c30f66775/16:9/w_1280,c_limit/highway-traffic.jpg"
result = model(result)
result.print()
```
==output結果如下圖所示==

- 上圖的結果包含了影像大小、偵測到的物件、偵測速度等資訊
<br>
接著我們可以透過```render()```把結果畫出來
```python=
%matplotlib inline
plt.imshow(np.squeeze(result.render()))
plt.show()
```
==偵測結果如下圖所示==

**NOTE:**
```python=
result
```
如果直接執行上述程式,只是一個base class,==如下圖所示==

```python=
result.
```
若輸入上述的程式,並按```tab```,可看到很多```function```,==如下圖所示==

而```result.render()```是會回傳將detection畫上影像的結果,==如下圖所示==

由於```result.render()```是list,所以無法直接透過```matplotlib```畫出,所以要先轉成```array```。但直接透過```np.array()```轉,其shape會多出一個batch size的參數。為了得到```(720, 1280, 3)```的影像,就須透過```np.squeeze()```。
<br>
## Real Time Detections
確認YOLO能夠執行之後,我們可以先玩看看real time的偵測。透過```cv2.VideioCapture()```來打開攝影機,並擷取到影像。
```python=
# 調用攝影機
cap = cv2.VideoCapture(0)
# 確認攝影機是否正常開啟
if not cap.isOpened():
print("Cannot Open Camera...")
exit()
while True
# 讀取攝影機影像
ret, frame = cap.read()
# 確認是否能正常讀取影像
if not ret:
print("Cannot Receieve Frame...")
break
# make detections
result = model(frame)
cv2.imshow("YOLO", np.squeeze(result.render()))
if cv2.waitKey(10) & 0xFF == ord('q'):
break
# 釋放攝影機,並關閉視窗
cap.release()
cv2.destroyAllWindows()
```
## Collect Images
接著我們就可以建立打瞌睡偵測模型了。但再建模型之前,先需要有打瞌睡和醒著兩種狀態的影像來建立資料集,所以需要先建立影像,再透過LabelImg標記。
- 收集影像
```python=
# collect awake and drowsy images from camera
import uuid # unique identifier to name the images
import os
import time
# 影像存放位置
IMAGES_PATH = os.path.join('data', 'images') # data/images
labels = ['awake', 'drowsy']
number_img = 20
cap = cv2.VideoCapture(0)
# 確認camera正常
if not cap.isOpened():
print("Cannot Open Camera...")
exit()
for label in labels:
print(f'Collect images for {label}')
time.sleep(5)
for num in range(number_img):
print(f'Collect images for {label}, image number {num}')
# 取得影像
ret, frame = cap.read()
# 建立image path
image_name = os.path.join(IMAGES_PATH, label+'.'+str(uuid.uuid1())+'.jpg')
# 儲存影像
cv2.imwrite(image_name, frame)
cv2.imshow('Image Collection', frame)
# 2 seconds delay between capture
time.sleep(2)
if cv2.waitKey(10) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
```
- uuid可產生unique identifier,==如下圖所示==

## LabelImg
接著我們需要安裝[LabelImg](https://github.com/HumanSignal/labelImg.git),建議也可以專門使用LabelImg的虛擬環境。
在安裝的過程中遇到很多問題,照著[LabelImg](https://github.com/HumanSignal/labelImg.git)的Github也無法安裝成功,後來看了網路上的方法可以透過```pip install labelImg```的方式安裝,不需要像Github上的複雜步驟。
而虛擬環境一開始使用Python的方式安裝,但同樣也無法安裝完成,最後使用anaconda的方式,而我是使用max os,其他os並沒有試過,以下是我的安裝方式:
- 安裝虛擬環境
```=
# 建立虛擬環境
conda create --name to_label
# 啟動虛擬環境
conda activate to_label
```
- 安裝LabelImg
```=
pip install labelImg
```
- 安裝好後,輸入
```=
labelImg
```
==如果安裝成功會出現以下的畫面==

==進入標記之前,先給大家看一下整個資料夾的結構==

data資料夾下包含:
- images:存放醒著和打瞌睡的影像
- labels:存放透過LabelImg標記的資訊
**接著就可以標記了,我使用兩張影像做示範**
**快捷鍵:**
* W => 建立Bounding box
* A => 上一張
* D => 下一張
- 開啟影像資料夾

- 設定存放目錄

- 設定為YOLO格式

- 按w開始標記,此影像為drowsy,**標記完記得儲存**

- 接著就會在labels資料夾下看到新增的檔案

* classes.txt:類別檔
* 剛標記的Bouding Box位置
- 按w開始標記,此影像為awake

接著按照這些步驟,把所有的影像都標記完就可以了!
## Train from Scratch
接著就可以透過剛標記好的影像進行訓練了,終於來到這一步了!!!!
```python=
!cd yolov5 && python train.py --img 320 --batch 16 --epoch 500 --data dataset.yaml --weights yolov5s.pt
```
上述的程式有一個```yaml file```需要建立,可以透過Visual Studio Code開啟檔案,並輸入以下資訊。建立完後,請將```yaml file```**放入yolo v5資料夾下**。
```
# Train/val/test sets as 1) dir: path/to/imgs, 2) file: path/to/imgs.txt, or 3) list: [path/to/imgs1, path/to/imgs2, ..]
path: ../data
train: images
val: images
test:
# Classes (80 COCO classes)
names:
0: awake
1: drowsy
```
訓練完後,在```yolov5/runs/train/exp```會看到所有剛訓練的資料,包含訓練完的權重、learning curve與相關結果等。
## Testing
接著就可以把剛訓練完的模型拿來做測試了
- 影像測試
```python=
model = torch.hub.load("ultralytics/yolov5", 'custom', path='yolov5/runs/train/exp3/weights/last.pt', force_reload=True)
img = os.path.join('data', 'images', 'awake.8f423c6a-6f5a-11ee-8139-c4b301bfc059.jpg')
result = model(img)
result.print()
%matplotlib inline
plt.imshow(np.squeeze(result.render()))
plt.show()
```
==偵測結果如下==

- Video測試
```python=
cap = cv2.VideoCapture(0)
while cap.isOpened():
ret, frame = cap.read()
result = model(frame)
cv2.imshow(np.squeeze(result.render()))
if cv2.waitKey() & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
```
[Source Code Please Visit](https://github.com/ChrisCodeNation/Drowsiness-Detection-using-Yolo.git)
# 參考資料
[YOLO v5](https://github.com/ultralytics/yolov5.git)
[LabelImg](https://github.com/HumanSignal/labelImg.git)
[Deep Drowsiness Detection using YOLO, Pytorch and Python](https://youtu.be/tFNJGim3FXw?si=OZH1QIqIxVB4e5vM)