- Move test scripts to tests/ directory for better organization - Add improved YOLOv5 postprocessing with reference implementation - Update gitignore to exclude *.mflow files and include main.spec - Add debug capabilities and coordinate scaling improvements - Enhance multi-series support with proper validation - Add AGENTS.md documentation and example utilities 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
202 lines
6.9 KiB
Python
202 lines
6.9 KiB
Python
"""
|
||
fire_detection_inference.py
|
||
|
||
此模組提供火災檢測推論介面函式:
|
||
inference(frame, params={})
|
||
|
||
當作為主程式執行時,也可以使用命令列參數測試推論。
|
||
"""
|
||
|
||
import os
|
||
import sys
|
||
import time
|
||
import argparse
|
||
import cv2
|
||
import numpy as np
|
||
import kp
|
||
|
||
# 固定路徑設定
|
||
# SCPU_FW_PATH = r'external\res\firmware\KL520\fw_scpu.bin'
|
||
# NCPU_FW_PATH = r'external\res\firmware\KL520\fw_ncpu.bin'
|
||
# MODEL_FILE_PATH = r'src\utils\models\fire_detection_520.nef'
|
||
# 若作為測試使用,預設的圖片檔案路徑(請根據實際環境調整)
|
||
# IMAGE_FILE_PATH = r'test_images\fire4.jpeg'
|
||
|
||
|
||
def preprocess_frame(frame):
|
||
"""
|
||
將輸入的 numpy 陣列進行預處理:
|
||
1. 調整大小至 (128, 128)
|
||
2. 轉換為 BGR565 格式(KL520 常用格式)
|
||
"""
|
||
if frame is None:
|
||
raise Exception("輸入的 frame 為 None")
|
||
|
||
print("預處理步驟:")
|
||
print(f" - 原始 frame 大小: {frame.shape}")
|
||
|
||
# 調整大小
|
||
frame_resized = cv2.resize(frame, (128, 128))
|
||
print(f" - 調整後大小: {frame_resized.shape}")
|
||
|
||
# 轉換為 BGR565 格式
|
||
# 注意:cv2.cvtColor 直接轉換到 BGR565 並非 OpenCV 標準用法,但假設此方法在 kneron SDK 下有效
|
||
frame_bgr565 = cv2.cvtColor(frame_resized, cv2.COLOR_BGR2BGR565)
|
||
print(" - 轉換為 BGR565 格式")
|
||
|
||
return frame_bgr565
|
||
|
||
|
||
def postprocess(pre_output):
|
||
"""
|
||
後處理函式:將模型輸出轉換為二元分類結果(這裡假設輸出為單一數值)
|
||
"""
|
||
probability = pre_output[0] # 假設模型輸出僅一個數值
|
||
return probability
|
||
|
||
|
||
def inference(frame, params={}):
|
||
"""
|
||
推論介面函式
|
||
- frame: numpy 陣列(BGR 格式),輸入的原始影像
|
||
- params: dict,包含額外參數,例如:
|
||
'port_id': (int) 預設 0
|
||
'model': (str) 模型檔案路徑,預設 MODEL_FILE_PATH
|
||
回傳一個 dict,內容包含:
|
||
- result: "Fire" 或 "No Fire"
|
||
- probability: 推論信心分數
|
||
- inference_time_ms: 推論耗時 (毫秒)
|
||
"""
|
||
# 取得參數(若未提供則使用預設值)
|
||
port_id = params.get('usb_port_id', 0)
|
||
model_path = params.get('model')
|
||
IMAGE_FILE_PATH = params.get('file_path')
|
||
SCPU_FW_PATH = params.get('scpu_path')
|
||
NCPU_FW_PATH = params.get('ncpu_path')
|
||
|
||
print("Parameters received from main app:", params)
|
||
try:
|
||
# 1. 設備連接與初始化
|
||
print('[連接設備]')
|
||
device_group = kp.core.connect_devices(usb_port_ids=[port_id])
|
||
print(' - 成功')
|
||
|
||
print('[設置超時]')
|
||
kp.core.set_timeout(device_group=device_group, milliseconds=5000)
|
||
print(' - 成功')
|
||
|
||
print('[上傳韌體]')
|
||
kp.core.load_firmware_from_file(device_group=device_group,
|
||
scpu_fw_path=SCPU_FW_PATH,
|
||
ncpu_fw_path=NCPU_FW_PATH)
|
||
print(' - 成功')
|
||
|
||
print('[上傳模型]')
|
||
model_descriptor = kp.core.load_model_from_file(device_group=device_group,
|
||
file_path=model_path)
|
||
print(' - 成功')
|
||
|
||
# 2. 圖像預處理:從 frame 轉換到符合 KL520 格式的輸入
|
||
print('[預處理影像]')
|
||
img_processed = preprocess_frame(frame)
|
||
|
||
# 3. 建立推論描述物件
|
||
inference_input_descriptor = kp.GenericImageInferenceDescriptor(
|
||
model_id=model_descriptor.models[0].id,
|
||
inference_number=0,
|
||
input_node_image_list=[
|
||
kp.GenericInputNodeImage(
|
||
image=img_processed,
|
||
image_format=kp.ImageFormat.KP_IMAGE_FORMAT_RGB565,
|
||
resize_mode=kp.ResizeMode.KP_RESIZE_ENABLE,
|
||
padding_mode=kp.PaddingMode.KP_PADDING_CORNER,
|
||
normalize_mode=kp.NormalizeMode.KP_NORMALIZE_KNERON
|
||
)
|
||
]
|
||
)
|
||
|
||
# 4. 執行推論
|
||
print('[執行推論]')
|
||
start_time = time.time()
|
||
kp.inference.generic_image_inference_send(
|
||
device_group=device_group,
|
||
generic_inference_input_descriptor=inference_input_descriptor
|
||
)
|
||
generic_raw_result = kp.inference.generic_image_inference_receive(
|
||
device_group=device_group
|
||
)
|
||
inference_time = (time.time() - start_time) * 1000 # 毫秒
|
||
print(f' - 推論耗時: {inference_time:.2f} ms')
|
||
|
||
# 5. 處理推論結果
|
||
print('[處理結果]')
|
||
inf_node_output_list = []
|
||
for node_idx in range(generic_raw_result.header.num_output_node):
|
||
inference_float_node_output = kp.inference.generic_inference_retrieve_float_node(
|
||
node_idx=node_idx,
|
||
generic_raw_result=generic_raw_result,
|
||
channels_ordering=kp.ChannelOrdering.KP_CHANNEL_ORDERING_CHW
|
||
)
|
||
inf_node_output_list.append(inference_float_node_output.ndarray.copy())
|
||
|
||
# 整理成一維陣列並後處理
|
||
probability = postprocess(np.array(inf_node_output_list).flatten())
|
||
result_str = "Fire" if probability > 0.5 else "No Fire"
|
||
|
||
# 6. 斷開設備連接
|
||
kp.core.disconnect_devices(device_group=device_group)
|
||
print('[已斷開設備連接]')
|
||
|
||
# 回傳結果
|
||
return {
|
||
"result": result_str,
|
||
"probability": probability,
|
||
"inference_time_ms": inference_time
|
||
}
|
||
|
||
except Exception as e:
|
||
print(f"錯誤: {str(e)}")
|
||
# 嘗試斷開設備(若有連線)
|
||
try:
|
||
kp.core.disconnect_devices(device_group=device_group)
|
||
except Exception:
|
||
pass
|
||
raise
|
||
|
||
|
||
# 若作為主程式執行,支援從命令列讀取圖片檔案並測試推論
|
||
# if __name__ == '__main__':
|
||
# parser = argparse.ArgumentParser(
|
||
# description='KL520 Fire Detection Model Inference'
|
||
# )
|
||
# parser.add_argument(
|
||
# '-p', '--port_id', help='Port ID (Default: 0)', default=0, type=int
|
||
# )
|
||
# parser.add_argument(
|
||
# '-m', '--model', help='NEF model path', default=model_path, type=str
|
||
# )
|
||
# parser.add_argument(
|
||
# '-i', '--img', help='Image path', default=IMAGE_FILE_PATH, type=str
|
||
# )
|
||
# args = parser.parse_args()
|
||
|
||
# # 讀取圖片(使用 cv2 讀取)
|
||
# test_image = cv2.imread(args.img)
|
||
# if test_image is None:
|
||
# print(f"無法讀取圖片: {args.img}")
|
||
# sys.exit(1)
|
||
|
||
# # 構造參數字典
|
||
# params = {
|
||
# "port_id": args.port_id,
|
||
# "model": args.model
|
||
# }
|
||
|
||
# # 呼叫推論介面函式
|
||
# result = inference(test_image, params)
|
||
|
||
# print("\n結果摘要:")
|
||
# print(f"預測結果: {result['result']}")
|
||
# print(f"信心分數: {result['probability']:.4f}")
|
||
# print(f"推論時間: {result['inference_time_ms']:.2f} ms")
|