# Sophon Chakcy 插件管理系统使用文档

## 系统概述

Sophon Chakcy 是一个基于 Sophon SAIL 的插件化目标检测框架，核心是动态插件管理系统，而非具体的检测算法。YOLOv8 仅作为预置的插件示例，系统支持任意检测算法的插件化扩展。

### 核心特性

- **动态插件加载**：运行时根据名称动态加载对应检测器
- **统一配置管理**：集中管理所有插件配置
- **资源虚拟化**：支持 SQLite VFS 打包模型和配置
- **模块化设计**：插件间互相独立，易于扩展和维护

## 核心架构

```text
sophon_chakcy/
├── get_detector.py      # 核心插件管理器
├── __init__.py         # 工具函数
└── build.py            # 插件打包工具
```

## 关键组件解析

### 1. 插件管理器 (get_detector.py)

```python
def get_detector(name: str, is_vfs: bool = False):
    """核心插件加载函数
    
    Args:
        name: 插件名称（在config.json中定义）
        is_vfs: 是否从VFS加载资源
        
    Returns:
        tuple: (检测器类, 配置字典)
    """
```

**工作原理：**

1. 根据插件名称查找 `config.json` 中的配置
2. 动态导入对应的 Python 模块
3. 加载插件配置文件
4. 返回检测器类和配置对象

### 2. 配置文件结构

```json
{
  "model_map": {
    "yolov8": {
      "name": "yolov8",
      "module": "YOLOv8.yolov8_detector",  # 模块导入路径
      "config": "configs/yolov8_config.json"  # 插件配置文件
    }
  }
}
```

## 插件接口开发指南

### 插件接口规范

每个插件必须提供以下接口：

#### 1. 配置类（必需）

```python
from dataclasses import dataclass
from typing import Dict, Any

@dataclass
class PluginConfig:
    """插件配置基类"""
    bmodel_path: str
    classes: list
    
    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "PluginConfig":
        """从字典创建配置对象"""
        return cls(**data)
```

#### 2. 检测器类（必需）

```python
class Detector:
    """插件检测器基类"""
    
    def __init__(self, config_dict: Dict[str, Any], is_vfs: bool = False, 
                 vfs_path: str = "./application.svfs", dev_id: int = 0,
                 conf_thresh: float = 0.25, nms_thresh: float = 0.7):
        """
        初始化检测器
        
        Args:
            config_dict: 配置字典
            is_vfs: 是否使用VFS
            vfs_path: VFS文件路径
            dev_id: 设备ID
            conf_thresh: 置信度阈值
            nms_thresh: NMS阈值
        """
        self.config = PluginConfig.from_dict(config_dict)
        # 其他初始化逻辑...
    
    def detect_single_image(self, image_path: str, output_dir: str = "./results") -> list:
        """单张图像检测接口"""
        pass
    
    def detect_images_in_directory(self, input_dir: str, output_dir: str = "./results"):
        """批量图像检测接口"""
        pass
```

### 创建新插件的完整流程

#### 步骤1：创建插件目录结构

```text
my_detector/
├── __init__.py
├── my_detector.py      # 主检测器文件
├── preprocess.py       # 预处理模块（可选）
├── postprocess.py      # 后处理模块（可选）
└── utils.py           # 工具函数（可选）
```

#### 步骤2：实现检测器类

```python
# my_detector/my_detector.py
import os
import numpy as np
from dataclasses import dataclass
from typing import List, Dict, Any

import sophon_chakcy.sail as sail
from .preprocess import Preprocessor
from .postprocess import Postprocessor

@dataclass
class MyDetectorConfig:
    """自定义检测器配置"""
    bmodel_path: str
    classes: List[str]
    input_size: tuple = (640, 640)
    mean: List[float] = None
    std: List[float] = None
    
    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "MyDetectorConfig":
        return cls(**data)

class Detector:
    """自定义检测器实现"""
    
    def __init__(self, config_dict: Dict[str, Any], is_vfs: bool = False,
                 vfs_path: str = "./application.svfs", dev_id: int = 0,
                 conf_thresh: float = 0.25, nms_thresh: float = 0.7):
        
        # 解析配置
        self.config = MyDetectorConfig.from_dict(config_dict)
        
        # 加载模型
        if is_vfs:
            from sqlite_vfs.core import SQLiteVFS
            vfs = SQLiteVFS(vfs_path)
            bmodel_bytes = vfs.read_file(self.config.bmodel_path)
            bmodel_size = vfs.get_file_info(self.config.bmodel_path)["file_size"]
            self.net = sail.Engine(bmodel_bytes, bmodel_size, dev_id, sail.IOMode.SYSO)
        else:
            self.net = sail.Engine(self.config.bmodel_path, dev_id, sail.IOMode.SYSO)
        
        # 初始化组件
        self.preprocessor = Preprocessor(self.config)
        self.postprocessor = Postprocessor(
            conf_thresh=conf_thresh,
            nms_thresh=nms_thresh,
            classes=self.config.classes
        )
        
        # 其他初始化...
        print(f"插件 '{self.__class__.__name__}' 初始化完成")
    
    def detect_single_image(self, image_path: str, output_dir: str = "./results"):
        """实现单图检测逻辑"""
        # 1. 读取图像
        # 2. 预处理
        # 3. 推理
        # 4. 后处理
        # 5. 返回结果
        pass
    
    def detect_images_in_directory(self, input_dir: str, output_dir: str = "./results"):
        """实现批量检测逻辑"""
        for img_file in os.listdir(input_dir):
            img_path = os.path.join(input_dir, img_file)
            self.detect_single_image(img_path, output_dir)
```

