###### tags: `選修`
# 進階程式設計(Python專題)
:::success
**評量方式**
:::
1. 上課練習 50%
2. 專題實作作業 25%
3. 筆試 25%
:::success
**Python 程式語言**
:::
## 一、基本使用
### 1. 編輯軟體
+ [Google Colaboratory](https://colab.research.google.com/)
+ 自學網站:[Teach Python 3 and web design with 200+ exercises - Learn Python 3 - Snakify](https://snakify.org/en/)
### 2. 變數與資料型態
+ 變數:存放資料的記憶體空間(名稱自訂)
+ 數值型態:int(整數)、float(浮點數、小數)、bool(True、False)
+ 字串型態:' ' 或 " " 中的文字
+ 容器型態:
- list:有順序、可變動的資料集合
- tuple:有順序、不可變動的資料集合
- set:無順序的資料集合
- dict:鍵值對(key-value)的集合
+ 資料型態轉換:int()、float()、str()
### 3. 輸入輸出
+ 變數 = input(\[提示字串\])
+ print(項目1\[,項目2,...\])
- 字串要加 ' ' 或 " "
- 可用 + 將字串連接
+ print('A=%d B=%d C=%d' %(A,B,C))
- [Python 3 print 函数用法总结](http://www.runoob.com/w3cnote/python3-print-func-b.html)
+ print('A={} B={} C={}'.format(A, B, C))
- {} 的位置會被後方 format 所帶的參數所取代,預設會依照順序輸出。
- [Python Format String 字串格式化整理](https://blog.jaycetyle.com/2018/01/python-format-string/)
+ print(f'A={A} B={B} C={C}')
- [f-string 是 Python 3.6 才有的方法](https://steam.oxxostudio.tw/category/python/basic/format.html#a3/)
:::info
EX_01:輸入三角形三邊長,以[海龍公式](https://zh.wikipedia.org/zh-tw/%E6%B5%B7%E4%BC%A6%E5%85%AC%E5%BC%8F)求三角形面積。
:::
``` python=
a = input('請輸入邊長1:') # 輸入為字串型態,類似C++的getline()
a = float(a) # 將a字串轉成浮點數存回a
b = ~
c = ~
s = ~
area = ~ # 開根號 **0.5
print('三角形面積為%10.2f' %area)
print('三角形面積為{:10.2f}'.format(area))
print(f'三角形面積為{area:10.2f}')
# %10.2f 固定印出10個字元(含小數點),小數點固定印出2位
# a=5,b=6,c=7,area=14.70
```
<br />
## 二、運算子
### 1. 算數運算子
| 算數運算子 | 意義 | 備註|
|:-------- |:------ |:-------|
| + | 加 | |
| - | 減 | |
| * | 乘 | |
| / | 除 |17/5 為 3.4 |
| // | 取商數 |17//5 為 3 |
| % | 取餘數 |17%5 為 2 |
| ** | 次方 | 5**2 為 25 |
### 2. 關係運算子
| 關係運算子 | 意義 | 備註|
|:-------- |:------ |:-------|
| == | 等於 | =是指派,==才是判斷是否相等 |
| != | 不等於 ||
| > | 大於 ||
| >= | 大於等於 ||
| < | 小於 ||
| <= | 小於等於 ||
+ x < y < z 為 合法條件
### 3. 邏輯運算子
| 邏輯運算子 | 意義 | 備註|
|:-------- |:-----|:---|
| and | 而且 ||
| or | 或者 ||
| not | 否 ||
### 4. 複合指定運算子
+ i += 3 表示 i = i+3
<br />
## 三、選擇
### 1. if 條件式語法
``` python
if 條件:
條件「成立」時的程式碼
```
### 2. if~else 語法
``` python
if 條件:
條件「成立」時的程式碼
else:
條件「不成立」時的程式碼
```
:mega: 以冒號及縮排(1個Tab或4個空白)來表示程式區塊,可開啟『Colab/工具/設定/編輯器/顯示縮排指南』增加辨識度。
:::info
EX_02:承EX_01,先判斷三邊長是否可以構成三角形。如果可以,求三角形面積;如果不行,輸出錯誤。
Tip:每邊長要大於0,且任兩邊之和要大於第三邊。
:::
``` python=
a = ~ input('請輸入邊長1:')
b = ~
c = ~
if a>0 and ~ and a+b>c and ~ :
s = ~
area = ~ # 開根號 **0.5
print('三角形面積為%10.2f' %area)
else:
print('不能構成三角形')
# 程式執行完向使用者Say Goodbye
```
### 3. 巢狀 if 語法
``` python
if 條件1:
if 條件2:
條件1「成立」,且條件2「成立」時的程式碼
else:
條件1「成立」,且條件2「不成立」時的程式碼
else:
條件1「不成立」時程式碼
```
:mega: if或else內,都可再增加條件式。
:::info
EX_03:驗證登入帳號、密碼。
:::
``` python=
user = input('請輸入帳號:')
pwd = input('請輸入密碼:')
if ~ : # 帳號等於'admin' 而且 密碼等於'1234',判斷是否相等「==」
print('Welcome')
else:
print('帳號或密碼輸入錯誤')
```
``` python=
user = input('請輸入帳號:')
pwd = input('請輸入密碼:')
if ~ :
print('Welcome')
else:
~ : # 不等於「!=」
print('帳號錯誤')
~ :
print('密碼錯誤')
```
### 4. 多重選擇 if\~elif\~else 語法
``` python
if 條件1:
條件1「成立」時程式碼區塊
elif 條件2:
條件1「不成立」,且條件2「成立」時的程式碼
elif 條件3:
條件1、2「不成立」,且條件3「成立」時的程式碼
....
else:
上述條件都「不成立」時的程式碼
```
:mega: elif 可視需要增加。
:::info
EX_04:將成績轉換成等級制。
80以上 A,70 ~ 80 B,60 ~ 70 C,60以下 D,其它「錯誤」。
:::
``` python=
score = int(input('請輸入成績:'))
if score >= 80:
print('A')
~ : # 70~80
~
~ : # 60~70
~
~ : # 0~60
~
else:
print('輸入錯誤!')
# Bonus:超過100輸出錯誤
```
<br />
## 四、迴圈
### 1. range 函式
+ 串列變數 = range(整數)
- 產生0~「整數-1」的串列
+ 串列變數 = range(起始值,終止值)
- 產生起始值~「終止值-1」的串列
+ 串列變數 = range(起始值,終止值,間隔值)
- 產生:起始值, 起始值+間隔值, 起始值+間隔值*2, ...
+ [Python range() 函数用法](http://www.runoob.com/python/python-func-range.html)
### 2. for 迴圈語法
``` python
for 變數 in range(重複次數):
程式碼
```
``` python
for 變數 in 串列:
程式碼
a = [3,5,7,1,2]
for x in a:
print(x)
```
``` python
for 變數 in 字典: # 變數會儲存字典的key
程式碼
```
``` python
break:強制跳出「整個」迴圈
continue:強制跳出「此次」 迴圈,繼續進入下一次圈
```
``` python
for 變數 in 串列或字串:
程式碼
else:
迴圈正常結束,執行此區塊程式碼。(break不會執行)
```
:::info
EX_05_1:印出10~50(含50)的偶數。
:::
``` python=
for i in range(~,~,~):
print(~, end = ' ')
```
:::info
EX_05_2:計算1+2+...+n。
:::
``` python=
n = int(input('請輸入正整數:'))
sum = ~ # sum初值設為0
for ~ : # 讓 i 從 1~n
~ # 每次迴圈 sum = sum+i
print('1 到 {} 的整數和為 {} '.format(n, sum)) # 只需輸出一次
# 以 f-string 格式化字符串改寫
```
### 3. 巢狀迴圈語法
``` python
for 變數1 in range(重複次數):
for 變數2 in range(重複次數):
程式碼
```
:::info
EX_06:貝殼紋。
![](https://i.imgur.com/EF4LRvk.png)
:::
:mega: Colab 上使用 Turtle
``` python=
!pip3 install ColabTurtle
```
``` python==
from ColabTurtle.Turtle import *
initializeTurtle()
# speed(10) # 讓海龜暴走
right(100) # 先轉100度
len = 180 # 設一開始正方形的長度為180
for ~ : # 正方形數量 70 個
for ~ : # 1個正方形有4個邊
forward(len)
right(90)
~ # 下一個正方形多轉10度
~ # 下一個正方形長度少3
```
### 4. while 迴圈語法
``` python
while 條件判斷: # 條件為「真」的時候繼續執行
程式碼
```
:::info
EX_07:while 存錢買手機。
:::
``` python
i = 1
~ # sum 初值為 0
while ~ : # 存款金額小於 25000
money = int(input('請輸入第 ' + str(i) + ' 個月的存款: '))
~ # 把 money 加到 sum
~ # i+1
print('存了 {} 個月,總共 {} '.format(i-1, sum))
```
## 五、複合式資料型態
### 1. [list(串列)](https://www.twblogs.net/a/5cd7d860bd9eee67a77f8884)
+ 串列變數 = [ 元素1, 元素2, .... ]
- 類似「陣列」,但元素可以是不同型態。
- lst = [1, 2, 3, 4, 5]
+ 列表對「+」和「$*$」的操作與字串相似。「+」用予組合列表,「$*$」用於重複列表。
| 串列運算 | 範列 |
|:-------- |:-------------- |
| + |[1, 2]+[3, 4]為[1, 2, 3, 4]|
| $*$ |[1, 2]\*3為[1, 2, 1, 2, 1, 2]|
| x in lst |判斷x是否在串列, 3 in [1, 2, 3]為True|
| == |判斷兩串列是否相等, [1, 2] == [1, 2, 3]為False|
+ 運用 [] 取值,可以使用 index(索引值從0開始)、slice 來存取陣列裡的資料。假設lst,lst2為串列變數,常用的運算和方法如下表
| slice運算 | 意義 |
|:------------ |:-------------- |
| lst[i] | 取出索引值i的元素 |
| lst[-1] | 取出最後一個元素 |
| lst[i:j] | 取出索引值i~j-1的元素 |
| lst[i:j:k] | 取出索引值i~j-1的,間隔為k的元素 |
| lst[i:j] = lst2| 把索引值i~j-1的元素換成lst2的元素 |
| del lst[i:j] | 刪除索引值i~j-1的元素 |
| 串列函式 | 意義 |
|:------------ |:-------------- |
| lst = list() 或 lst = [] |宣告空的串列|
| len(lst) | 回傳串列個數 |
| max(lst) | 回傳串列中的最大值 |
| min(lst) | 回傳串列中的最小值 |
| list('abc') | 將'abc'拆成'a','b','c'加入串列 |
| 串列方法(函式) | 意義 |
|:---------------|:-------------- |
| lst.append(x) | 將x附加到串列後面 |
| lst.insert(i,x)| 將x插入到索引值i的位置 |
| lst.extend(x) | 將串列x中的所有元素,附加到串列後面。<br />a = [1,2] b = [3,4] <br /> a.append(b) vs. a.extend(b)|
| lst.remove(x) | 刪除串列中的第一個x |
| lst.pop(i) | 回傳索引值i的元素,並將其刪除。如果沒有i,則傳回最後一個元素並刪除 |
| lst.index(x) | 回傳第一次出現x的索引值 |
| lst.count(x) | 計算出現x的次數 |
| lst.sort() | 將串列中的元素小->大排序,大->小則加入參數reverse = True |
| lst.reverse() | 將串列中的元素反轉 |
| lst2 = lst.copy()| copy串列 |
| lst.clear() | 清除串列內所有元素 |
+ 多維list
- 直接建立
``` python
lst = [ [1, 2, 3], [4, 5, 6] ]
```
- 使用[列表生成式(List Comprehensions)](https://www.liaoxuefeng.com/wiki/1016959663602400/1017317609699776)建立, 類似數學集合的表示法
$\{ 2n+1 \mid n \in \{0, 1, 2, 3, 4\} \} \rightarrow \{1,3,5,7,9\}$
``` python
lst = [2*n+1 for n in range(5)]
```
``` python
a = [1,2,3,4,6,7,13,21]
lst = [ n for n in a if n%2 == 0] # lst = [2, 4, 6]
lst = [ [0 for i in range(3)] for j in range(2) ] # lst = [[0, 0, 0], [0, 0, 0]]
```
- 使用numpy模組建立
``` python
import numpy as np
lst = np.zeros((2, 3), dtype=np.int)
```
### 2. tuple(元組)
- 元組變數 = ( 元素1, 元素2, .... )
- 類似 list,但元素個數及元素值不能改變,執行效能較好。
- 列表使用 [],而元組使用 ()。
- 元組內的元素不可被新增、刪除,但可以重新定義整個元組的資料。
``` python
ct = ('台北', '台中', '高雄')
print(ct)
ct = ('台北市', '台中市', '高雄市')
print(ct)
```
- 元組可與串列互換
``` python
lst = ['台北', '台中', '高雄']
tpl = tuple(lst)
print(tpl)
tpl = ('台北市', '台中市', '高雄市')
lst = list(tpl)
print(lst)
```
### 3. set(集合)
+ 集合變數 = { 元素1, 元素2, .... }
- 串列: []
元組: ()
集合: {}
+ 在set裡面存放的是一群無順序且唯一的元素,可以是不同的型態。
``` python
lst1 = [ 1, 2, 3, 1, 2, 3 ]
st = set(lst1) # { 1, 2, 3 }
lst2 = list(st) # [ 1, 2, 3 ]
```
+ 集合沒有索引值,集合不能用索引(index)與切片(slicing)取得內含的資料。
+ 假設s、t為集合變數,常用的方法如下表
| 集合方法(函式) | 意義 |
|:--------------------|:---------- |
| s = set() | 建立空的集合 |
| s.add(x) | 將x加入集合 |
| s.update(lst) | 將lst內的元素加入集合s |
| s.discard(x) | 將x自集合中刪除,x不存在不會報錯 |
| s.remove(x) | 將x自集合中刪除,x不存在會報錯 |
| s.clear() | 將集合清空 |
| len(s) | 回傳集合的元素數量 |
| x in s | x 是否在集合 |
| x not in s | x 是否不在集合|
| s.issubset(t) | s 是否為 t 子集|
| s.issuperset(t) | s 是否包含 t|
| s.intersection(t) |交集 & |
| s.union(t) |聯集 \| |
| s.difference(t) |差集 - |
:::info
EX_08_1:大樂透電腦選號。
:::
``` python=
import random as rnd
rnd.seed() # rnd.seed(2)會重複
~ # 建立一個空串列lst
for i in range(6):
lst.~(rnd.randint(1, 49)) # 隨機產生1~49的整數放入串列
for ~ : # 用for迴圈把串列中的數字印出
print(n, end=' ')
# for i in range(6):
# print(lst[i], end=' ')
```
:::info
EX_08_2:大樂透電腦選號(解決數字重複的問題)。
:::
``` python=
import random as rnd
rnd.seed(2)
~ # 建立一個空的集合
while ~ : # 集合長度 < 6
s.~(rnd.randint(1, 49)) # 集合的元素不會重複
''' 先以lst的方式完成
lst = []
while ~ : # 串列長度 < 6
n = rnd.randint(1, 49)
if ~ : # n不在串列中
lst.append(n)
'''
print('大樂透中獎號碼:', s)
```
:::info
EX_08_3:大樂透電腦選號(將數字排序顯示)。
:::
``` python=
import random as rnd
rnd.seed()
s = set() # 建立一個空的集合
while len(s) < 6: # 集合長度 < 6
s.add(rnd.randint(1, 49)) # 集合的元素不會重複
~ # 用集合的元素建立一個串列
~ # 用sort方法,將串列中的元素排序
print('大樂透中獎號碼:', lst)
```
### 4. dict(字典)
+ 字典變數 = { 鍵1:值1, 鍵2:值3, .... }
``` python
ascii = { 'a': 1, 'b': 98, 'c': 99 }
ascii['a'] = 97 # 修改
ascii['d'] = 100 # 新增
color = { 'red': [255, 0, 0], 'green': [0, 255, 0], 'blue': [0, 0, 255] }
```
+ [複雜字典範例](https://medium.com/ccclub/ccclub-python-for-beginners-tutorial-533b8d8d96f3)
+ dict(字典)類似C++ map,儲存的資料為「key(鍵)」與「value(值)」,可以快速取出對應值,key 不能重複。
+ 字典的操作與串列大同小異,最大的差別在於串列透過數字索引值,字典為鍵值。
+ dict(字典)資料沒有順序的觀念,list(串列)用於儲存有順序性的資料。
+ 假設dt、dt2為字典變數,key為鍵,val為值,常用的方法如下表
| 字典方法(函式) | 意義 |
|:-------------|:------------ |
| dt = dict() 或 dt = {} |宣告空的字典|
| dt.clear() | 清除字典所有內容 |
| dt.fromkeys(lst[, val]) | 建立新字典,以lst序列中的元素為鍵,val為所有鍵對應的初始值|
| dt.get(key) | 透過key取得相對應的val |
| del dt['key']| 刪除鍵為'key'的元素 |
| 'key' in dt | 判斷key是否存在於dt |
| dt.keys() | 取得所有鍵 |
| dt.values() | 取得所有值 |
| dt.items() | 取得所有鍵值對,將(key,val)組成一個tuple |
| dt.update(dt2)| 使用 dt2 的值去更新 dt 內相同鍵的值|
| len(dt) | 回傳字典的元素數量|
:::info
EX_09:字典操作練習。
:::
``` python=
# 1.宣告
ascii = {'a':1, 'b':98, 'c':99 }
color = {'red':[255, 0, 0], 'green':[0, 255, 0], 'blue':[0, 0, 255]}
type(color)
# 2.取值
print(~) 印出紅色的RGB
# 3.修改
~ # 將 'a' 的 ascii 改為 97
print(ascii)
# 4.新增
~ # 新增 'd' 的 ascii 改為 100
print(ascii)
# 5.刪除
~ # 刪除 'a' 的資料
print(ascii)
# 6.取得color字典的所有鍵 keys()
print(~)
for key in color.keys():
print(key)
[key for key in color.keys()]
# 7.取得color字典的所有值 values()
print(~)
# 8.取得color字典的所有鍵值對 items()
print(~)
for ~ in color.items(): # 將鍵值對的key指定到color_name,value指定到rgb後列印出來
print(f'{color_name:6} 的 RGB 為 {rgb}')
```
<br />
## 六、函式
+ 將會重複使用的程式碼聚集在同一個區塊,透過呼叫函式名執行。
### 1. 內建函式庫
+ 不需要任何引入動作,就可以直接使用,最基本、核心的函式庫。
+ [常用內建函式庫]( https://codertw.com/%E7%A8%8B%E5%BC%8F%E8%AA%9E%E8%A8%80/372961/)
+ [Python Built in Functions](https://www.w3schools.com/python/python_ref_functions.asp)
| 函式 | 意義 |
|:---------------|:------ |
| abs(x) |x的絕對值|
| pow(x,y) |x的y次方|
| round(x,n) |將x四捨五入到小數點後n位|
| max(x1,x2,...) |傳回最大值|
| min(x1,x2,...) |傳回最小值|
| sorted(串列) |將串列排序|
| sum(串列) |傳回串列元素和|
+ 字串(string)相關函式
- [Python 字串操作(string替換、刪除、擷取、複製、連線、比較、查詢、包含、大小寫轉換、分割等)](https://codertw.com/%E7%A8%8B%E5%BC%8F%E8%AA%9E%E8%A8%80/358746/)
- [RUNOOB Python 字符串](http://www.runoob.com/python/python-strings.html)
| 字串相關函式 | 意義 |
|:----------------|:------ |
| str(n) |將變數n轉為字串型態|
| len(s) |計算變數s的長度|
| str.lower() |將字串中的字母轉成小寫|
| str.upper() |將字串中的字母轉成大寫|
| str.islower() |判斷字串中的字母是否皆為小寫|
| str.isupper() |判斷字串中的字母是否皆為大寫|
| str.find('abc') |尋找字串中'abc'的位置|
| str.count('abc')|計算字串中'abc'出現的次數|
| str.replace(舊子字串,新子字串)|將字串中的舊子字串用新子字串取代|
| str.split()|將字串分割成數個子字串(以空白區分)|
| str.strip()|去掉字串左右空白|
| str.join(sequence)| 指定字符(str)連接串列(sequence)中的元素後,生成新字串 |
| 'ab' in 'abcd' | 判斷'ab'是否在'abcd' |
| str[::-1] | 將字串反轉 |
| [] | 操作同list |
:::info
EX_10:多元選修系統將選課名單匯出成[CSV檔](https://zh.wikipedia.org/wiki/%E9%80%97%E5%8F%B7%E5%88%86%E9%9A%94%E5%80%BC),請幫老師將名單變成點名表(依字母排序且名字的第一個字母大寫)。
+ [python多個變數的for迴圈](https://www.itread01.com/content/1544266142.html)
+ [Python zip()函數](http://www.runoob.com/python3/python3-func-zip.html)
+ [Python enumerate() 函数](http://www.runoob.com/python/python-func-enumerate.html)
:::
``` python=
str = 'john, arno, eden, charles, steve, david, joe, adam, ben, haley '
lst = ~ # 使用split函式,將str以「,」為分隔分割
for i in ~ : # lst裏的每個名字,以replace函式去除空白
lst[i] = ~
'''
不可以用下面的程式,Why?
for name in lst:
name = name.replace...
'''
lst.sort()
for name in lst:
print(name[~].upper() + name[~]) # 字串的索引值從「0」開始,名字的第「0」個字母變大寫。取字串內部字元的方法同list
# 在名字前印出座號,使用 zip() 示範
num = range(~)
for no, name in zip(~, ~):
print(no,name[0].upper()+name[1:])
# Bonus:使用 enumerate()
```
:::warning
AI 選修綜合練習題:[c295: APCS-2016-1029-2最大和](https://zerojudge.tw/ShowProblem?problemid=c295)
+ [python map 使用方法](https://www.wongwonggoods.com/python/python-map/)
+ [Python map() function - JournalDev](https://www.journaldev.com/22960/python-map-function)
- map 函式會回傳 map object(一種iterator)可用list()函式轉換成list
+ [Print lists in Python (4 Different Ways) - GeeksforGeeks](https://www.geeksforgeeks.org/print-lists-in-python-4-different-ways/)
:::
``` python=
n, m = map(int, input().split()) # inpt()輸入為字串型態,類似C++的getline()
rowMax = []
sum = 0
for i in range(n):
lst = list(map(int, input().split()))
lst = [int(x) for x in input().split()]
```
### 2. 標準函式庫
+ [Python 標準函式庫 (Standard Library)](https://python-doc-tw.github.io/library/index.html)
+ 使用前需先將該函式的套件匯入,套件匯入方法
- from 套件名稱 import 模組名稱
``` python
from math import gcd, pow # 使用時不用輸入套件名稱
```
:mega: 用『from 套件名稱 import *』,會全部匯入,如果在不同套件中有相同的函式名稱,易造成混亂,==不推薦使用全部匯入的方式==。
``` python
from random import *
randint(1, 10) # 產生 1~10 的亂數
from numpy.random import *
randint(1, 10) # 產生 1~9 的亂數,同名函式造成混亂
```
- import 套件名稱
``` python
import random # random套件中的『所有』函式均匯入
random.randint(1, 10) # 套件名稱.函式名稱
```
- import 套件名稱 as 別名
``` python
import random as rnd # random套件中的『所有』函式均匯入
rnd.randint(1, 10) # 別名.函式名稱
```
### 3. 第三方套件(外部函式庫)
+ 不要重造輪子
![](https://i.imgur.com/1tVoiLV.png =300x)
+ [Python Package Index(幾乎包含所有主流套件)](https://pypi.python.org/pypi)
+ 使用前需要先行安裝的函式庫
### 4. 自訂函式
``` python
def 函式名稱([參數1, 參數2, ....]):
程式碼
[return 回傳值1, 回傳值2, ....] # 函數可以同時返回多個值(一個tuple)
```
``` python
def 函式名稱([參數1=預設值, 參數2, ....]):
程式碼
[return 回傳值1, 回傳值2, ....] # 函數可以同時返回多個值(一個tuple)
```
``` python
def 函式名稱(*參數): # 參數數量不定,以tuple儲存
程式碼
[return 回傳值1, 回傳值2, ....]
```
:::info
EX_11:寫一函式可以計算n!,並以之求 $C(n,k)=\frac{n!}{(n-k)! * k!}$,例如C(4,2)=6。
:::
```python=
def f(n): # 計算 n!
mul = 1
for ~
~
return mul
n,k = map(int, input('請輸入n、k(空白隔開): ').split()) # map會把函式依次作用到list的每個元素上
ans = ~
print(ans)
```
<br />
:::success
**程式設計專題實作**
:::
## 一、網路爬蟲
### 1. requests 模組
+ requests 模組能發送 HTTP/1.1 的請求,以取得伺服器回應的 HTML 文件。
+ [請求方式 get、post 的差別](https://blog.toright.com/posts/1203/%E6%B7%BA%E8%AB%87-http-method%EF%BC%9A%E8%A1%A8%E5%96%AE%E4%B8%AD%E7%9A%84-get-%E8%88%87-post-%E6%9C%89%E4%BB%80%E9%BA%BC%E5%B7%AE%E5%88%A5%EF%BC%9F.html)
``` c
import requests
回應內容 = requests.請求方式('網址', 參數)
```
```c=
import requests
url = 'https://cs.cysh.cy.edu.tw'
html = requests.get(url)
html.encoding = 'utf-8'
# print(html) # <Response [200]> 為 HTTP 的狀態碼,表示成功回應
# print(html.text) # 比對網頁原始檔是否一樣
```
+ [HTTP 狀態碼](https://developer.mozilla.org/zh-TW/docs/Web/HTTP/Status)
### 2. BeautifulSoup 模組
+ [爬蟲使用模組介紹-Beautiful Soup (1)](https://ithelp.ithome.com.tw/articles/10206419)、[(2)](https://ithelp.ithome.com.tw/articles/10206668)
+ [HTML 網頁架構](https://pydoing.blogspot.com/2011/12/html-5-page-structure.html)
+ [BeautifulSoup 模組](https://ithelp.ithome.com.tw/articles/10186119)能拆解 HTML 文件為 bs4 物件,接著能使用標籤(tag)、id 名稱、class 名稱,取得網頁元素的內容及屬性。
``` c
from bs4 import BeautifulSoup
bs4物件 = BeautifulSoup(HTML原始碼, 'lxml')
```
```c=
from bs4 import BeautifulSoup
sp = BeautifulSoup(html.text, 'lxml') # 指定 lxml 作為解析器
# print(sp)
# print(sp.h3)
# print(sp.h3.text)
```
### 3. BeautifulSoup常用方法
+ find():傳回第一個符合條件的標籤,找到會傳回一個字串,如果找不到則傳回 None。
```c=
sp.find('footer')
# sp.find('footer', id='footer') # 亦可增加 HTML 屬性值進行搜尋
# sp.find('ul', class_='copyright') # 找 ul 標籤,class 為 copyright,class 為 python 的保留字,Beautiful Soup 改以 class_ 代表 HTML 節點的 class 屬性
# sp.find('ul', class_='copyright').li
```
+ find_all() :傳回有所符合條件的標籤,找到會傳回一個串列,如果找不到則傳回空的串列。
```c=
a_tags = sp.find_all('a') # 所有 a 標籤組成的 list
# print(a_tags)
# print(a_tags[1])
for itm in a_tags:
print(itm)
# print(itm.text)
```
+ bs4物件.get(屬性名稱) 或 bs4物件['屬性名稱']:可取出標籤的屬性
```c=
ul_tags = sp.find('ul', class_='alt')
# print(ul_tags)
ul_a_tags = ul_tags.find_all('a')
# print(ul_a_tags)
for itm in ul_a_tags:
print(itm)
# print(itm.get('href')) # 讀取 <a> 標籤中 href 的屬性(超連結)
# print(itm['href'])
```
:::info
EX_01:[台灣彩卷威力彩](https://www.taiwanlottery.com.tw)
+ Chrome/F12:開發人員工具
:::
```c=
# EX_01:台灣彩卷威力彩
import requests
from bs4 import BeautifulSoup
# 先印出日期、期號
url = ~ # 網址
html = ~ # 以 requests.get 取得網址的 html
sp = ~ # 以 BeautifulSoup 解析
datas = ~ # 找到 class_="contents_box02" 這個區塊的資料放入datas
print(~) # 再從datas中找到 class_="font_black15" 的文字
# 印出開出順序
nums = datas.find_all(~)
print('開出順序:', end='')
for i in range(0, 6):
print(nums[i].text, end=',')
# 印出大小順序
print('\n大小順序:', end='')
~
~
print("\n第二區: " + datas.find(~)
```
<br />
## 二、Web API
+ [API(Application Programming Interface)是什麼?](https://tw.alphacamp.co/blog/api-introduction-understand-web-api-http-json)
+ 常用的 Web API 資料交換格式為 JSON(python Object Notation)或 XML( Extensible Markup Language)
### 1. JSON
+ [JSON為一種輕量級資料交換格式,其內容由屬性和值所組成。](https://zh.wikipedia.org/wiki/JSON)
+ [JSON 資料格式](https://www.fooish.com/json/data-format.html)
+ [JSON Viewer](https://codebeautify.org/jsonviewer):解析Json資料
+ json.loads():將 JSON 格式文字轉為資料物件。
+ json.dumps():將資料物件轉為 JSON 格式文字。
:::info
EX_02:JSON 練習
:::
```python=
# 1.載入json模組
import json
jsondata = """{
"id": 910501,
"Name": "小明",
"Email": "s910501@student.cysh.cy.edu.tw",
"contents": [
{
"subject":"Chinese",
"score":70
},
{
"subject":"English",
"score":80
},
{
"subject":"Math",
"score":90
}
]
}
""" # """表示多行字串
# 2.載入json資料,檢查各層資料型態
datas = ~ # 載入json資料(jsondata)資料至datas
print(type(jsondata))
print(type(datas))
print(type(datas['id']))
~ # 印出姓名的資料型態
~ # 印出contents的資料型態
# 3.印出學生id、姓名、各科成績
print(datas['id'], ~ ) # id 姓名
for i in range(len(datas['contents'])):
print(datas['contents'][i]['subject'], ~ ) # 科目 成績
```
:::info
EX_03:至[臺北市資料大平臺](https://data.taipei/#/),輸入「youbike」搜尋,找到「[YouBike2.0臺北市公共自行車即時資訊](https://data.taipei/#/dataset/detail?id=c6bc8aed-557d-41d5-bfb1-8da24f78f2fb)」,查詢臺大附近 YouBike 的數量、空位數量。
:::
```c=
import requests
import json
name = input('請輸入站名:')
url = 'https://tcgbusfs.blob.core.windows.net/dotapp/youbike/v2/youbike_immediate.json'
raw = requests.get(url)
datas = json.loads(raw.text)
for item in datas:
if item['sna'].find(name) >= 0: # 如果>=0,表示item['sna']中有name字串,找不到回傳-1
print(item['sna'],'\t',item['sbi'],'\t',item['bemp'])
```
:::info
EX_04(作業):參考 EX_02、EX_03 的程式碼,列出[萌典](https://www.moedict.tw/)-國語字典查到「那」的6個注音。
:::
```python=
import ~
import ~
word = input('請輸入要查詢的國字:')
url = 'https://www.moedict.tw/raw/' + word
raw = ~ # 以 requests.get 取得網址的資料
datas = ~ # 以 json 解析
print(datas['title'], ~ , '劃') # 印出要查詢的字和筆劃
for i in ~: # 鍵值 heteronyms 裏有全部的讀音
print('注音:', ~ ) # 印出注音
```
<br />
## 三、Pandas 應用
+ Python 的 Excel,Pandas 可以擷取表格式的資料,如 JSON, CSV, Excel, HTML ,進行資料分析處理。主要資料型態有Series(一維)、DataFrame(二維、類似表格)。
+ [pandas 官方手冊](https://pandas.pydata.org/pandas-docs/stable/index.html)
### 1. Pandas 基礎入門
+ DataFrame 二維資料
- 建立 DataFrame:可以透過 Dictionary、Array 來建立,也可以讀取外部資料(CSV、資料庫等)來建立。
- DataFrame 基本資訊
| 基本資訊方法 | 說明 |
|:--------------|:-------------|
| df.shape | 表格列數與欄數 |
| df.index | 回傳 index |
| df.columns | 回傳欄位名稱 |
| df.head(n) | 回傳前n筆資料,省略預設為5筆 |
| df.tail(n) | 回傳後n筆資料,省略預設為5筆 |
| df.describe() | 回傳各類統計資料 |
- [資料選取](https://codertw.com/%E7%A8%8B%E5%BC%8F%E8%AA%9E%E8%A8%80/462517/)
| 選取方法 | 說明 |
| -------------| --------- |
| df['欄位名'] | 選取欄的元素|
| df.loc() | 根據列、欄標籤來選取 |
| df.iloc() | 根據索引值來選取|
- 資料篩選:在中括號裡面放入篩選條件
- 資料排序
| 排序方法 | 說明 |
| ------------ | --------- |
| df.sort_index() | 依索引值排序,axis指定是列索引值或欄索引值排序|
| df.sort_values()| 依欄位值排序,ascending指定升冪或降冪排序|
- [pandas中axis參數](https://stackoverflow.com/questions/25773245/ambiguity-in-pandas-dataframe-numpy-array-axis-definition)
![](https://i.imgur.com/pqyXFHj.jpg =250x)
<br />
:::info
EX_05:上傳 [pandas_grades](https://drive.google.com/open?id=1kXda1ylxi5V8htWYQGc7BDA4m0OhfGwp) 至 Colab,練習 DataFrame 的基本操作(選取、篩選、排序)。
:::
``` python=
import pandas as pd
df = pd.read_csv('pandas_grades.csv')
# 顯示基本資訊
df.shape
df.index
df.columns
df.head(10)
df.tail()
df.describe()
# 資料選取
# 列
df.loc[0, '國文'] # 取單一儲存格的值
df.loc[5:10] # 取得index 5~10 列的資料
df.loc[5:10, ['國文','英文']] # 取得index 5~10 的資料,多欄要以串列表示,同df.loc[5:10][['國文','英文']]
df.loc[5:10, '國文':'數學']
df.iloc[5:11, 1:4] # 同上,用iloc以索引值來選取(不含最後一個數字)
df.iloc[:, -2:] # 取後所有列,後2欄
# 欄
df['姓名']
df[['國文', '數學']].head()
~ # 顯示國文、英文、數學三欄後5筆的資料
# 篩選
filter = df['姓名'] == '陳上紫'
df[filter]
filter = df['姓名'].str.contains('陳')
filter = df['數學'] >= 12
filter = (df.數學 == 15) & (df.自然 >= 12) # panda 中而且為「&」、或者為「|」
~ # 篩選出數學或自然滿級分者
df.loc[filter, ['姓名', '數學', '自然']]
# 排序
df['主科'] = df.數學*1.5 + df.英文 # 新增欄位
df
df['總級分'] = df[['國文', '英文', '數學', '自然']].sum(axis=1) # df["新欄位名稱"] = df[要加總的欄位的list].sum(axis=1)
df.sort_values(['國文']) # 預設遞增排序。沒有指定到df不會改變原始表格
df.sort_values(['主科','國文'], ascending=False) # 遞減排序
~ # 依總級分遞減排序,同分再依數學排序,取前10個
```
### 2. Pandas 資料爬蟲
+ 讀取JSON
:::info
EX_06:讀取「[臺南市停車場即時剩餘車位資訊](https://data.gov.tw/dataset/102772)」的JSON檔,列出「中西區」停車位 >= 50 的停車場。
:::
``` python=
import pandas as pd
# 讀取網路上的json檔
url = 'http://parkweb.tainan.gov.tw/api/parking.php'
df = pd.read_json(url)
filter = (df['zone'] == '中西區') & (df['car'] >= 50)
df.loc[filter, ['id','name','zone','address','car']]
```
+ 讀取CSV檔
:::info
EX_07(作業):參考 EX_05、EX_06 的程式碼,讀取「[政府資料開放平臺](https://data.gov.tw/)」裏「[空氣品質指標(AQI)](https://data.gov.tw/dataset/40448)」的CSV檔,列出「臺北市」、嘉義市」的空氣品質狀況,只需顯示「sitename、county、aqi、status」這些欄位。
:::
``` python=
import pandas as pd
# 讀取網路上的CSV檔
url='https://data.epa.gov.tw/api/v2/aqx_p_432?api_key=e8dd42e6-9b8b-43f8-991e-b3dee723a52d&limit=1000&sort=ImportDate%20desc&format=CSV'
df = ~
filter = ~
df.loc~
```
+ 讀取HTML
:::info
EX_08:至「[臺灣證卷交易所/交易資訊/個股日成交資訊](https://www.twse.com.tw/zh/page/trading/exchange/STOCK_DAY.html)」輸入股票代碼2330,查詢到台積電各日成交資訊(按「列印/HTML」取得網址),抓取後以收盤價繪成圖表。
:::
``` python=
import pandas as pd
# 讀取HTML
url = 'https://www.twse.com.tw/exchangeReport/STOCK_DAY?response=html&date=20211215&stockNo=2330'
df = pd.read_html(url) # 回傳為一個list(因為網頁上可能有多個表格)
# df = pd.read_html(url)[0]
# df = pd.read_html(url, header=1)[0] # header=1指定第1列為表頭
df
```
+ df.plot(kind=圖表種類, x=x軸資料來源, y=y軸資料來源, title=圖表標題, grid=是否顯示格線, fontsize=字形大小, figsize=(長,寬))
- kind(圖表種類):line(直線圖)、bar(直條圖)、barh(橫條圖)、pie(圖形圖)
``` python=
# 繪製直線圖
df[['日期','收盤價']].plot(kind='line', x='日期', y='收盤價', title='股票每日成交資訊:台積電(2330)',grid=True , figsize=(10,4))
```
``` python=
# 在Colab上顯示圖表的中文
# 先下載台北黑體字型
!wget -O taipei_sans_tc_beta.ttf https://drive.google.com/uc?id=1eGAsTN1HBpJAkeVM57_C7ccp7hbgSz3_&export=download
import matplotlib
import matplotlib.pyplot as plt
# 新增字體
matplotlib.font_manager.fontManager.addfont('taipei_sans_tc_beta.ttf')
# 設定中文字型及負號正確顯示
plt.rcParams['font.sans-serif'] = 'Taipei Sans TC Beta'
plt.rcParams["axes.unicode_minus"] = False
```
<br />
## 四、Deepface人臉辨識
+ Deepface 是 Facebook 所開發的深度學習面部識別系統(人臉偵測、辨識、人臉特徵分析)。
+ 人臉辨識觀念 (PPT 1~17)
+ 人臉偵測 detectFace()
``` python
# 載入模組
from deepface import DeepFace
# 模型 opencv、retinaface、mtcnn、dlib、ssd
face = DeepFace.detectFace(
img_path = 圖片路徑,
detector_backend = 模型,
enforce_detection = False
)
```
+ 人臉驗證 verify():判斷是否是同一人
``` python
# 模型:VGG-Face、Facenet、Facenet512、OpenFace、DeepFace、、ArcFace、SFace
result = DeepFace.verify(
img1_path = 圖片路徑1,
img2_path = 圖片路徑2,
model_name = 驗證模型名稱
model = 建立模型,
detector_backend = 偵測模型名稱,
distance_metric = 距離計算方式,
enforce_detection = 布林值
)
# 回傳值
{'verified': 布林值,
'distance': 圖片差異距離,
'max_threshold_to_verify': 最大差異值,
'model': 驗證模型名稱,
'similarity_metric': 距離計算方式
}
```
+ 人臉搜尋 find()
``` python
df = DeepFace.find(
img_path = 圖片路徑,
db_path = 圖片資料夾目錄路徑,
model_name = 驗證模型名稱,
model = 建立模型,
detector_backend = 偵測模型名稱,
distance_metric = 距離計算方式,
enforce_detection = 布林值
)
```
+ 人臉屬性分析 analyze()
``` python
# 屬性串列:age(年齡), gender(性別), race(種族), emotion(情緒)
result = DeepFace.analyze(
img_path = 圖片路徑,
actions = 屬性串列,
detector_backend = 偵測模型名稱,
enforce_detection = 布林值
)
```
:::info
EX_09:匯入「[復仇者聯盟](https://drive.google.com/file/d/1tJsYj8x-itTBf2e2axu25Rgr0Af6q8lC/view?usp=sharing)」角色圖片至Colab,練習 Deepface 的「人臉偵測」。
:::
``` python=
!pip install deepface
```
``` python=
from deepface import DeepFace # 匯入模組 deepface
import matplotlib.pyplot as plt
face = DeepFace.detectFace(
img_path = '洛基(湯姆希德斯頓Tom Hiddleston)_2.jpg',
enforce_detection = False
)
plt.imshow(face)
# 試試其它模型
plt.imsave('face.jpg', face) # 儲存檔案
```
:::info
EX_10:練習 Deepface 的「人臉驗證」。
:::
``` python=
result = DeepFace.verify(
img1_path = '神盾局局長(山繆傑克森Samuel L. Jackson)_1.jpg',
img2_path = '神盾局局長(山繆傑克森Samuel L. Jackson)_2.jpg',
model_name = 'VGG-Face',
enforce_detection = False
)
print(result)
if result['verified'] == True:
print("同一人")
else:
print("不同人")
```
``` python=
# 使用其他模型進行驗證
import pandas as pd
models = ["VGG-Face", "Facenet", "Facenet512", "OpenFace", "DeepFace", "ArcFace", "SFace"]
resultLst = []
for model in models:
result = DeepFace.verify(
img1_path = '神盾局局長(山繆傑克森Samuel L. Jackson)_1.jpg',
img2_path = '神盾局局長(山繆傑克森Samuel L. Jackson)_2.jpg',
model_name = model,
enforce_detection = False
)
resultLst.append(result)
print(resultLst)
df = pd.DataFrame(resultLst) # 匯入Pandas(dataframe),以表格呈現結果
df
```
:::info
EX_11:上網找一張「復仇者聯盟」成員不同的圖片,練習 Deepface 的「人臉搜尋」。
+ 先在Colab建一個資料夾(member),將所有圖片(iron_man.jpg除外)上傳至 member 內。
:::
``` python=
# models = ["VGG-Face", "Facenet", "Facenet512", "OpenFace", "DeepFace", "ArcFace", "SFace"]
df = DeepFace.find(
img_path = 'iron_man.jpg',
db_path= 'member',
model_name = 'Facenet',
enforce_detection = False
)
df
```
:::info
EX_12(作業):放一張自己的照片至「member」,利用 EX_11 和內建 Camera Capture 的程式碼,完成「神盾局人臉登入系統」。
+ 如果加入新圖片至 member, representations_facenet.pkl 要先刪除。
+ 在 Colab 中使用 WebCam:程式碼片段/篩選程式碼片段/搜尋「camera」/將「Camera Capture」程式碼加入。
+ 如果找到len(df)>0
:::
:::info
EX_13:練習 Deepface 的「人臉屬性分析」。
:::
``` python=
result = DeepFace.analyze(
img_path = '蟻人(保羅路德Paul Rudd)_1.jpg',
actions = ['age', 'gender', 'race', 'emotion'],
enforce_detection = False
)
print(result)
print('年齡 {}'.format(result[0]['age']))
print('性別 {}'.format(result[0]['gender']))
print('種族 {}'.format(result[0]['dominant_race']))
print('情緒 {}'.format(result[0]['dominant_emotion']))
```
``` python=
# 以字典,將分析結果翻譯成中文
label = {
'angry':'生氣',
'disgust':'厭惡',
'fear':'恐懼',
'happy':'開心',
'neutral':'中性',
'sad':'悲傷',
'surprise':'吃驚',
'Man':'男',
'Woman':'女',
'asian':'亞洲',
'black':'黑',
'indian':'印第安',
'latino hispanic':'拉丁美洲',
'middle eastern':'中東',
'white':'白'
}
result = DeepFace.analyze(
img_path = '蟻人(保羅路德Paul Rudd)_1.jpg',
actions = ['age', 'gender', 'race', 'emotion'],
enforce_detection = False
)
print(result)
print('年齡 {}'.format(result[0]['age']))
print('性別 {}'.format(result[0]['gender']))
print('種族 {}'.format(label[result[0]['dominant_race']]))
print('情緒 {}'.format(label[result[0]['dominant_emotion']]))
```
:::info
EX_14(作業):拍攝自己不同表情的照片(演員特訓),看看 Deepface 的「人臉屬性分析」能不能分辨出來。
+ angry(生氣)、disgust(厭惡)、fear(恐懼)、happy(開心)、neutral(中性)、sad(悲傷)、surprise(吃驚)
:::
:::info
EX_15(作業):以 [Teachable Machine](https://teachablemachine.withgoogle.com/) 訓練有沒有戴口罩的模型後匯入 Colab ,在 Colab 中使用 WebCam 偵測客人是否有戴口罩。
+ [參考網站](https://www.youtube.com/watch?v=J_vKgzcVUE0#t=54m10s)
:::
## 五、OCR
+ [keras-ocr模組](https://github.com/faustomorales/keras-ocr):效果強大OCR
+ 語法
``` python
import keras_ocr
管道變數 = keras_ocr.pipeline.Pipeline()
辨識結果串列 = 管道變數.recognize(圖片串列)
keras_ocr.tools.drawAnnotations(image=圖片, predictions=辨識結果) # 畫出辨識結果
```
+ 應用:車牌辨識
:::info
EX_16:找一張車牌,看看 keras-ocr 能不能辨識出來。
:::
``` python=
!!pip install keras-ocr
```
``` python=
import keras_ocr
pipeline = keras_ocr.pipeline.Pipeline()
images = []
images.append(keras_ocr.tools.read('/content/plate.jpg'))
prediction_groups = pipeline.recognize(images)
keras_ocr.tools.drawAnnotations(image=images[0], predictions=prediction_groups[0])
```
:::info
EX_17(作業):參考[網站範例](https://github.com/faustomorales/keras-ocr),練習辨識多張圖片裏的文字。
:::
## 六、OpenCV
+ [OpenCV(Open Source Computer Vision Library)是一個跨平台、被廣泛採用的影像處理函式庫。](https://medium.com/jimmy-wang/opencv-%E5%9F%BA%E7%A4%8E%E6%95%99%E5%AD%B8%E7%AD%86%E8%A8%98-with-python-d780f571a57a)
+ [OpenCV 教學 ( Python ) | STEAM 教育學習網](https://steam.oxxostudio.tw/category/python/ai/opencv-index.html)
+ [OpenCV練習:實作自動駕駛的第一課-尋找車道線](https://medium.com/@a4793706/%E6%89%8B%E6%8A%8A%E6%89%8B%E5%B8%B6%E4%BD%A0%E9%80%B2%E5%85%A5%E8%87%AA%E5%8B%95%E9%A7%95%E9%A7%9B%E7%9A%84%E7%AC%AC%E4%B8%80%E8%AA%B2-%E5%B0%8B%E6%89%BE%E8%BB%8A%E9%81%93%E7%B7%9A-%E4%BA%8C-python-opencv%E5%BD%B1%E5%83%8F%E8%99%95%E7%90%86-%E9%99%84%E4%B8%8A%E7%A8%8B%E5%BC%8F%E7%A2%BC%E8%AC%9B%E8%A7%A3-423744bf4839)
### 1. 安裝
+ [安裝 Python](https://www.codingspace.school/blog/2021-04-07)
+ [安裝 PyCharm](https://www.codingspace.school/blog/2021-01-22)
+ File/Settings/Project:pythoProject/Python Interpreter/+/搜尋opencv-python/Install Package
### 2. 讀入圖片(圖片格式)、影片
``` python=
import cv2
# 2.1 讀取圖片
img = cv2.imread('shiba_inu.jpg')
# img=cv2.resize(img, (150, 200)) # 改變圖片大小(指定像素)
img = cv2.resize(img, (0,0), fx=0.5, fy=0.5) # 改變圖片大小(倍數縮放)
cv2.imshow('img', img)
cv2.waitKey(0)
# 2.2 圖片格式
print(img)
print(type(img))
print(img.shape)
partImg = img[:100, :150] # 切出左上角
~ # 切出狗頭
cv2.imshow('part', partImg)
cv2.imshow('img', img)
cv2.waitKey(0)
# 2.3 讀取影片一幀
cap = cv2.VideoCapture('shiba_inu.mp4')
ret, frame = cap.read() # ret:有沒有成功取得下一幀,frame:下一幀
if ret:
cv2.imshow('video', frame)
cv2.waitKey(0)
# 2.4 連續播放影片
cap = cv2.VideoCapture('shiba_inu.mp4')
# cap = cv2.VideoCapture(0) # 由第一鏡頭取得視訊
while True:
ret, frame = cap.read() # ret:有沒有成功取得下一幀,frame:下一幀
# frame = cv2.resize(frame, (0, 0), fx=0.5, fy=0.5) # 改變圖片大小(倍數縮放)
if ret:
cv2.imshow('video', frame)
else:
break
# cv2.waitKey(10)
if cv2.waitKey(10) == ord('q'):
break
```
### 3. 圖片預處理
+ [色彩轉換(cvtColor)](https://steam.oxxostudio.tw/category/python/ai/opencv-cvtcolor.html)
+ [高斯模糊(GaussianBlur)](https://steam.oxxostudio.tw/category/python/ai/opencv-blur.html#a2):每個像素點為原圖上對應的像素點與周圍像素點的加權和。
+ [邊緣偵測(Canny)](https://medium.com/@bob800530/opencv-%E5%AF%A6%E4%BD%9C%E9%82%8A%E7%B7%A3%E5%81%B5%E6%B8%AC-canny%E6%BC%94%E7%AE%97%E6%B3%95-d6e0b92c0aa3):先計算梯度值,把值高於 high_threshold 的像素留下,低於 low_threshold 的像素刪除。值在兩者之間的像素,如果和 high_threshold 像素相連則保留,不相連則刪除。
+ [形態學(Morphology)-膨脹(Dilation)、侵蝕(Erosion)、關閉(Closing)、開啟(Opening)](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) [(2)](https://hackmd.io/@cws0701/B1AxRjijq)
+ [圖片素材](https://github.com/qwp8510/Self-driving_Finding_lane/blob/master/img_Finding_lane/lane.png)
``` python=
import cv2
import numpy as np
# 3.1 cvtColor 色彩轉換,將圖檔轉灰階
img = cv2.imread('lane.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 3.2 GaussianBlur 高斯模糊,進行平滑處理,抑制干擾
kernel_size = 5
blur_gray = cv2.GaussianBlur(gray ,(kernel_size, kernel_size), 0) # 核、標準差
# 3.3 Canny 邊緣偵測,low_threshold、high_threshold 為門檻值
low_threshold = 50
high_threshold = 150
canny = cv2.Canny(blur_gray, low_threshold, high_threshold)
# 3.4 dilate 膨脹
kernel = np.ones((5, 5), np.uint8) # kernel 為捲積運算時的範圍尺寸
dilated = cv2.dilate(canny, kernel)
# 3.5 關閉(Closing) - 先膨脹後侵蝕,能彌合小裂縫
closed = cv2.erode(dilated, kernel)
cv2.imshow('img', img)
cv2.imshow('gray', gray)
cv2.imshow('blur_gray', blur_gray)
cv2.imshow('canny', canny)
cv2.imshow('dilated', dilated)
cv2.imshow('closed', closed)
cv2.waitKey(0)
```
### 4. 偵測並畫出線條
+ [霍夫轉換(Hough Transform)找出直線](https://medium.com/@bob800530/hough-transform-cf6cb8337eac)
+ [Python numpy.copy函数方法的使用
](https://www.cjavapy.com/article/842/)
+ [np.dstack()、np.hstack()、np.vstack()用法](https://blog.csdn.net/qq_43965708/article/details/115673439)
+ [影像的疊加與相減](https://steam.oxxostudio.tw/category/python/ai/opencv-add.html)
+ [參考網站](https://easyfly007.github.io/blogs/selfdriving/laneline_cv.html)
``` python=
import cv2
import numpy as np
# 讀入圖片 ➞ 將其變成灰階 ➞ 高斯模糊 ➞ 邊緣偵測
img = cv2.imread('lane.png')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
blur_gray = cv2.GaussianBlur(gray,(5, 5),0)
masked_edges = cv2.Canny(blur_gray, 50, 150)
# cv2.imshow('masked_edges', masked_edges)
# cv2.waitKey(0)
# 4.1 霍夫轉換找出直線
rho = 1
theta = np.pi/180
threshold = 1
min_line_length = 10
max_line_gap = 1
lines = cv2.HoughLinesP(masked_edges, rho, theta, threshold, np.array([]),
min_line_length, max_line_gap)
# print(lines.shape)
# 4.2 準備一張同尺寸的空白圖,畫上找到的直線
line_image = np.copy(img)*0 # np.copy才不會改到原圖
for line in lines:
x1,y1,x2,y2 = line[0]
cv2.line(line_image, (x1,y1), (x2,y2), (0,0,255), 5) # BGR
# cv2.imshow('line', line_image)
# 4.3 將灰階的邊緣圖轉為彩色,使其可以與直線圖組合
color_edges = np.dstack((masked_edges, masked_edges, masked_edges)) # np.dstack() 按深度顺序堆叠陣列
# 4.4 在邊緣圖上畫上線條
combo = cv2.addWeighted(color_edges, 0.6, line_image, 1, 0)
cv2.imshow('combo', combo)
cv2.waitKey(0)
```
### 5. 色域轉換過濾顏色
+ [三分钟带你快速学习RGB、HSV和HSL颜色空间 - 知乎](https://zhuanlan.zhihu.com/p/67930839)
+ [色碼轉換器](https://www.peko-step.com/zhtw/tool/hsvrgb.html)
+ [inRange 抓取特定範圍顏色](https://steam.oxxostudio.tw/category/python/ai/opencv-inrange.html)
+ [參考網站](https://medium.com/@a4793706/%E6%89%8B%E6%8A%8A%E6%89%8B%E5%B8%B6%E4%BD%A0%E9%80%B2%E5%85%A5%E8%87%AA%E5%8B%95%E9%A7%95%E9%A7%9B%E7%9A%84%E7%AC%AC%E4%B8%80%E8%AA%B2-%E5%B0%8B%E6%89%BE%E8%BB%8A%E9%81%93%E7%B7%9A-%E4%BA%8C-python-opencv%E5%BD%B1%E5%83%8F%E8%99%95%E7%90%86-%E9%99%84%E4%B8%8A%E7%A8%8B%E5%BC%8F%E7%A2%BC%E8%AC%9B%E8%A7%A3-423744bf4839)
``` python=
import cv2
import numpy as np
# 讀入圖片 ➞ 高斯模糊
img = cv2.imread('lane.png')
blur = cv2.GaussianBlur(img,(5,5),0)
# 5.1 將圖片轉成 HSV 色彩空間後,只保留邊線顏色
hsv = cv2.cvtColor(blur,cv2.COLOR_BGR2HSV)
low_yellow = np.array([15,90,140])
high_yellow = np.array([50,160,255])
mask = cv2.inRange(hsv, low_yellow, high_yellow) # inRange 抓取特定範圍顏色
# cv2.imshow('mask', mask)
# 邊緣偵測 ➞ 霍夫轉換找出直線
canny = cv2.Canny(mask,50,150)
lines = cv2.HoughLinesP(canny, 1, np.pi/180, 50, maxLineGap=50, minLineLength=20)
# 直接在原圖上畫上找到的直線
for line in lines:
x1,y1,x2,y2 = line[0]
cv2.line(img, (x1,y1), (x2,y2), (255,0,0), 5)
cv2.imshow('img', img)
cv2.waitKey(0)
```
### 6. 影像中尋找車道線
+ [填充多邊形 fillPoly](https://shengyu7697.github.io/python-opencv-fillpoly/)
+ [bitwise_and(交集)]()
+ [影片素材](https://pysource.com/wp-content/uploads/2018/03/road_car_view.mp4)
+ [參考網站](https://medium.com/@a4793706/%E6%89%8B%E6%8A%8A%E6%89%8B%E5%B8%B6%E4%BD%A0%E9%80%B2%E5%85%A5%E8%87%AA%E5%8B%95%E9%A7%95%E9%A7%9B%E7%9A%84%E7%AC%AC%E4%B8%80%E8%AA%B2-%E5%BD%B1%E5%83%8F%E4%B8%AD%E5%B0%8B%E6%89%BE%E8%BB%8A%E9%81%93%E7%B7%9A-%E4%B8%89-python-opencv%E5%BD%B1%E5%83%8F%E8%99%95%E7%90%86-%E9%99%84%E4%B8%8A%E7%A8%8B%E5%BC%8F%E7%A2%BC%E8%AC%9B%E8%A7%A3-8069b6ed62e7)
``` python=
import cv2
import numpy as np
img = cv2.imread('lane.png')
img = cv2.resize(img, (1500, 800))
blur = cv2.GaussianBlur(img, (5, 5), 0)
hsv = cv2.cvtColor(blur, cv2.COLOR_BGR2HSV)
low_yellow = np.array([15, 90, 140])
high_yellow = np.array([50, 160, 255])
mask = cv2.inRange(hsv, low_yellow, high_yellow)
canny = cv2.Canny(mask, 50, 150)
#cv2.imshow('canny', canny)
# 6.1 ROI(Region of Interest)
ROI_mask = np.zeros(canny.shape, dtype=np.uint8) # 都填 0,為全黑
ROI_points = np.array([(0, 800), (1400, 800), (1400, 350), (0, 350)])
cv2.fillPoly(ROI_mask, [ROI_points], 255)
#cv2.imshow('ROI_mask', ROI_mask)
ROI_canny = cv2.bitwise_and(canny, ROI_mask)
#cv2.imshow('ROI_canny_', ROI_canny)
cv2.waitKey(0)
```
``` python=
# 6.2 將以上步驟套用到影片裏的每一幀圖
import cv2
import numpy as np
cap = cv2.VideoCapture('road_car_view.mp4')
while True:
ret, frame = cap.read()
if ret:
frame = cv2.resize(frame, (1500, 800))
blur = cv2.GaussianBlur(frame, (5, 5), 0)
hsv = cv2.cvtColor(blur, cv2.COLOR_BGR2HSV)
low_yellow = np.array([15, 90, 140])
high_yellow = np.array([50, 160, 255])
mask = cv2.inRange(hsv, low_yellow, high_yellow)
canny = cv2.Canny(mask, 50, 150)
ROI_mask = np.zeros(canny.shape, dtype=np.uint8)
ROI_points = np.array([(0, 800), (1400, 800), (1400, 350), (0, 350)])
cv2.fillPoly(ROI_mask, [ROI_points], 255)
ROI_canny = cv2.bitwise_and(canny, ROI_mask)
try:
lines = cv2.HoughLinesP(ROI_canny, 1, np.pi / 180, 50, maxLineGap=50, minLineLength=20)
for line in lines:
x1, y1, x2, y2 = line[0]
cv2.line(frame, (x1, y1), (x2, y2), (255, 0, 0), 3)
except:
pass
cv2.imshow('frame', frame)
if cv2.waitKey(10) == ord('q'):
break
```
## 七、Mediapipe
+ [安裝 python-3.8.0rc1-amd64](https://www.codingspace.school/blog/2021-04-07)
+ File/Settings/Project:pythoProject/Python Interpreter/+/搜尋opencv-python、mediapipe/Install Package
+ [使用 MediaPipe - AI 影像辨識教學 ( Python ) | STEAM 教育學習網](https://steam.oxxostudio.tw/category/python/ai/ai-mediapipe.html)
### 1. 手掌偵測(Hands)
+ [Mediapipe 手掌偵測 ( hands ) - AI 影像辨識教學 ( Python ) | STEAM 教育學習網](https://steam.oxxostudio.tw/category/python/ai/ai-mediapipe-hand.html)
``` python=
# 1.1 手掌偵測
import cv2
import mediapipe as mp
mp_hands = mp.solutions.hands # 手部模型
mp_drawing = mp.solutions.drawing_utils # 將偵測到的點座標畫出來
# 啟用偵測手掌
hands = mp_hands.Hands() # ()裏可設定參數
cap = cv2.VideoCapture(0)
while True:
ret, img = cap.read()
if ret:
imgRGB = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 手部偵測要用 RGB
results = hands.process(imgRGB)
# print(results.multi_hand_landmarks)
if results.multi_hand_landmarks: # 有偵測到手
for hand_landmarks in results.multi_hand_landmarks: # 可以偵測到很多隻手
mp_drawing.draw_landmarks(img, hand_landmarks, mp_hands.HAND_CONNECTIONS) # 將手部節點繪製到影像中
cv2.imshow('img', img)
if cv2.waitKey(1) == ord('q'):
break
```
``` python=
# 1.2 每個關節點旁加上編號
import cv2
import mediapipe as mp
mp_hands = mp.solutions.hands # 手部模型
mp_drawing = mp.solutions.drawing_utils # 將偵測到的點座標畫出來
# 啟用偵測手掌
hands = mp_hands.Hands() # ()裏可設定參數
cap = cv2.VideoCapture(0)
while True:
ret, img = cap.read()
if ret:
imgRGB = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 手部偵測要用 RGB
imgHeight, imgWidth, _ = img.shape # 取得畫面的高和寬
results = hands.process(imgRGB)
if results.multi_hand_landmarks: # 有偵測到手
for hand_landmarks in results.multi_hand_landmarks: # 可以偵測到很多隻手
mp_drawing.draw_landmarks(img, hand_landmarks, mp_hands.HAND_CONNECTIONS) # 將手部節點繪製到影像中
# 1.2 每個關節點旁加上編號
for i, joint in enumerate(hand_landmarks.landmark):
# print(i, joint.x, joint.y) 原始座標為比例
jx = int(joint.x * imgWidth)
jy = int(joint.y * imgHeight)
# print(i, jx, jy)
cv2.putText(img, str(i), (jx-25, jy+5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,0,255), 2)
cv2.imshow('img', img)
if cv2.waitKey(1) == ord('q'):
break
```
``` python=
# 1.3 食指打方塊
import cv2
import mediapipe as mp
import numpy as np
mp_hands = mp.solutions.hands # 手部模型
mp_drawing = mp.solutions.drawing_utils # 將偵測到的點座標畫出來
# 啟用偵測手掌
hands = mp_hands.Hands() # ()裏可設定參數
cap = cv2.VideoCapture(0)
hit = True # 是否擊中,初值設為 True,會先產生第一次的位子
score = 0
while True:
ret, img = cap.read()
img = cv2.flip(img, 1) # 左右水平翻轉
if ret:
# 隨機產生方塊位子
imgHeight, imgWidth, _ = img.shape # 取得畫面的高和寬
if hit:
hit = False # 沒有擊中,則不換位子
rx = np.random.randint(50, imgWidth-100)
ry = np.random.randint(50, imgHeight-100)
imgRGB = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 手部偵測要用 RGB
results = hands.process(imgRGB)
if results.multi_hand_landmarks: # 有偵測到手
for hand_landmarks in results.multi_hand_landmarks: # 可以偵測到很多隻手
mp_drawing.draw_landmarks(img, hand_landmarks, mp_hands.HAND_CONNECTIONS) # 將手部節點繪製到影像中
# 計算食指位子,判斷方塊是否被擊中
ix = int(hand_landmarks.landmark[8].x * imgWidth)
iy = int(hand_landmarks.landmark[8].y * imgHeight)
if ix > rx and ix < rx+50 and iy > ry and iy < ry+50:
hit = True
score +=1
cv2.rectangle(img, (rx, ry), (rx + 50, ry + 50), (0, 0, 255), 5)
cv2.putText(img, f'Hit : {score}', (30, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 0, 0), 2)
cv2.imshow('img', img)
if cv2.waitKey(1) == ord('q'):
break
```
### 2. 姿勢偵測(Pose)
+ [Mediapipe 姿勢偵測 ( Pose ) - AI 影像辨識教學 ( Python ) | STEAM 教育學習網](https://steam.oxxostudio.tw/category/python/ai/ai-mediapipe-pose.html)
``` python=
import cv2
import mediapipe as mp
import numpy as np
mp_pose = mp.solutions.pose # 姿勢偵測模型
mp_drawing = mp.solutions.drawing_utils # 將偵測到的點座標畫出來
# 啟用姿勢偵測
pose = mp_pose.Pose() # ()裏可設定參數
cap = cv2.VideoCapture(0)
while True:
ret, img = cap.read()
img = cv2.flip(img, 1) # 左右水平翻轉
if ret:
imgRGB = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 將 BGR 轉成 RGB
results = pose.process(imgRGB)
if results.pose_landmarks: # 有偵測到姿勢
mp_drawing.draw_landmarks(img, results.pose_landmarks, mp_pose.POSE_CONNECTIONS) # 將節點繪製到影像中
cv2.imshow('img', img)
if cv2.waitKey(1) == ord('q'):
break
```
:::info
EX_18(作業):整合上述程式,做出一個拳擊方塊的體感遊戲。
``` python
# 上例 19 行要增加手位子的程式碼
# 計算手位子(以節點19、20代表),判斷方塊是否被擊中
rwx = int(results.pose_landmarks.landmark[19].x * imgWidth) # 右手節點19 x 座標
```
:::