# KL630 Host Stream 技術報告

## 目錄

1. [系統概覽](#1-系統概覽)
2. [硬體架構](#2-硬體架構)
3. [軟體管線架構](#3-軟體管線架構)
4. [IMX662 DOL-HDR 雙曝光 ISP](#4-imx662-dol-hdr-雙曝光-isp)
5. [程式碼結構詳解](#5-程式碼結構詳解)
6. [從零開始：新裝置完整部署流程](#6-從零開始新裝置完整部署流程)
7. [常見問題排查](#7-常見問題排查)
8. [關鍵參數速查表](#8-關鍵參數速查表)
9. [STDC 語義分割邏輯詳解](#9-stdc-語義分割邏輯詳解)
10. [下一階段：高爾夫球車警示與煞車控制](#10-下一階段高爾夫球車警示與煞車控制)

---

## 1. 系統概覽

本專案在 Kneron KL630 AI SoC 上實現一個完整的即時視覺 AI 推論管線：

```
IMX662 魚眼攝影機 (DOL-HDR 雙曝光)
        │
        ▼
   KL630 ISP / IFP
   (去馬賽克、AWB、AE、色調映射)
        │
        ▼
  VMF 影像管線 (YM12 planar YUV420)
   ├── Stream 0: 1920×1080  → H.264 → RTSP
   └── Stream 1: 724×362    → NPU 推論 (STDC 語義分割)
        │
        ▼
  KL630 NPU (STDC_0520.nef)
  語義分割：道路 / 車輛 / 行人
        │
        ▼
   結果疊加 → HDMI (VOC) 或 RTSP 輸出
```

**主要元件版本：**

| 元件 | 版本 / 規格 |
|------|------------|
| SoC | Kneron KL630 (ARMv7-A Cortex-A7) |
| SDK | VMF Vienna SDK 2.5.6 |
| 感測器 | Sony IMX662 1920×1080 DOL-HDR |
| AI 模型 | STDC (Short-Term Dense Concatenate) 語義分割，ModelId=32769 |
| 執行環境 | uClibc 1.0.34，LD_LIBRARY_PATH=/mnt/flash/vienna/lib |

---

## 2. 硬體架構

### KL630 SoC 功能方塊

```
┌──────────────────────────────────────────────────────────┐
│                        KL630                             │
│                                                          │
│  MIPI CSI-2 ──► IFP (Image Front-end Processor)         │
│                  │  Bayer RAW 處理                        │
│                  │  DOL-HDR 合成 (長短曝光 Bayer → HDR)   │
│                  ▼                                        │
│                 ISP (Image Signal Processor)              │
│                  │  AWB / AE / GTR (Global Tone Remap)   │
│                  │  輸出：YM12 (planar YUV420)            │
│                  ▼                                        │
│  ┌─────────────────────────────────────┐                 │
│  │  VMF (Vatics Media Framework)       │                 │
│  │  SSM 共享記憶體環形緩衝區             │                 │
│  │  Video Encoder (H.264/H.265/MJPEG) │                 │
│  │  VOC (Video Output Component/HDMI) │                 │
│  └─────────────────────────────────────┘                 │
│                  │                                        │
│                  ▼                                        │
│                 NPU (Neural Processing Unit)              │
│                  │  執行 .nef 模型                        │
│                  │  FifoQ 非同步推論佇列                   │
│                  ▼                                        │
│              推論結果回傳 → 主程式後處理                    │
│                                                          │
│  /mnt/flash/  ── eMMC flash，重開機資料保留               │
│  /dev/shm/    ── 揮發性 RAM shared memory                │
└──────────────────────────────────────────────────────────┘
```

### 儲存空間配置

```
/mnt/flash/vienna/
├── kp_firmware_host_stream     ← 主程式 binary
├── lib/                        ← VMF 動態函式庫
├── demo_rtsp.sh                ← RTSP 模式啟動腳本
├── demo_hdmi.sh                ← HDMI 模式啟動腳本
├── demo_rtsp_hdmi.sh           ← RTSP+HDMI 同時輸出啟動腳本
└── drivers/                    ← 硬體 kernel module
    ├── driver.sh               ← 一鍵載入所有驅動腳本
    ├── vpl_edmc_v6.3.0.2.ko    ← EDMC DMA 控制器（必須第一個載入）
    ├── vpl_dmac_v2.0.0.3.ko
    ├── vma_ifpe_v1.1.0.0.ko    ← IFP 前端處理
    ├── vma_ispe_v2.0.0.5.ko    ← ISP 影像處理
    ├── vpl_voc_v2.2.0.1.ko     ← VOC HDMI 輸出
    ├── it66121enc_v1.0.0.3.ko  ← HDMI encoder
    ├── IMX662_v1.0.0.1.ko      ← 感測器驅動
    └── ...（其他 VMA/VPL 模組）

/mnt/flash/plus/kp_firmware/kp_firmware_0/kp_firmware/bin/
├── ini/
│   ├── host_stream.ini         ← 主設定檔
│   ├── demo_rtsp.sh            ← 腳本副本（從 vienna/ 複製）
│   ├── demo_hdmi.sh
│   ├── demo_rtsp_hdmi.sh
│   └── fec_calibrate.ini / fec_conf.ini
├── nef/
│   └── STDC_0520.nef           ← NPU 模型
└── Resource/
    ├── VIC/0/imx662_1920x1080_ch0.cfg   ← 主感測器設定
    ├── VIC/1/imx662_1920x1080_ch1.cfg   ← Fusion (短曝光通道)
    ├── AWB/AutoWhiteBalance.ini
    └── ISP/0/ 及 ISP/1/
        └── pqtable_ispe_Config.cfg
```

> **重要：** `/mnt/flash/etc/` 雖然是 UBIFS 分區的一部分，但寫入的變更在斷電後不會持久（UBIFS journal 未 flush）。寫入後必須執行 `sync` 才能確保資料寫入 flash。`/mnt/flash/vienna/` 和 `/mnt/flash/plus/` 不受此限制。

---

## 3. 軟體管線架構

### 執行緒模型

```
主執行緒 (kp_firmware.c: main)
│
├── loadConfig()          讀取 host_stream.ini
├── init_video_source()   初始化 VMF_VSRC (IFP + ISP)
├── 建立 FifoQ            NPU 推論佇列
│
├── Thread A: kdp2_host_stream_image_thread
│   │  SSM_Reader → 取得 YUV 影格
│   │  app_hdr_send_inf_cb → 放入 FifoQ
│   └► 餵圖給 NPU
│
├── Thread B: kdp2_host_update_result_thread
│   └► NPU 結果回傳 → 解析 STDC 輸出 → g_atDrawInfo[]
│
├── Thread C: kdp2_host_stream_draw_box (DrawBoxEnable=1 時)
│   │  SSM_Reader → DMA 複製 YUV → 在 YUV 上畫框
│   └► SSM_Writer → 送給 H.264 Encoder
│
├── Thread D: kdp2_host_video_thread
│   └► H.264 Encoder → SRB → rtsps (RTSP server)
│
└── Thread E: kdp2_host_voc_thread (VOC=1 時)
    └► SSM_Reader → VOC (HDMI 輸出)
```

### SSM (Sync Shared Memory) 緩衝區格式

ISP 輸出的每個影格存放在 SSM 緩衝區中，格式為 **YM12 planar YUV420**：

```
SSM 緩衝區佈局 (1920×1080 為例)
┌─────────────────────────────────┐ ← buffer 起始
│  VMF_FRAME_INFO_T header        │   偏移 0，大小 = VMF_MAX_SSM_HEADER_SIZE (256 bytes)
│  包含：dwOffset[0/1/2]           │
│         dwYStride, dwYSize      │
│         dwUVSize, dwType...     │
├─────────────────────────────────┤ ← dwOffset[0] (通常 = 256，16-byte aligned)
│  Y 平面                         │   dwYSize = dwYStride × H = 1920 × 1080
│  1920 × 1080 = 2,073,600 bytes  │
│  stride = 1920 (無 padding)     │
├─────────────────────────────────┤ ← dwOffset[1] (≠ 256+YSize！需 stride 對齊)
│  Cb 平面                        │   dwUVSize = dwYStride/2 × H/2 = 960 × 540
│  960 × 540 = 518,400 bytes      │
├─────────────────────────────────┤ ← dwOffset[2]
│  Cr 平面                        │   同 Cb 大小
│  960 × 540 = 518,400 bytes      │
└─────────────────────────────────┘

重要：dwOffset[1] 由 ISP 計算含對齊 padding，
     不等於 VMF_MAX_SSM_HEADER_SIZE + dwYSize。
     所有 DMA 操作必須使用 dwOffset[0/1/2]，不可自行計算。
```

---

## 4. IMX662 DOL-HDR 雙曝光 ISP

### 4.1 為什麼 IMX662 需要特殊設定

IMX662 是 Sony 的 StarVis 2 感測器，支援 **DOL-HDR (Digital Overlap High Dynamic Range)** 模式。在此模式下，感測器在同一條 MIPI 資料流中交錯輸出兩次曝光的 Bayer 資料：

```
MIPI 資料流內容 (DOL-HDR)
┌──────────────────────────────────────────┐
│ 列 0 (短曝光 Bayer)  ← 亮部細節         │
│ 列 1 (長曝光 Bayer)  ← 暗部細節         │
│ 列 2 (短曝光 Bayer)                      │
│ 列 3 (長曝光 Bayer)                      │
│ ...                                      │
└──────────────────────────────────────────┘

IFP 接收後格式標識：VMF_FRAME_FORMAT_FUSION_BAY = 31
```

這個格式讓 ISP 能夠合成高動態範圍影像——長曝光捕捉暗部，短曝光捕捉亮部，合成後得到超過單次曝光能表現的動態範圍。

### 4.2 雙曝光模式下 AWB 的問題

AWB (Auto White Balance) 需要分析影像的色彩統計數據才能決定色溫增益。對於 DOL-HDR 感測器，ISP 的 AES (Auto Exposure Statistics) 模組必須同時處理兩個曝光通道的統計數據。

**錯誤模式（使用 NORMAL 模式）：**

```
程式碼：vsrc_initopt.eAppMode = VMF_VSRC_APP_MODE_NORMAL

→ IFP 嘗試用單通道統計模式處理雙通道 Bayer 資料
→ VSRC_AES_GetViBuffer() 持續失敗，錯誤碼 0x801C000B
→ AWB 無法取得有效統計數據
→ AWB 卡在冷啟動初始增益 (Cr 增益偏高)
→ 輸出影像持續偏粉紅色
```

**正確模式（使用 FUSION 模式）：**

```
程式碼：vsrc_initopt.eAppMode = VMF_VSRC_APP_MODE_FUSION

→ IFP 使用雙曝光統計模式 (dwStatisticsSrcType = 2)
→ AES 成功取得兩個通道的統計數據
→ AWB 正常收斂，色溫 Cr/Cb 趨近 128 (中性)
→ 輸出色彩正常
```

### 4.3 啟用 Fusion 模式的完整條件

Fusion 模式由三個層面共同決定，**三個必須同時正確**：

#### 層面一：INI 設定 (`host_stream.ini`)

```ini
[sensor]
sensor_cfg  = "./Resource/VIC/0/imx662_1920x1080_ch0.cfg"   ; 主通道（長曝光）
fusion_cfg  = "./Resource/VIC/1/imx662_1920x1080_ch1.cfg"   ; Fusion 通道（短曝光）
```

`fusion_cfg` 這一行**不可被注解或刪除**。程式碼邏輯：

```c
// kp_firmware.c → loadConfig()
tmp = iniparser_getstring(ini, "sensor:fusion_cfg", NULL);
if (tmp)
    pHostStreamInit->pszFusionConfigPath = strdup(tmp);

// kdp2_host_stream.c → init_video_source()
if (pHostStreamInit->pszFusionConfigPath) {
    tFrontConfig.dwSensorConfigCount = 2;
    tFrontConfig.apszSensorConfig[0] = pHostStreamInit->pszSensorConfigPath;
    tFrontConfig.apszSensorConfig[1] = pHostStreamInit->pszFusionConfigPath;
    vsrc_initopt.eAppMode = VMF_VSRC_APP_MODE_FUSION;   // ← 關鍵
}
```

#### 層面二：ISP AWB 統計設定 (裝置端 flash)

```
檔案：$BIN_DIR/Resource/AWB/AutoWhiteBalance.ini

必須設定：dwStatisticsSrcType = 2
          （0 = 單曝光，2 = 雙曝光 DOL-HDR）
```

此設定告訴 AWB 演算法從哪個統計來源取色彩數據。設為 0 時，即使 eAppMode=FUSION，AWB 仍然只看單通道統計，無法正確收斂。

#### 層面三：ISP 色調映射設定 (裝置端 flash)

```
檔案：$BIN_DIR/Resource/ISP/0/pqtable_ispe_Config.cfg
      $BIN_DIR/Resource/ISP/1/pqtable_ispe_Config.cfg

必須設定：bGTREnable = 1
          （GTR = Global Tone Remapping，全域色調重映射）
```

GTR 是 HDR 到 SDR 的色調壓縮演算法。DOL-HDR 合成的高動態範圍影像若不經過 GTR 壓縮，在標準顯示器上亮部會過飽和，色彩失真。

### 4.4 三個層面的關係圖

```
                    ┌──────────────────────────────┐
INI: fusion_cfg ───►│ eAppMode = FUSION             │
                    │ dwSensorConfigCount = 2        │
                    │ 提供長短曝光兩組 .cfg 給 IFP   │
                    └──────────────┬───────────────┘
                                   │
                                   ▼
                    ┌──────────────────────────────┐
Resource: AWB ─────►│ dwStatisticsSrcType = 2       │
                    │ AES 模組使用雙通道統計          │
                    │ AWB 收斂 → 色溫正確            │
                    └──────────────┬───────────────┘
                                   │
                                   ▼
                    ┌──────────────────────────────┐
Resource: ISP ─────►│ bGTREnable = 1                │
                    │ GTR 壓縮 HDR→SDR               │
                    │ 亮度/色彩還原正常              │
                    └──────────────┬───────────────┘
                                   │
                                   ▼
                         ✅ 色彩正確的 YM12 影像
```

---

## 5. 程式碼結構詳解

### 5.1 原始碼目錄

```
kl630_build/
├── compile.sh                          ← 交叉編譯腳本 (ARM armv7-a)
├── ini/
│   └── host_stream.ini                 ← 主設定檔（部署時下載至裝置）
├── src/
│   ├── host_stream/
│   │   ├── kp_firmware.c               ← main()，loadConfig()，執行緒管理
│   │   ├── kdp2_host_stream.c          ← 核心管線：影像執行緒、畫框、VOC
│   │   ├── application_init.c          ← NPU job dispatch (STDC_JOB_ID=200)
│   │   └── app_header_init.c           ← FifoQ 封包封裝
│   ├── app_flow/
│   │   └── demo_customize_inf_single_model.c  ← 推論回呼
│   ├── pre_post/
│   │   └── stdc_post_process.c         ← STDC 後處理（分割圖解析）
│   └── stdc/
│       ├── fec_api.c                   ← FEC 魚眼校正 API
│       ├── stat_shim.c                 ← stat64 shim（交叉編譯相容）
│       └── glibc_shim.c               ← glibc ABI 相容層
├── lib/                                ← 裝置端 .so 函式庫（從裝置複製）
├── tools/device/
│   ├── deploy.sh                       ← 一鍵部署腳本
│   ├── demo_rtsp.sh                    ← 裝置端 RTSP 啟動腳本
│   └── demo_hdmi.sh                    ← 裝置端 HDMI 啟動腳本
└── SDK/                                ← Kneron VMF SDK 原始碼（參考用）
```

### 5.2 kp_firmware.c — 入口點

```
main()
├── loadConfig(HOST_STREAM_CONFIG_PATH)
│   ├── 讀取 [sensor] sensor_cfg, fusion_cfg, fec_mode ...
│   ├── 讀取 [nnm] ModelPath, ModelId, JobId, InferenceStream ...
│   ├── 讀取 [streamer] StreamCount ...
│   └── 讀取 [voc] → g_dwVocPixFmt = YM12 (hardcoded)
│
├── VMF_NNM_Fifoq_Manager_Init()        ← NPU 推論佇列初始化
├── VMF_NNM_Inference_App_Init()        ← 載入 .nef 模型
│
├── kdp2_host_stream_init(pHostStreamInit)
│   ├── init_video_source()
│   │   ├── loadFECConfig()             ← 魚眼校正參數
│   │   ├── tFrontConfig.apszSensorConfig[0/1]
│   │   ├── eAppMode = FUSION / NORMAL
│   │   └── VMF_VSRC_Init() + VMF_VSRC_Start()
│   │
│   └── 啟動各執行緒
│
└── 等待 SIGINT/SIGTERM 清理退出
```

### 5.3 kdp2_host_stream.c — 核心執行緒

#### 影像採集執行緒 (`kdp2_host_stream_image_thread`)

```c
// 核心迴圈
SSM_Reader_ReturnReceiveNewestBuff(ptSsmHandle, &ssm_buf, eImageBufMode);
VMF_VSRC_SSM_GetInfo(ssm_buf.buffer, &vsrc_ssm_info);
// 取得 YUV 影格，交給 NPU FifoQ
app_hdr_send_inf_cb(buf_addr, ..., ssm_buf.buffer, &vsrc_ssm_info);
```

#### 畫框執行緒 (`kdp2_host_stream_draw_box`)

此執行緒在 DrawBoxEnable=1 時啟動，用 DMA 複製 YUV 影格後疊加邊界框：

```c
// 正確的 DMA 目標位址（使用 SSM header 中的對齊偏移）
dma_addr.pbyDstYPhysAddr  = buffer_phys + vsrc_ssm_reader_info.dwOffset[0];
dma_addr.pbyDstCbPhysAddr = buffer_phys + vsrc_ssm_reader_info.dwOffset[1];
dma_addr.pbyDstCrPhysAddr = buffer_phys + vsrc_ssm_reader_info.dwOffset[2];
// ↑ 關鍵：不可用 VMF_MAX_SSM_HEADER_SIZE + dwYSize，
//   因為 dwOffset[1] 含有 stride 對齊的 padding，數值不同。
```

### 5.4 application_init.c — NPU Job 分派

```c
switch (job_id) {
    case STDC_JOB_ID:   // = 200
        stdc_inference(job_id, ...);
        break;
    // 其他 job ID...
}
```

### 5.5 host_stream.ini — 關鍵設定說明

```ini
[sensor]
sensor_cfg  = "./Resource/VIC/0/imx662_1920x1080_ch0.cfg"
fusion_cfg  = "./Resource/VIC/1/imx662_1920x1080_ch1.cfg"  ; ← 必須！啟用 DOL-HDR
fec_mode    = 0    ; 0=直通（不做魚眼校正），其他值啟用 FEC

[nnm]
ModelPath       = "nef/SDTC_0520.nef"
ModelId         = 32800    ; STDC segmentation model id
JobId           = 212      ; STDC_JOB_ID，對應 application_init.c
InferenceStream = 1        ; 使用 stream1 (724×362) 做推論
                           ; RTSP+HDMI 同時模式必須設為 0（否則 SIGSEGV）
Fps             = 25
DrawBoxEnable   = 1        ; 必須為 1，才會啟動畫框執行緒與語義分割疊加
DrawOnResize    = 0        ; InferenceStream=0 時必須設為 1（在 resize stream 畫框）

[streamer]
StreamCount = 2            ; 開啟 stream0 (1920×1080) 和 stream1 (724×362)
                           ; HDMI 單獨模式設為 1

[stream0]
Width = 1920  Height = 1080  FPS = 25  Bitrate = 2000000

[stream1]
Width = 724   Height = 362   FPS = 25  Bitrate = 2000000

[voc]
voc_enable = 1   ; 0=關閉 HDMI，1=啟用
VocWidth = 1920  VocHeight = 1080
PixFmt = NV12    ; 注意：此值被程式碼忽略，實際固定為 YM12
```

### 5.6 三種輸出模式的 INI 差異

| 模式 | voc_enable | StreamCount | InferenceStream | DrawOnResize |
|------|-----------|-------------|----------------|--------------|
| RTSP only | 0 | 2 | 1 | 0 |
| HDMI only | 1 | 1 | 0 | 1 |
| RTSP + HDMI | 1 | 2 | **0** (必須！) | 1 |

> `voc_enable=1 + StreamCount=2 + InferenceStream=1` 會造成 **SIGSEGV** crash，因此 RTSP+HDMI 模式必須將 InferenceStream 設為 0，並用 DrawOnResize=1 讓畫框作用在 resize stream 上。

---

## 6. 從零開始：新裝置完整部署流程

### 步驟 0：前置準備 — 準備開發環境

**在 Host PC 上（Windows + Docker）：**

```bash
# 1. 確認 Docker 安裝並有 ARM 交叉編譯環境
docker run --rm arm-compiler:latest arm-linux-gnueabihf-gcc --version

# 2. 確認 lib/ 目錄有裝置端函式庫（從裝置複製或 SDK 提供）
ls kl630_build/lib/
# 應包含：libvmf.so, libmembroker.so, libmsgbroker.so,
#         libsyncringbuffer.so, libiniparser.so,
#         libvmf_nnm.so, libkutils.so, libapp_yolo.so,
#         libuClibc-1.0.34.so 等

# 3. 確認 SDK headers 路徑
ls kl630_build/SDK/sdk/vtcs_root_vienna/include/vmf/
# 應包含：video_source.h, video_encoder.h, ssm_info.h 等
```

### 步驟 1：編譯 Binary

```bash
# 在 Host PC 上，於 Docker 容器內執行
docker run --rm \
    -v "C:/Users/hipe5/Desktop/kl630_build:/workspace/kl630_build" \
    kl630-dev \
    bash /workspace/kl630_build/compile.sh

# 編譯成功後輸出：
# === SUCCESS: /workspace/kl630_build/build/kp_firmware_host_stream ===
# 並顯示 patchelf 修補後的 NEEDED 清單（應無 ld-linux-armhf.so.3）
```

**compile.sh 做了什麼：**

1. 用 `arm-linux-gnueabihf-gcc` 編譯所有 `.c` → `.o`
2. Link 成 ELF binary，rpath 指向 `$ORIGIN/lib`
3. 用 `patchelf` 將 dynamic linker 從 glibc 改為 uClibc
4. 用 `patchelf` 將 `libc.so.6` → `libc.so.0`（uClibc soname）

### 步驟 2：架設 HTTP 伺服器（Host PC）

```bash
# 在 kl630_build/build/ 目錄下架設 HTTP server
cd kl630_build/build/
cp ../ini/host_stream.ini .          # 複製最新 INI
python -m http.server 8080

# 或 Windows 版
python -m http.server 8080
```

確認裝置能連到 Host PC：
- Host PC IP：`192.168.3.1`（透過 USB 網路介面）
- Port：`8080`
- 提供檔案：`kp_firmware_host_stream`、`host_stream.ini`

### 步驟 3：連線裝置並執行一次性 ISP 設定

```bash
# SSH 連入裝置（USB 網路或序列埠）
ssh root@192.168.3.10

# 在裝置上，首次只需執行一次：
sh /tmp/deploy.sh --setup
```

或手動執行以下指令（效果相同）：

```sh
BIN_DIR=/mnt/flash/plus/kp_firmware/kp_firmware_0/kp_firmware/bin

# 修正 1：AWB 雙曝光統計模式
sed -i 's/dwStatisticsSrcType = 0/dwStatisticsSrcType = 2/' \
    $BIN_DIR/Resource/AWB/AutoWhiteBalance.ini

# 修正 2：啟用 GTR 色調映射（兩個 ISP 通道）
sed -i 's/bGTREnable = 0/bGTREnable = 1/' \
    $BIN_DIR/Resource/ISP/0/pqtable_ispe_Config.cfg
sed -i 's/bGTREnable = 0/bGTREnable = 1/' \
    $BIN_DIR/Resource/ISP/1/pqtable_ispe_Config.cfg

# 確認
grep dwStatisticsSrcType $BIN_DIR/Resource/AWB/AutoWhiteBalance.ini
grep bGTREnable $BIN_DIR/Resource/ISP/0/pqtable_ispe_Config.cfg
```

> **這個步驟只需要在新裝置上執行一次。**
> 所有變更寫入 `/mnt/flash/`（eMMC），重開機後永久保留。

### 步驟 4：部署新 Binary（每次重新編譯後執行）

```bash
# 在裝置上
wget -q http://192.168.3.1:8080/deploy.sh -O /tmp/deploy.sh
sh /tmp/deploy.sh
```

`deploy.sh` 自動執行：

```
1. killall -9 kp_firmware_host_stream
2. killall -9 rtsps
3. rm -f /dev/shm/*
4. wget kp_firmware_host_stream → /mnt/flash/vienna/kp_firmware_host_stream
5. wget host_stream.ini → $BIN_DIR/ini/host_stream.ini
6. cd $BIN_DIR && sh ./ini/demo_rtsp.sh
```

### 步驟 5：驗證執行

```bash
# 在裝置上確認程式執行
ps | grep kp_firmware
ps | grep rtsps

# 查看啟動 log
# 應看到：
# [NNM] fusion_cfg: ./Resource/VIC/1/imx662_1920x1080_ch1.cfg
# [init_video_source] Fusion Mode
# [VOC] PixFmt: YM12 (0x324D5930)

# 用 VLC 或 ffplay 播放 RTSP
# rtsp://192.168.3.10/live1.sdp
```

### 步驟 6：確認色彩正常

| 現象 | 可能原因 | 解法 |
|------|---------|------|
| 粉紅色影像 | fusion_cfg 沒設定 | 檢查 host_stream.ini 第二行有無 `fusion_cfg` |
| 粉紅色影像 | dwStatisticsSrcType = 0 | 重新執行 `deploy.sh --setup` |
| 粉紅色影像 | bGTREnable = 0 | 重新執行 `deploy.sh --setup` |
| 色彩正常但有條紋 | DMA 畫框位址錯誤 | 已修正，重新編譯部署即可 |

---

## 7. 常見問題排查

### 7.1 `VSRC_AES_GetViBuffer() failed: 801C000B`

```
問題：log 出現此錯誤且持續出現（每幀）
原因：eAppMode = NORMAL，AES 無法處理 FUSION_BAY (fmt=31) 格式
解法：確認 host_stream.ini 有 fusion_cfg 這一行且未被注解
```

### 7.2 Stack Smashing 崩潰

```
問題：*** stack smashing detected *** : Aborted
原因：stat64/fstat64 符號在 uClibc 上與 glibc 不相容
解法：已在 src/stdc/stat_shim.c 加入 shim，確認編譯時包含此檔案
```

### 7.3 程式啟動後立即退出

```
問題：VMF_VSRC_Init() 回傳 NULL
檢查清單：
1. LD_LIBRARY_PATH=/mnt/flash/vienna/lib 是否設定
2. /dev/shm/ 是否清空（執行 rm -f /dev/shm/*）
3. 舊的 kp_firmware_host_stream 是否已 kill
4. Resource/ 目錄下的設定檔是否存在
```

### 7.4 RTSP 連不上

```
問題：VLC 打開 rtsp://192.168.3.10/live1.sdp 失敗
檢查：
1. demo_rtsp.sh 中 sleep 4 後 rtsps 是否已啟動
   → ps | grep rtsps
2. stream_server_config.ini 是否存在於 $BIN_DIR
3. venc_srb_* 共享記憶體是否存在
   → ls /dev/shm/
```

### 7.5 HDMI 無輸出

```
問題：voc_enable = 1 但 HDMI 無畫面
檢查：
1. VocWidth/VocHeight 是否正確設定
2. ISP 輸出格式：固定為 YM12，程式碼已 hardcode
3. HDMI 顯示器是否支援 1920×1080
```

### 7.6 `[MemMgr][Error]: Open EDMC device fail !!`

```
問題：firmware 啟動後立即出現此錯誤並退出
原因：硬體 kernel module 尚未載入，/dev/vpl_edmc 不存在
      （EDMC = Enhanced DMA Controller，由 vpl_edmc_v6.3.0.2.ko 提供）

排查：
  strace 確認：
  open("/dev/vpl_edmc", O_RDWR) = -1 ENOENT (No such file or directory)

解法：
  cd /mnt/flash/vienna/drivers && sh driver.sh 2>/dev/null
  然後重新執行 firmware

注意：
- /lib/modules/ 中有兩個版本：Godshand.ko 和 Godshand_v2.1.0.1.ko
  系統預設載入的是 Godshand.ko，兩者皆不建立 /dev/vpl_edmc
  vpl_edmc 必須由 driver.sh 明確載入 vpl_edmc_v6.3.0.2.ko 才會建立
- driver.sh 使用相對路徑 insmod，必須從 drivers/ 目錄執行
- demo_rtsp.sh / demo_hdmi.sh / demo_rtsp_hdmi.sh 已內建自動檢查：
  [ -e /dev/vpl_edmc ] || (cd /mnt/flash/vienna/drivers && sh driver.sh 2>/dev/null && sleep 1)
```

### 7.7 Write Autostart 後重開機無效

```
問題：透過 web UI Write Autostart 後，重開機 firmware 沒有自動啟動
原因：/mnt/flash/etc/ 的寫入在斷電重開後會消失
      UBIFS journal 未 flush 到 flash

解法：
  web_serve.py 的 api_autostart_write 在寫完 rc.local 後自動執行 sync
  手動寫入時必須在斷電前執行：sync

開機自動啟動原理：
  /etc/init.d/rcS → S95done → insmod /lib/modules/Godshand.ko
                             → sh /mnt/flash/etc/rc.local（若存在）
  rc.local 內容：
    cd /mnt/flash/vienna/drivers && sh driver.sh 2>/dev/null  ← 載入硬體驅動
    sleep 2
    cd /mnt/flash/vienna
    sleep 3
    nohup sh /mnt/flash/vienna/demo_*.sh > /tmp/fw.log 2>&1 &
```

---

## 8. 關鍵參數速查表

### 每次部署需確認的 INI 設定

| 參數 | 位置 | 必要值 | 說明 |
|------|------|--------|------|
| `fusion_cfg` | `[sensor]` | `./Resource/VIC/1/...` | DOL-HDR 必須，不可注解 |
| `ModelPath` | `[nnm]` | `nef/SDTC_0520.nef` | STDC 模型路徑 |
| `ModelId` | `[nnm]` | `32800` | STDC segmentation model id |
| `JobId` | `[nnm]` | `212` | 對應 `STDC_JOB_ID` |
| `InferenceStream` | `[nnm]` | `1`（RTSP）/ `0`（HDMI/RTSP+HDMI） | 推論用 stream 索引 |
| `DrawBoxEnable` | `[nnm]` | `1` | 必須，否則語義分割疊加不會執行 |
| `DrawOnResize` | `[nnm]` | `1`（InferenceStream=0 時） | 在 resize stream 上畫框 |
| `StreamCount` | `[streamer]` | `2`（RTSP）/ `1`（HDMI only） | stream 數量 |

### 裝置端 flash 一次性設定

| 參數 | 檔案 | 必要值 | 說明 |
|------|------|--------|------|
| `dwStatisticsSrcType` | `AWB/AutoWhiteBalance.ini` | `2` | 雙曝光統計模式 |
| `bGTREnable` | `ISP/0/pqtable_ispe_Config.cfg` | `1` | 色調映射開啟 |
| `bGTREnable` | `ISP/1/pqtable_ispe_Config.cfg` | `1` | 色調映射開啟 |

### 編譯關鍵 Flags

| Flag | 說明 |
|------|------|
| `-DVATICS_PLATFORM` | VMF SDK 平台識別 |
| `-DKL630` | KL630 特定程式碼路徑 |
| `-march=armv7-a -mfpu=neon -mfloat-abi=hard` | Cortex-A7 + NEON |
| `-Wl,--dynamic-linker,/lib/ld-uClibc.so.1` | 指定 uClibc dynamic linker |
| patchelf: `libc.so.6` → `libc.so.0` | glibc soname → uClibc soname |

---

## 9. STDC 語義分割邏輯詳解

本節說明目前 `stdc_post_process.c` 和 `app_header_init.c` 實作的完整判斷邏輯，作為下一階段警示/煞車控制的輸入基礎。

### 9.1 分割類別定義

STDC 模型輸出 128×64 的像素分類圖，每個像素屬於以下 8 個類別之一：

| 索引 | 類別 | 說明 |
|------|------|------|
| 0 | `BUNKER` | 沙坑 |
| 1 | `CAR` | 車輛 |
| 2 | `GRASS` | 草地 |
| 3 | `GREENERY` | 植被（灌木、雜草等） |
| 4 | `PERSON` | 行人 |
| 5 | `POND` | 水塘 |
| 6 | `ROAD` | 道路/路徑 |
| 7 | `TREE` | 樹木 |

每個類別都計算全域佔比 `class_ratio[ci]`（該類像素數 ÷ 總像素數）。

### 9.2 感興趣區域（ROI）定義

#### 碰撞 ROI（Collision ROI）

以影像分割圖比例定義，判斷是否有危險物體進入正前方區域：

```
左邊界:  25% 寬度   右邊界:  75% 寬度
上邊界:  25% 高度   下邊界:  70% 高度

對應 1920×1080 畫面：x=480 y=270 w=960 h=486（DrawBox 視覺輸出位置）
```

#### 前進 ROI（Forward ROI）— 梯形

模擬球車前方路面的透視梯形，用於草地/樹木判斷：

```
頂邊：左 45% ─────────── 右 55%   (影像 55% 高度處)
底邊：左 30% ─────────────────── 右 70%   (影像 95% 高度處)
```

### 9.3 警告旗標與閥值

`stdc_analysis_t` 中的 boolean 旗標由以下邏輯設定：

| 旗標 | 條件 | 閥值來源 |
|------|------|---------|
| `on_grass` | 前進 ROI 內草地佔比 > 30% | `THR_GRASS_ROI = 0.30` |
| `grass_warning` | `on_grass` 持續時間 > 2 秒 | `STDC_WARNING_SECONDS = 2.0` |
| `person_warning` | 全域行人佔比 > 1% | `THR_PERSON_GLOBAL = 0.01` |
| `car_warning` | 全域車輛佔比 > 5% | `THR_CAR_GLOBAL = 0.05` |
| `greenery_warning` | 全域植被佔比 > 20% | `THR_GREENERY_GLOBAL = 0.20` |
| `bunker_warning` | 全域沙坑佔比 > 1% | `THR_BUNKER_GLOBAL = 0.01` |
| `pond_warning` | 全域水塘佔比 > 1% | `THR_POND_GLOBAL = 0.01` |
| `tree_dense` | 全域樹木佔比 > 30% | `THR_TREE_DENSE = 0.30` |
| `tree_approaching` | 前進 ROI 內樹木佔比相較前幀成長 > 5% | `STDC_TREE_GROWTH_THR = 0.05` |
| `collision_risk` | 碰撞 ROI 內任一危險類別超過各自閥值（見下表） | — |
| `is_moving` | 前進 ROI 內 luma 平均差 > 3.0 | `STDC_MOTION_THRESHOLD = 3.0` |

#### `collision_risk` 詳細條件（碰撞 ROI 內）

| 類別 | 閥值 |
|------|------|
| 行人 (`col_person_ratio`) | > 1% |
| 車輛 (`col_car_ratio`) | > 5% |
| 樹木 (`col_tree_ratio`) | > 20% |
| 沙坑 (`col_bunker_ratio`) | > 1% |
| 水塘 (`col_pond_ratio`) | > 1% |

任一條件成立即觸發 `collision_risk = 1`。

### 9.4 警告優先層級

目前的邏輯是平坦的 bitmask（無優先順序），下一階段需要整合成層級：

```
Level 2（緊急） ← collision_risk = 1
                   OR person_warning = 1

Level 1（警告） ← car_warning = 1
                   OR pond_warning = 1
                   OR bunker_warning = 1
                   OR tree_approaching = 1

Level 0（正常） ← 其餘情況
```

`grass_warning`、`greenery_warning`、`tree_dense` 在球場環境中幾乎常時觸發，**不納入煞車邏輯**，僅保留為 HUD 顯示資訊。

### 9.5 視覺疊加輸出（DrawBox，1920×1080）

```
畫面區域            內容
─────────────────────────────────────────────────────
中央大框            碰撞 ROI 輪廓（480,270 960×486）
                    collision_risk 時加畫第二層內框（492,282 936×462）
中央下框            前進 ROI 輪廓（576,594 768×432）
左側警告欄 (200×55) y=10  collision_risk
                    y=73  person_warning
                    y=136 car_warning
                    y=199 grass_warning
                    y=262 tree_dense
                    y=325 tree_approaching
右側類別欄 (40×55)  每個 class 當比例超過各自閥值時亮起
                    bunker/car/grass/greenery/person/pond/road/tree（由上到下）
```

---

## 10. 下一階段：高爾夫球車警示與煞車控制

### 10.1 目標

在現有 KL630 AI 視覺管線上疊加一套安全警示與動力介入系統，適用於高爾夫球場電動球車：

- **行人/障礙物偵測** → 即時警告
- **緊急煞車** → GPIO 訊號觸發 relay，截斷馬達動力或拉起電磁煞車
- **未來擴充** → 油門 PWM 控制（依障礙物距離梯度減速）

### 10.2 系統架構變化

```
現有管線（第一階段）
  STDC 分割 → stdc_analysis_t flags → printf 警告 + DrawBox 視覺疊加

新增（第二階段）
  stdc_analysis_t flags
        │
        ▼
  warn_level 整合（0/1/2）        ← app_header_init.c 新增
        │
   ┌────┴─────────┐
   ▼              ▼
GPIO 煞車訊號   GPIO 警告燈       ← gpio_ctrl.c 新增
/sys/class/gpio/  relay/buzzer
        │
        ▼
  馬達控制器 / 電磁煞車
```

### 10.3 需新增/修改的檔案

| 檔案 | 動作 | 說明 |
|------|------|------|
| `src/host_stream/gpio_ctrl.c` | **新增** | GPIO 初始化、`gpio_set_warning(level)` |
| `src/host_stream/gpio_ctrl.h` | **新增** | 函式宣告、pin 定義 |
| `src/host_stream/app_header_init.c` | **修改** | STDC 結果區塊加 warn_level 計算 + 呼叫 `gpio_set_warning()` |
| `src/host_stream/kp_firmware.c` | **修改** | `main()` 加入 `gpio_ctrl_init()` |
| `ini/host_stream.ini` | **修改** | 新增 `[golf_cart]` 區段，放置 GPIO pin 號與閥值 |
| `src/host_stream/kp_firmware.c` | **修改** | `loadConfig()` 讀取 `[golf_cart]` 參數 |

### 10.4 警告等級 → GPIO 輸出對應

```
warn_level 0（正常）  → GPIO_WARN=0  GPIO_BRAKE=0
warn_level 1（警告）  → GPIO_WARN=1  GPIO_BRAKE=0   （警示燈亮）
warn_level 2（緊急）  → GPIO_WARN=1  GPIO_BRAKE=1   （警示燈 + 煞車 relay 觸發）
```

觸發條件：
- Level 2：`collision_risk == 1` 或 `person_warning == 1`
- Level 1：`car_warning == 1` 或 `pond_warning == 1` 或 `bunker_warning == 1` 或 `tree_approaching == 1`

### 10.5 新增 INI 參數規劃

```ini
[golf_cart]
BrakeOnCollision = 1      ; collision_risk 觸發煞車（0=停用）
BrakeOnPerson    = 1      ; person_warning 觸發煞車（0=停用）
WarnOnCar        = 1      ; car_warning 觸發警告燈
WarnOnPond       = 1      ; pond_warning 觸發警告燈
WarnOnBunker     = 1      ; bunker_warning 觸發警告燈
GpioPinWarn      = 5      ; 警告燈/蜂鳴器 GPIO pin 編號
GpioPinBrake     = 6      ; 煞車 relay GPIO pin 編號
BrakePulseMs     = 0      ; 0=持續拉高；>0=觸發後維持 N ms 再釋放（pulse 模式）
```

### 10.6 待確認事項

| 項目 | 說明 |
|------|------|
| KL630 GPIO pin mapping | 確認板上哪些 pin 拉出，對應 `/sys/class/gpio/gpioX` 編號（需查 BSP / schematic） |
| 煞車介面 | relay DO（直接 GPIO）或需要 PWM 訊號給馬達控制器 |
| 草地閥值調整 | 球場全是草，`THR_GRASS_ROI=0.30` 會讓 `on_grass` 幾乎常時觸發；考慮提高閥值或直接排除 grass 警告出煞車邏輯 |
| 反應延遲 | 目前 STDC 推論速率約 25 fps，collision_risk 觸發到 GPIO 拉高延遲 < 80ms，是否符合需求 |
| 安全冗餘 | 視覺 AI 單點失效時的 failsafe 策略（例如：推論超時 → 強制觸發警告） |

---

*報告產生日期：2026-03-24*
*適用版本：VMF Vienna SDK 2.5.6，STDC_0520.nef，Firmware Ver. 1.2.0.1203*
