辨識藥品顆粒並圈出來
count.py
import cv2
import time
from datetime import datetime
import numpy as np
import sys
import os
import platform
import subprocess
from scipy.stats import norm
def open_image(path):
system = platform.system()
if system == "Windows":
os.startfile(path) # 直接呼叫預設看圖程式
elif system == "Darwin": # macOS
subprocess.run(["open", path])
else: # Linux (例如 Raspberry Pi, Ubuntu)
subprocess.run(["xdg-open", path])
def count_and_draw(in_path, out_path="output.png"):
img = cv2.imread(in_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (7,7), 0)
ret, thresh = cv2.threshold(blur, 200, 255, cv2.THRESH_BINARY) # 手動閥值 200
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 將極端面積的區域去除(極小面積、極大面積)
# 計算一顆藥錠的面積 unit_area
area_list=[]
for i,contour in enumerate(contours):
area = cv2.contourArea(contour)
area_list.append(area)
area_list_sorted=np.sort(area_list)
trim_percent=0.3
n=len(area_list_sorted)
lower_idx=int(n*trim_percent)
upper_idx=int(n*(1-trim_percent))
area_list_trimmed=area_list_sorted[lower_idx:upper_idx]
data=np.array(area_list_trimmed)
unit_area,std=norm.fit(data)
print(area_list_trimmed)
print("unit_area="+str(unit_area))
min_area = unit_area*0.6
max_area = unit_area*1.4
total1=0
total2=0
for i,contour in enumerate(contours):
area = cv2.contourArea(contour)
x, y, w, h = cv2.boundingRect(contour)
if area<min_area:
continue
elif area>max_area:
count_maybe=int(round(area/unit_area,0))
cv2.drawContours(img, [contour], -1, (0,0,255), 2)
cv2.putText(img, str(count_maybe), (int(x+w/2)-10+3, int(y+h/2)+10+3), cv2.FONT_HERSHEY_SIMPLEX, 2, (61,135,38), 5)
cv2.putText(img, str(count_maybe), (int(x+w/2)-10, int(y+h/2)+10), cv2.FONT_HERSHEY_SIMPLEX, 2, (0,0,255), 5)
total2=total2+count_maybe
else:
cv2.drawContours(img, [contour], -1, (255,0,0), 2)
total1=total1+1
cv2.putText(img, "1="+str(total1), (30,100), cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 0, 0), 4)
cv2.putText(img, "2="+str(total2), (30,150), cv2.FONT_HERSHEY_SIMPLEX, 1.5, (0, 0, 255), 4)
if total1>total2:
cv2.putText(img, "n="+str(total1+total2), (30,200), cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 0, 255), 4)
cv2.imwrite(out_path, img)
return total1, total2, out_path
else:
return count_and_draw_advanced(in_path, out_path, unit_area)
def count_and_draw_advanced(in_path, out_path, unit_area_ver1):
img = cv2.imread(in_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (7,7), 0)
ret, thresh = cv2.threshold(blur, 200, 255, cv2.THRESH_BINARY) # 手動閥值 200
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 將極端面積的區域去除(極小面積、極大面積)
# 計算一顆藥錠的面積 unit_area
area_list=[]
for i,contour in enumerate(contours):
area = cv2.contourArea(contour)
if area>(unit_area_ver1/5) and area<unit_area_ver1:
area_list.append(area)
area_list_sorted=np.sort(area_list)
trim_percent=0.3
n=len(area_list_sorted)
lower_idx=int(n*trim_percent)
upper_idx=int(n*(1-trim_percent))
area_list_trimmed=area_list_sorted[lower_idx:upper_idx]
data=np.array(area_list_trimmed)
unit_area,std=norm.fit(data)
print(area_list_trimmed)
print("unit_area="+str(unit_area))
min_area = unit_area*0.6
max_area = unit_area*1.4
total1=0
total2=0
for i,contour in enumerate(contours):
area = cv2.contourArea(contour)
x, y, w, h = cv2.boundingRect(contour)
if area<min_area:
continue
elif area>max_area:
count_maybe=int(round(area/unit_area,0))
cv2.drawContours(img, [contour], -1, (0,0,255), 2)
cv2.putText(img, str(count_maybe), (int(x+w/2)-10+3, int(y+h/2)+10+3), cv2.FONT_HERSHEY_SIMPLEX, 2, (61,135,38), 5)
cv2.putText(img, str(count_maybe), (int(x+w/2)-10, int(y+h/2)+10), cv2.FONT_HERSHEY_SIMPLEX, 2, (0,0,255), 5)
total2=total2+count_maybe
else:
cv2.drawContours(img, [contour], -1, (255,0,0), 2)
total1=total1+1
cv2.putText(img, "1="+str(total1), (30,100), cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 0, 0), 4)
cv2.putText(img, "2="+str(total2), (30,150), cv2.FONT_HERSHEY_SIMPLEX, 1.5, (0, 0, 255), 4)
if total2>=total1:
cv2.putText(img, "should be "+str(total1+total2), (30,200), cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 0, 255), 4)
else:
cv2.putText(img, "n="+str(total1+total2), (30,200), cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 0, 255), 4)
cv2.imwrite(out_path, img)
return total1, total2, out_path
def main(device_index=0, width=1920, height=1080):
cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
if not cap.isOpened():
print("無法打開攝影機,請確認 device_index 是否正確或裝置是否連接。")
return
cap.set(cv2.CAP_PROP_FRAME_WIDTH, width)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, height)
scale=0.6
while True:
ret, frame = cap.read()
if not ret:
print("讀取影格失敗,結束。")
break
resized_frame = cv2.resize(frame, (0, 0), fx=scale, fy=scale)
cv2.imshow("Camera", resized_frame)
key = cv2.waitKey(1) & 0xFF
if key == ord('q'): # 按 q 鍵離開
break
if key == ord(' '): # 空白鍵
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"capture_{timestamp}.jpg"
cv2.imwrite(filename, frame)
print(f"已儲存影像:{filename}")
in_path=filename
out_path=filename.replace('.', '_out.')
total1, total2, out_path = count_and_draw(in_path, out_path)
print(f"Total1:{total1}. Total2:{total2} Result image saved to {out_path}")
if (total1+total2)<1:
result = subprocess.run([sys.executable, "speak_chinese.py", "什麼都沒有喔!"], capture_output=True, text=True)
if total2>=total1:
open_image(out_path)
result = subprocess.run([sys.executable, "speak_chinese.py", "請看一下,藥錠應該是 "+str(total1+total2)+" 顆。"], capture_output=True, text=True)
else:
open_image(out_path)
result = subprocess.run([sys.executable, "speak_chinese.py", "太棒了,你有藥錠 "+str(total1+total2)+" 顆。"], capture_output=True, text=True)
cap.release()
cv2.destroyAllWindows()
if __name__ == "__main__":
# device_index 預設 0;如果有多個攝影機,改成 1, 2, ...
main(device_index=0, width=1920, height=1080)
speak_chinese.py
from gtts import gTTS
from pydub import AudioSegment
from pydub.playback import play
import io
import sys
# windows 7 底下
# pip install gtts
if len(sys.argv) > 1:
received_text = sys.argv[1]
tts = gTTS(text=received_text, lang='zh-tw')
fp = io.BytesIO()
tts.write_to_fp(fp)
fp.seek(0)
audio = AudioSegment.from_file(fp, format="mp3")
play(audio)
讓 raspberry pi zero 2 w 變成鍵盤(做成 service)
- [micro usb] : POWER IN,就接電源
- [micro usb] : micro usb 公 - usb a 公 (接 Nintendo Switch 或 PC)
- [mini HDMI] : 空
- [micro sd] : micro sd 卡 (已安裝 raspberry pi os 64bit)
- 請用 ssh 的方式連入 pi zero 2 w 控制
[01]
sudo nano /boot/firmware/config.txt
在最後一行,通常會是 [all] 之後,加入 dtoverlay=dwc2 儲存後,重新開機 (sudo reboot)
[02]
sudo nano /home/pi/Desktop/kb.sh
# kb.sh 內文請參考後面
sudo chmod +x /home/pi/Desktop/kb.sh
[03]
sudo nano /etc/systemd/system/kb.service
# kb.service 內文請參考後面
[04]
sudo systemctl daemon-reload
sudo systemctl enable kb.service # 開機自動執行
sudo systemctl start kb.service # 立即執行測試
sudo systemctl status kb.service # 查看狀態與錯誤
[05]
sudo python test2.py
# test2.py 內文請參考後面
====== kb.sh ======
#!/bin/bash
#modprobe libcomposite
if ! lsmod | grep -q libcomposite; then
modprobe libcomposite
fi
#mount -t configfs none /sys/kernel/config
if ! mountpoint -q /sys/kernel/config; then
mount -t configfs none /sys/kernel/config
fi
G=/sys/kernel/config/usb_gadget/pi_keyboard
mkdir -p $G
echo 0x1d6b > $G/idVendor # Linux Foundation
echo 0x0104 > $G/idProduct # Multifunction Composite Gadget
echo 0x0100 > $G/bcdDevice
echo 0x0200 > $G/bcdUSB
mkdir -p $G/strings/0x409
echo "fedcba9876543210" > $G/strings/0x409/serialnumber
echo "Raspberry Pi" > $G/strings/0x409/manufacturer
echo "Pi Zero Keyboard" > $G/strings/0x409/product
mkdir -p $G/configs/c.1/strings/0x409
echo "Config 1: HID Keyboard" > $G/configs/c.1/strings/0x409/configuration
echo 120 > $G/configs/c.1/MaxPower
mkdir -p $G/functions/hid.usb0
echo 1 > $G/functions/hid.usb0/protocol
echo 1 > $G/functions/hid.usb0/subclass
echo 8 > $G/functions/hid.usb0/report_length
echo -ne \
'\x05\x01\x09\x06\xa1\x01\x05\x07\x19\xe0\x29\xe7\x15\x00\x25\x01\x75\x01\x95\x08\x81\x02\x95\x01\x75\x08\x81\x03\x95\x05\x75\x01\x05\x08\x19\x01\x29\x05\x91\x02\x95\x01\x75\x03\x91\x03\x95\x06\x75\x08\x15\x00\x25\x65\x05\x07\x19\x00\x29\x65\x81\x00\xc0' \
> $G/functions/hid.usb0/report_desc
ln -s $G/functions/hid.usb0 $G/configs/c.1/
#ls /sys/class/udc > $G/UDC
UDC=$(ls /sys/class/udc | head -n1)
if [ -z "$UDC" ]; then
echo "No UDC found, exiting"
exit 1
fi
echo $UDC > $G/UDC
====== kb.service ======
[Unit]
Description=Run keyboard gadget script at boot
After=multi-user.target
[Service]
Type=simple
ExecStart=/home/pi/Desktop/kb.sh
WorkingDirectory=/home/pi/Desktop
User=root
Restart=on-failure
[Install]
WantedBy=multi-user.target
====== test2.py ======
import random
import time
HID_DEVICE = "/dev/hidg0"
keycodes = {
'a': 0x04, 'b': 0x05, 'c': 0x06, 'd': 0x07,
'e': 0x08, 'f': 0x09, 'g': 0x0A, 'h': 0x0B,
'i': 0x0C, 'j': 0x0D, 'k': 0x0E, 'l': 0x0F,
'm': 0x10, 'n': 0x11, 'o': 0x12, 'p': 0x13,
'q': 0x14, 'r': 0x15, 's': 0x16, 't': 0x17,
'u': 0x18, 'v': 0x19, 'w': 0x1A, 'x': 0x1B,
'y': 0x1C, 'z': 0x1D,
'ENTER': 0x28, 'enter': 0x28
}
SHIFT = 0x02 # modifier mask for Shift
def send_key(dev, key, shift=False):
report = bytearray(8)
if shift:
report[0] = SHIFT
report[2] = keycodes[key]
dev.write(report)
dev.flush()
time.sleep(0.02)
dev.write(bytearray(8)) # release
dev.flush()
with open(HID_DEVICE, "wb") as dev:
while True:
key = random.choice(list("abcdefghijklmnopqrstuvwxyz") + ["ENTER"])
shift = key.isupper() # 如果需要大寫
print(key)
send_key(dev, key.lower(), shift)
time.sleep(0.1)