thonny->tools->open system shell
pip install ultralytics
下載 yolov8的資料夾
python.exe -m pip install --upgrade pip
1:220 TT馬達
用thonny開啟 產生訓練資料集.py
直接執行
飲水思源首頁 ->長得像原始碼的積木 -> Arduino-based積木
開啟AI Pythin積木
序列埠控制器 ->選積木
複製程式碼到thonny
import serial
import time
#開啟COM埠並設定傳輸速率 9600 bits/sec
serialPort=serial.Serial(port ='COM3',baudrate=9600)
while True:
#如果序列埠有待讀取之資料,讀取序列埠傳來之資料解碼後並印出
if (serialPort.in_waiting > 0):
line=serialPort.readline().decode('utf-8')
print(line,end="")
執行,確認序列埠列印ok
範例一,鍵盤左右鍵控制SERVO
pip install keyboard
import serial
import time
import keyboard
serialPort=serial.Serial(port ='COM3',baudrate=9600)
# 等待2秒讓serialPort先連線
time.sleep(2)
while True:
if keyboard.is_pressed("left"):
serialPort.write('a'.encode('utf-8'))
time.sleep(0.5)
if keyboard.is_pressed("right"):
serialPort.write('b'.encode('utf-8'))
time.sleep(0.5)
#include <Servo.h>
//宣告伺服馬達物件
Servo servo;
//序列埠(接收)輸入範例
void setup(){
Serial.begin(9600);
servo.attach(7);
}
void loop(){
if(Serial.available()){
char c=Serial.read();
if(c=='a'){
servo.write(90);
}
if(c=='b'){
servo.write(0);
}
}
}
用thonny開啟產生訓練資料集.py
執行後,會生成200張,並產生dataset資料夾
yoloFinetuneDemo-main\yoloFinetuneDemo-main\dataset\labels 裡面有文件檔,每個檔案都有三筆資料,第一個數字是類別,接下來前面兩個是左上角座標,最後兩個是右下角目標
資料產生多,當然會比較精準,但是過多也不好
現在目前生圖是640*640,如果電腦跑太慢,可以改成240*240
接下來訓練資料
開啟 2.yolo訓練(finetune).py,執行訓練
程式碼要改 cpu,預設是gpu,
imgsz要改640,因為之前產生的訓練圖形尺寸是640
from ultralytics import YOLO
# 載入預訓練模型(yolov8n.pt)
model = YOLO("yolov8n.pt")
# 開始訓練
model.train(
data="dataset/data.yaml", # 指定資料描述檔
epochs=100, # 訓練輪數,可自行調整
imgsz=640, # 輸入圖片尺寸(與合成圖一致)
batch=4, # 每批次圖片數量,若用CPU訓練,可設小一點
name="yolov8n_finetune", # 儲存目錄名稱(在 runs/detect/yolov8n_finetune 下)
workers=0, # CPU 訓練建議設 0,避免 dataloader 卡住
device="cpu" # 強制使用 CPU,有輝達顯卡可以用 gpu
)
# 轉存最終權重為 yolov8n_finetune.pt
model_path = "runs/detect/yolov8n_finetune/weights/best.pt"
model.save("yolov8n_finetune.pt")
print(f"✅ 模型已儲存為 yolov8n_finetune.pt")
開啟第三個檔案 3.偵測指定圖檔.py
會跳出視窗,以這個圖片來說,會偵測到0.94
thonny開啟 4.使用webCam偵測.py
可以指定攝影機(程式碼可以改)
會去偵測指定圖檔(可以利用手機拍照)
其中results = model(frame, verbose=False, conf=0.6) 代表conf 超過0.6才會顯示標註
鏡頭移動到中間範圍,而且停止一段時間,就代表瞄準完
第九腳位是控制擊發
輸入a正轉,輸入b逆轉,輸入c停止
#include <Servo.h>
//宣告伺服馬達物件
Servo servo;
//序列埠(接收)輸入範例
void setup(){
Serial.begin(9600);
servo.attach(9);
}
void loop(){
if(Serial.available()){
char c=Serial.read();
if(c=='a'){
servo.write(90);
digitalWrite(4,HIGH);
digitalWrite(5,LOW);
}
if(c=='b'){
servo.write(0);
digitalWrite(4,LOW);
digitalWrite(5,HIGH);
}
if(c=='c'){
servo.write(0);
digitalWrite(4,LOW);
digitalWrite(5,LOW);
}
}
}
你是python與arduino的開發專案專家,也精通yolo v8的模型訓練
我需要你幫忙寫兩隻程式,
第一支程式是python程式,有webcam,使用yolov8模型來偵測物體,這個辨識物體的偵測標籤是coin,180度伺服馬達接在pin 9
視訊鏡頭偵測如果偵測到物體,偏左邊時,送出L到 Arduino的序列埠,一直到物體在鏡頭偵測的中間位置
視訊鏡頭偵測如果偵測到物體,偏右邊時,送出R到 Arduino的序列埠,一直到物體在鏡頭偵測的中間位置
如果連續兩秒coin都在中間(中間給一個容忍值得參數可以做調整),就印出擊發,coin的信心水準也提供一個參數可以做調整
第二支程式是arduino程式,180度伺服馬達接在pin 9
當接收到L時,伺服馬達逆時針轉5度,每次持續0.1秒
當接收到R時,伺服馬達順時針轉5度,每次持續0.1秒
import cv2
from ultralytics import YOLO
import serial
import time
# ==========================================
# 參數設定區
# ==========================================
# 1. 硬體設定
SERIAL_PORT = 'COM3' # 請修改為你的 Port (例如 Mac 是 '/dev/tty...')
BAUD_RATE = 9600
# 2. 模型與辨識設定
MODEL_PATH = 'yolov8n_finetune.pt' # 你的 YOLOv8 模型 (需包含 coin 標籤)
TARGET_LABEL = 'coin' # 要追蹤的標籤名稱
CONF_THRESHOLD = 0.6 # [參數] 信心水準 (0.0 ~ 1.0),低於此值不處理
# 3. 追蹤與擊發邏輯
CENTER_TOLERANCE = 50 # [參數] 中心容忍值 (像素),越大越容易判定為「中間」
TIME_TO_FIRE = 2.0 # [參數] 連續在中間幾秒後判定為擊發
# ==========================================
# --- 連接 Arduino ---
try:
ser = serial.Serial(SERIAL_PORT, BAUD_RATE, timeout=1)
time.sleep(2) # 等待 Arduino 重啟
print(f"Arduino 連接成功: {SERIAL_PORT}")
except Exception as e:
print(f"警告: 無法連接 Arduino ({e}),系統將僅執行視覺預覽")
ser = None
# --- 載入模型 ---
print(f"正在載入模型: {MODEL_PATH} ...")
try:
model = YOLO(MODEL_PATH)
except:
print("找不到模型檔案,請確認路徑。目前將使用 yolov8n.pt 測試 (可能不包含 coin)")
model = YOLO('yolov8n.pt')
# --- 開啟鏡頭 ---
cap = cv2.VideoCapture(0)
if not cap.isOpened():
print("錯誤: 無法開啟 WebCam")
exit()
# 取得畫面中心點
frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
frame_center_x = frame_width // 2
# 計時器變數
center_entry_time = None
print("系統啟動!按 'q' 結束程式。")
while True:
ret, frame = cap.read()
if not ret:
break
# 1. YOLO 推論
results = model(frame, verbose=False)
target_found = False # 是否在畫面中找到目標
is_in_center_now = False # 目標是否「正在」中間
# 2. 遍歷偵測結果
for result in results:
boxes = result.boxes
for box in boxes:
# 取得信心值
conf = float(box.conf[0])
# [過濾] 信心水準不足則跳過
if conf < CONF_THRESHOLD:
continue
# 取得標籤名稱
cls_id = int(box.cls[0])
label_name = model.names[cls_id]
# 鎖定特定標籤 (coin)
if label_name == TARGET_LABEL:
target_found = True
# 取得座標 (x1, y1, x2, y2)
x1, y1, x2, y2 = map(int, box.xyxy[0])
object_center_x = (x1 + x2) // 2
object_center_y = (y1 + y2) // 2
# 繪製方框與文字
cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2)
cv2.putText(frame, f"{label_name} {conf:.2f}", (x1, y1 - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
cv2.circle(frame, (object_center_x, object_center_y), 5, (0, 0, 255), -1)
# --- 3. 判斷邏輯 (左 / 中 / 右) ---
# 定義邊界
left_bound = frame_center_x - CENTER_TOLERANCE
right_bound = frame_center_x + CENTER_TOLERANCE
if object_center_x < left_bound:
# ---> 偏左
status_text = "Action: LEFT (Tracking)"
color = (0, 165, 255) # 橘色
if ser: ser.write(b'L')
# 偏離中間,重置計時
center_entry_time = None
elif object_center_x > right_bound:
# ---> 偏右
status_text = "Action: RIGHT (Tracking)"
color = (0, 165, 255)
if ser: ser.write(b'R')
# 偏離中間,重置計時
center_entry_time = None
else:
# ---> 在中間 (瞄準中)
is_in_center_now = True
status_text = "Action: CENTER (Locking)"
color = (0, 255, 0) # 綠色
# 啟動或檢查計時器
if center_entry_time is None:
center_entry_time = time.time()
# 計算停留時間
elapsed = time.time() - center_entry_time
# 顯示倒數
cv2.putText(frame, f"Hold: {elapsed:.1f}s", (object_center_x, y2 + 25),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 0), 2)
# [擊發判斷]
if elapsed >= TIME_TO_FIRE:
status_text = "!!! FIRE !!!"
color = (0, 0, 255) # 紅色
cv2.putText(frame, "FIRE TRIGGERED", (frame_center_x - 100, frame.shape[0]//2),
cv2.FONT_HERSHEY_SIMPLEX, 1.5, (0, 0, 255), 4)
print(">>> 擊發條件達成 (2秒鎖定) <<<")
# 如果未來要Arduino做擊發動作,可在這加 ser.write(b'F')
# 顯示狀態文字
cv2.putText(frame, status_text, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, color, 2)
# 假設畫面只有一個 coin,處理完就跳出 loop
break
if target_found:
break
# 如果這一幀沒在中間 (可能消失了,或跑掉了),重置計時器
if not is_in_center_now:
center_entry_time = None
# --- 繪製輔助線 ---
# 左界線
cv2.line(frame, (frame_center_x - CENTER_TOLERANCE, 0),
(frame_center_x - CENTER_TOLERANCE, frame.shape[0]), (255, 255, 0), 1)
# 右界線
cv2.line(frame, (frame_center_x + CENTER_TOLERANCE, 0),
(frame_center_x + CENTER_TOLERANCE, frame.shape[0]), (255, 255, 0), 1)
# 中心線
cv2.line(frame, (frame_center_x, 0), (frame_center_x, frame.shape[0]), (100, 100, 100), 1)
# 顯示畫面
cv2.imshow('YOLOv8 Coin Tracker System', frame)
# 按 'q' 退出
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# 釋放資源
cap.release()
if ser:
ser.close()
cv2.destroyAllWindows()
/*
* Arduino Servo Tracker
* Pin 9: Servo Signal
*/
#include <Servo.h>
Servo myServo;
const int servoPin = 9; // 伺服馬達腳位
int currentAngle = 90; // 初始角度 (中間)
const int stepSize = 5; // 每次移動角度
void setup() {
myServo.attach(servoPin);
myServo.write(currentAngle); // 歸零
Serial.begin(9600); // 啟動 Serial
}
void loop() {
if (Serial.available() > 0) {
char command = Serial.read();
// --- 重要優化:清空 Serial 緩衝區 ---
// 因為動作會延遲 1 秒,這期間 Python 可能送了幾十個指令
// 我們只關心最新的,所以把舊的清掉,避免馬達反應過慢
while(Serial.available() > 0) {
Serial.read();
}
// 判斷指令
if (command == 'L') {
currentAngle -= stepSize; // 逆時針
}
else if (command == 'R') {
currentAngle += stepSize; // 順時針
}
// 限制角度範圍 (保護馬達)
if (currentAngle < 0) currentAngle = 0;
if (currentAngle > 180) currentAngle = 180;
// 執行轉動
myServo.write(currentAngle);
// 依照需求:持續 1 秒
delay(200);
}
}