###### 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) + 字串型態:'&nbsp;&nbsp;' 或 "&nbsp;&nbsp;" 中的文字 + 容器型態: - list:有順序、可變動的資料集合 - tuple:有順序、不可變動的資料集合 - set:無順序的資料集合 - dict:鍵值對(key-value)的集合 + 資料型態轉換:int()、float()、str() ### 3. 輸入輸出 + 變數 = input(\[提示字串\]) + print(項目1\[,項目2,...\]) - 字串要加 '&nbsp;&nbsp;' 或 "&nbsp;&nbsp;" - 可用 + 將字串連接 + 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 座標 ``` :::