#### 步骤3：创建插件配置文件
```json
// configs/my_detector_config.json
{
  "bmodel_path": "models/my_model.bmodel",
  "classes": ["cat", "dog", "person"],
  "input_size": [640, 640],
  "mean": [0.485, 0.456, 0.406],
  "std": [0.229, 0.224, 0.225]
}
```

#### 步骤4：注册插件

修改 `config.json` 添加新插件：

```json
{
  "model_map": {
    "yolov8": {
      "module": "YOLOv8.yolov8_detector",
      "config": "configs/yolov8_config.json"
    },
    "my_detector": {
      "module": "my_detector.my_detector",
      "config": "configs/my_detector_config.json"
    },
    "faster_rcnn": {
      "module": "faster_rcnn.detector",
      "config": "configs/faster_rcnn_config.json"
    }
  }
}
```

#### 步骤5：测试插件

```bash
# 使用新插件
python main.py --name my_detector --input ./test.jpg
```

### 插件打包与部署

#### 1. 打包工具详解 (build.py)

构建工具执行以下任务：

1. **读取配置**：解析 `config.json` 中的所有插件配置
2. **打包模块**：将插件模块打包到 SQLite 模块库
3. **打包资源**：将模型和配置文件打包到 VFS
4. **生成部署包**：创建完整的可部署包

#### 2. 打包命令

```bash
# 基本打包
python -m sophon_chakcy.build

# 自定义输出路径
python -m sophon_chakcy.build --vfs_path my_app.svfs --mbank_path my_plugins.mbank

# 详细日志
python -m sophon_chakcy.build --verbose
```

#### 3. 部署结构

```text
部署目录/
├── main.py                    # 主程序（使用VFS和模块库）
├── application.svfs          # 虚拟文件系统
│   ├── config.json
│   ├── configs/yolov8_config.json
│   ├── configs/my_detector_config.json
│   ├── models/yolov8n.bmodel
│   └── models/my_model.bmodel
└── plugins.mbank             # Python模块库
    ├── YOLOv8
    ├── my_detector
    └── faster_rcnn
```

## YOLOv8 示例

### 配置文件

在 `./config.json` 文件中添加对应的配置

```json
{
    "file_system": {
        "type": "vfs"
    },
    "vfs": {
        "file_path": "./application.svfs"
    },
    "model_map": {
        "yolo_example": {
            "name": "yolo_example",   # 插件名称
            "module": "YOLOv8.yolov8_detector",   # Detector 类模块位置
            "config": "resources/yolov8_config.json"   # 插件配置路径
        }
    }
}
```

编写插件配置 `resources/yolov8_config.json`
```json
{
    "bmodel_path": "resources/yolov8s_int8_1b.bmodel",   # 模型路径
    "classes": [    # 检测类型
        ...
    ],
    "colors": [     # 边框颜色
        [56, 0, 255],
        ...
    ]
}
```

将模型放置在配置对应的路径下，YOLOv8 插件代码可参考 `./YOLOv8` （注意需实现配置类和 Detector 类，且 Detector 所在路径需与 config.json 中的配置保持一致）

### 测试

执行 download.sh 脚本下载测试图片

```bash
./scripts/download.sh
```

执行测试

```bash
python main.py --name yolo_example --input ./resources/datasets/test/3.jpg
```

结果

