cluster4npu/tests/fire_detection_520.py
HuangMason320 ccd7cdd6b9 feat: Reorganize test scripts and improve YOLOv5 postprocessing
- 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>
2025-09-11 19:23:59 +08:00

202 lines
6.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
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")