```text
[BMRT][bmcpu_setup:406] INFO:cpu_lib 'libcpuop.so' is loaded.
bmcpu init: skip cpu_user_defined
open usercpu.so, init user_cpu_init 
[BMRT][load_bmodel:1084] INFO:Loading bmodel from [resources/yolov8s_int8_1b.bmodel]. Thanks for your patience...
[BMRT][load_bmodel:1023] INFO:pre net num: 0, load net num: 1
加载模型: resources/yolov8s_int8_1b.bmodel
模型信息:
  输入形状: [1, 3, 640, 640]
  输入类型: Dtype.BM_FLOAT32
  colors数量: 81
  classes类别数: 81

处理图像: ./resources/datasets/test/3.jpg
Open /dev/jpu successfully, device index = 0, jpu fd = 33, vpp fd = 34
图像大小: 854x480
原始检测数量: 11
过滤后数量 (置信度 > 0.25): 11

绘制 11 个检测结果:
--------------------------------------------------------------------------------
检测  1: person          | 置信度: 0.9176 | 位置: [  74,  163,  209,  422] | 大小:  135x 259 | 颜色: [226, 255, 0]
检测  2: person          | 置信度: 0.9176 | 位置: [ 728,  142,  849,  472] | 大小:  121x 330 | 颜色: [226, 255, 0]
检测  3: person          | 置信度: 0.8510 | 位置: [ 342,  181,  404,  314] | 大小:   62x 133 | 颜色: [226, 255, 0]
检测  4: person          | 置信度: 0.8510 | 位置: [ 539,  152,  640,  428] | 大小:  101x 276 | 颜色: [226, 255, 0]
检测  5: skis            | 置信度: 0.8196 | 位置: [  99,  384,  221,  464] | 大小:  122x  80 | 颜色: [132, 0, 255]
检测  6: person          | 置信度: 0.8196 | 位置: [ 494,  172,  571,  379] | 大小:   77x 207 | 颜色: [226, 255, 0]
检测  7: skis            | 置信度: 0.6078 | 位置: [ 450,  403,  728,  447] | 大小:  278x  44 | 颜色: [132, 0, 255]
检测  8: skis            | 置信度: 0.4471 | 位置: [ 439,  370,  588,  403] | 大小:  149x  33 | 颜色: [132, 0, 255]
检测  9: skis            | 置信度: 0.3922 | 位置: [ 365,  311,  404,  322] | 大小:   39x  11 | 颜色: [132, 0, 255]
检测 10: car             | 置信度: 0.2941 | 位置: [   1,  215,   33,  248] | 大小:   32x  33 | 颜色: [0, 37, 255]
检测 11: skis            | 置信度: 0.2510 | 位置: [ 364,  307,  404,  322] | 大小:   40x  15 | 颜色: [132, 0, 255]
--------------------------------------------------------------------------------

检测结果已保存到: ./detection_results/detected_3.jpg
```

![detected_3.jpg](./images/detected_3.jpg)

### 打包

```bash
python -m sophon_chakcy.build
```
```
清理旧的构建目录: build
开始构建...
配置文件加载成功
VFS初始化成功: application.svfs
模块打包器初始化成功: plugins.mbank
添加文件失败 config.json: UNIQUE constraint failed: files.path
添加文件: config.json
文件大小: 0.00 MB
数据库文件: application.svfs
导出文件失败 config.json: [Errno 2] No such file or directory: ''
已保存到: config.json
已保存到: application.svfs
配置文件已添加到VFS
打包模块: YOLOv8.yolov8_detector
打包包: YOLOv8 (来自: YOLOv8/__init__.py)
打包模块: YOLOv8.yolov8_detector (来自: YOLOv8/yolov8_detector.py)
打包模块: YOLOv8.postprocess_numpy (来自: YOLOv8/postprocess_numpy.py)
添加文件失败 resources/yolov8_config.json: UNIQUE constraint failed: files.path
添加文件: resources/yolov8_config.json
文件大小: 0.01 MB
数据库文件: application.svfs
导出成功: resources/yolov8_config.json -> resources/yolov8_config.json
已保存到: resources/yolov8_config.json
已保存到: application.svfs
添加文件失败 resources/yolov8s_int8_1b.bmodel: UNIQUE constraint failed: files.path
添加文件: resources/yolov8s_int8_1b.bmodel
文件大小: 11.44 MB
数据库文件: application.svfs
导出成功: resources/yolov8s_int8_1b.bmodel -> resources/yolov8s_int8_1b.bmodel
已保存到: resources/yolov8s_int8_1b.bmodel
已保存到: application.svfs
  添加模型文件: resources/yolov8s_int8_1b.bmodel

VFS内容:
  FILE: config.json (313 bytes)
  FILE: resources/yolov8_config.json (7178 bytes)
  FILE: resources/yolov8s_int8_1b.bmodel (11993088 bytes)

复制文件到构建目录...
  复制: public/main.py
  复制: application.svfs
  复制: plugins.mbank

构建成功完成! 输出目录: build
  主程序: build/main.py
  VFS文件: build/application.svfs
  模块库: build/plugins.mbank
```

./build
```
部署目录/
├── main.py                    # 主程序（使用VFS和模块库）
├── application.svfs          # 虚拟文件系统（存储配置文件和模型）
└── plugins.mbank             # Python模块库
```

## 部署

方法一：构建 whl 并在生成环境中安装

```bash
uv build
ls dist
```

```text
(sophon-chakcy) linaro@bm1684:/data/sophon_chakcy$ uv build
Building source distribution...
Building wheel from source distribution...
Successfully built dist/sophon_chakcy-0.1.0.tar.gz
Successfully built dist/sophon_chakcy-0.1.0-py3-none-any.whl
(sophon-chakcy) linaro@bm1684:/data/sophon_chakcy$ ls dist
sophon_chakcy-0.1.0-py3-none-any.whl  sophon_chakcy-0.1.0.tar.gz
(sophon-chakcy) linaro@bm1684:/data/sophon_chakcy$ 
```

方法二：复制 src/sophon_chakcy 到项目根路径
