ZeroAI-v0.1 ZeroAI是一个由配置文件驱动 的组件式 ,基于视频流 的多进程 AI框架,支持任意数量AI算法的快速部署与整合。
高效:
基于Python多进程编写,相比于Socket通信,效率更高
基于GPU的推理,可采用Tensor RT或ONNX-GPU加速
灵活性强:
可扩展性强:
基于组件式的设计,无需改动框架结构,可以轻松实现逻辑的横向、纵向扩展
易上手:
框架预提供了丰富的组件,可以很轻松地根据需要接入自己的业务
配备贴心的教程文档,助力开发人员快速上手
Tips:所有资源和权重私聊本人获取,不公开
文档目录
一、工程目录结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 . ├── bin # main脚本,运行时内容 ├── conf # 各种配置 │ ├── algorithm # 算法配置 │ ├── cam # 视频流配置(一个视频流配置可对应多个算法) │ ├── global # 全局配置 │ ├── application-dev.yaml # 项目根配置(开发环境) │ └── application-pro.yaml # 项目根配置(生产环境) ├── lib # 各类算法 │ ├── business # 业务类算法 │ ├── detection # 目标检测算法 │ ├── face # 人脸识别算法 │ ├── mot # 多目标追踪算法 │ └── reid # 重识别算法 ├── log # 日志(自动生成) ├── output # 框架输出 ├── pretrained # 预训练权重 ├── res # 资源 │ ├── images # 图像资源 │ └── videos # 视频资源 ├── script # 脚本工具 ├── zero # ZeroAI框架 │ ├── core # 框架核心代码 │ └── utility # 框架工具脚本 ├── README.md # 说明文档 ├── setup.py # 自定义包安装脚本 └── requirements.txt # 项目依赖文件
二、安装 方式一:虚拟环境安装 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 conda create -n zeroai python=3.9 -y conda activate zeroai conda install pytorch==1.12.1 torchvision==0.13.1 torchaudio==0.12.1 cudatoolkit=11.3 -c pytorch git clone https://github.com/congtoudada/ZeroAI.git cd ZeroAIpip install -r requirements.txt python installer.py python bin/main.py
Tips:
方式二:Docker安装 安装VcXsrv:使得容器能够在Windows上显示图形(若不安装需手动关闭配置文件中可视化选项,通常在算法的root.yaml配置内,将stream_draw_vis_enalbe
设为False)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 cd ZeroAI/docker build -t zeroai:latest . mkdir pretrainedmkdir resdocker run --name zero-ai --gpus all -it --rm ` -v ${PWD} \conf:/workspace/ZeroAI/conf ` -v ${PWD} \pretrained:/workspace/ZeroAI/pretrained ` -v ${PWD} \res:/workspace/ZeroAI/res ` -v ${PWD} \log :/workspace/ZeroAI/log ` -v ${PWD} \output:/workspace/ZeroAI/output ` -e DISPLAY=host.docker.internal:0 ` --device /dev/video0:/dev/video0:mwr ` --net=host --privileged zeroai:latest sudo python3 bin/main.py
Tips:目前只测试了Windows上Docker的安装运行,Linux后期再测,二者可能在图形显示上略有差异
1 2 3 4 5 6 7 8 9 10 docker run --name zero-ai --gpus all -it --rm \ -v $PWD /conf:/workspace/ZeroAI/conf \ -v $PWD /pretrained:/workspace/ZeroAI/pretrained \ -v $PWD /res:/workspace/ZeroAI/res \ -v $PWD /log:/workspace/ZeroAI/log \ -v $PWD /output:/workspace/ZeroAI/output \ -e DISPLAY=host.docker.internal:0 \ --device /dev/video0:/dev/video0:mwr \ --net=host --privileged zeroai:latest
三、效果演示
Tips:非最终效果,后期有新测试数据会替换
1.实时计数与人脸识别 安装环境后,输入bin/main.py
即可运行该默认示例

解释:
video_fps:视频帧率,实际使用时是读一帧丢一帧。正常视频流是24-30fps,因此该数值只要满足12-15fps即可
inference_fps:是自定义范围测试的fps,上图仅限计数组件本身耗时,不包括目标检测和目标追踪
灰线:人脸识别范围线,进入该区域将进行人脸识别,识别成陌生人的对象会继续识别直到消失
红蓝线:计数参考线,当人从红线上方穿过蓝线下方时会进行计数
人头包围框之上的两个数字:
具体做法
1.替换配置文件内容
位置:conf/cam/stream1.yaml
开启目标检测,多目标追踪,计数+人脸识别算法
1 2 3 4 5 6 7 8 stream: algorithm: - path: lib/detection/yolox_module/yolox/zero/component/yolox_comp.py conf: conf/algorithm/detection/yolox/yolox_head.yaml - path: lib/mot/bytetrack_module/bytetrack/zero/component/bytetrack_comp.py conf: conf/algorithm/mot/bytetrack/bytetrack_head.yaml - path: lib/business/count/extension/count_face_comp.py conf: conf/algorithm/business/count/count_face/count_face.yaml
位置:conf/algorithm/business/count/count_root.yaml
开启人脸计数算法的可视化
1 2 3 stream: draw_vis: enable: True
2.运行:python bin/main.py
Tips:按Ctrl+C
等待3s后程序会全部退出,如果存在图形界面按q
也可退出
2.单目标检测算法跑多个视频流
1.替换配置文件内容
位置:conf/application-dev.yaml
确保获取两个视频流,在这两个文件内可以设置取流地址
1 2 3 cam_list: - conf/cam/stream1.yaml - conf/cam/stream2.yaml
位置:conf/cam/stream1.yaml
和conf/cam/stream2.yaml
开启yolox目标检测
1 2 3 algorithm: - path: lib/detection/yolox_module/yolox/zero/component/yolox_comp.py conf: conf/algorithm/detection/yolox/yolox_person.yaml
确定输出端口
1 2 3 4 5 output_port: camera1 output_port: camera2
位置:conf/algorithm/detection/yolox/yolox_person.yaml
确定输入端口
1 2 3 input_port: - camera1 - camera2
位置:conf/algorithm/detection/detection_root.yaml
确保检测组件的可视化功能是开启的
1 2 3 stream: draw_vis: enable: True
2.运行:python bin/main.py

四、关键概念 1.框架图示 框架纵向流程图:展示了框架从开启到运行的各个阶段
框架横向示意图:横向展示了框架内预定义的所有组件
Tips:
每个Component通常对应一个Info,用于存储配置参数。
Based xxx,通常说明组件的输入 来自哪里;Base xxx,通常说明组件输出 到哪里。
2.Component 分类
基础组件
算法组件
服务组件
帮助组件
职责
管理其他组件
执行算法
提供全局服务
拓展或完善算法或服务组件功能
运行进程
独立进程
独立进程
独立进程
依赖其他进程
实时性
实时
实时
非实时
取决于绑定组件
举例
Launcher:启动组件,负责管理整个框架的初始化与运作 Stream:流组件,负责取流并初始化各个算法
Yolox:目标检测 Count:计数
Insight:人脸识别
FaceHelper:辅助人脸识别通信 SaveVideoHelper:存储视频
输入
无
流组件或算法组件本身
算法组件的请求(通常由帮助组件做转发)
取决于绑定的组件
输出
Launcher无输出,Steram输出视频流
根据需求决定
响应算法组件(通常由帮助组件做转发)
取决于绑定的组件
公共父类
Component
BasedStreamComponent
BaseServiceComponent
BaseHelperComponent
Tips:流组件本身既是基础组件也是算法组件,可以从application.yaml
的cam_list
项配置,也可以从stream.yaml
的algorithm
项配置,效果一样。这里为了一致性,把流组件归类为基础组件。
3.Component 生命周期函数
__init__
:构造函数。主要用于声明变量或进行简单初始化。
on_start
:初始化时调用一次。主要用于复杂的初始化工作。
on_update
:每帧调用,具体频率由配置文件指定。主要用于运行算法逻辑,返回bool。通常只有父类返回True,才会执行子类更新逻辑。
on_destroy
:销毁时调用。释放资源。
on_analysis
:每帧调用,具体频率由配置文件指定。自动打印日志。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Component (ABC ): def __init__ (self, shared_data: dict ): self.pname = "component" self.enable = True self.shared_data: dict = shared_data self.config: BaseInfo = None self.pname = f"[ {os.getpid()} :component ]" self.esc_event = None def on_start (self ): if self.shared_data is not None : self.esc_event = self.shared_data[SharedKey.EVENT_ESC] if LogKit.load_info(self.config): pass else : logger.info(f"{self.pname} 日志模块被关闭!" ) def on_update (self ) -> bool : return True def on_destroy (self ): logger.info(f"{self.pname} destroy!" ) def on_analysis (self ): pass ...
Tips:
某些组件可能有自己单独的生命周期函数
帮助类组件设计得十分灵活,主要由其他组件驱动,因此可以不遵循组件生命周期
4.Info 配置信息 通常每一个Component都对应一个Info文件,用于存放从配置文件中读取的配置信息,共享内存的Key等。使用Info文件可以有效避免手动输入字符串导致的错拼、漏拼的问题。
1 2 3 4 5 6 7 8 9 class BasedDetInfo (BasedStreamInfo ): def __init__ (self, data: dict = None ): self.input_port = [] self.detection_labels = [] super ().__init__(data) self.DETECTION_INFO = f"{SharedKey.DETECTION_INFO.name} -{self.input_port[0 ]} " self.DETECTION_TEST_SIZE = f"{SharedKey.DETECTION_TEST_SIZE.name} -{self.input_port[0 ]} "
Tips:切记,除非你明确知道后果,如果需要加载配置文件的内容,需将super().__init__(data)
放在变量声明的最后。
5.Config 配置 参考SpringBoot配置规则,编写自定义解析模块:
支持INCLUDE
关键字,通过加载其他配置文件来为自身添加所需配置;
基于YAML文件,支持配置细粒度重载;充分利用python动态语言特性,支持通过set_attrs
自动赋值。
工作流程:编写yaml配置文件 –> 编写对应的info脚本 –> 利用ConfigKit工具加载配置 –> 组件借助info脚本访问配置
Yolox yaml配置参考:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 INCLUDE: - conf/algorithm/detection/yolox/yolox_root.yaml stream: input_ports: - camera1 - camera2 detection: output_port: yolox yolox: args: expn: head path: null save_result: False ...
YoloxInfo参考:
在最后调用super().__init__(data)
来重载配置
1 2 3 4 5 6 7 8 class YoloxInfo (DetInfo ): def __init__ (self, data: dict = None ): self.yolox_vis = False self.yolox_args_expn = "" self.yolox_args_path = None self.yolox_args_save_result = False ... super ().__init__(data)
YoloxComponent参考:
其中self.config
就是根据路径从配置文件中加载的配置
1 2 3 4 5 6 7 8 class YoloxComponent (BaseDetComponent ): def __init__ (self, shared_data, config_path: str ): super ().__init__(shared_data) self.config: YoloxInfo = YoloxInfo(ConfigKit.load(config_path)) self.pname = f"[ {os.getpid()} :yolox for {self.config.yolox_args_expn} ]" self.output_dir = "" ...
6.Port 端口 端口的目的是为了能复用组件的输入和输出
例如单个目标检测模型,可以检测多个视频流并输出:
流组件(StreamComponent):从stream_url
获取视频流作为输入,并将输出的结果送至stream_output_port
基于流的组件通过input_port
获取输入,并输出给output_port
下面展示配置文件中计数算法的(依赖视频流、目标检测、目标追踪)完整端口链:
StreamComponent:取流stream_url: res/videos/renlian/renlian1.mp4
–> 视频流输出stream_output_port: camera1
BaseDetComponent:来自视频流的输出input_port: [camera1]
–> 检测模型output_port: yolox
BaseMOTComponent:来自检测模型的输出input_port: camera1-yolox
–> 追踪模型输出output_port: camera1-bytetrack
CountComponent:来自追踪模型的输出input_port: camera1-bytetrack
–> 计数业务输出output_port: None
7.共享内存(可选) 基于multiprocessing.Manager().dict()
(按照普通dict使用即可)实现进程间通信,进程安全
shared_data
:摄像头独享,用于单个视频流内算法的通信
global_shared_data
:全局共享内存,所有摄像头共享,利用camera_id
可以访问到特定摄像头的shared_data
8.共享内存Key(可选) 共享内存基于dict
,因此需要有Key,为了避免繁琐的字符串拼写,所有Key使用枚举管理
SharedKey:存放全局Key和视频流进程所需Key
FaceKey:存放人脸识别相关Key(避免SharedKey内的Key过多)
SharedKey参考 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class SharedKey (Enum ): """ 单个摄像头进程内,摄像头与算法共享数据的Key常量 """ """ 全局 """ EVENT_ESC = 0 WAIT_COUNTER = 1 CAMERAS = 2 LOCK = 3 ... """ 多目标追踪 """ MOT_INFO = 300 MOT_ID = 301 MOT_FRAME = 302 MOT_OUTPUT = 303 ...
FaceKey参考 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class FaceKey (Enum ): """ 人脸识别Key """ REQ = 0 REQ_CAM_ID = 1 REQ_PID = 2 REQ_OBJ_ID = 4 REQ_IMAGE = 5 RSP = 10 RSP_OBJ_ID = 11 RSP_PER_ID = 12 RSP_SCORE = 13
五、自定义组件 教程1:自定义算法组件 在下面的教程中,我们将参考StreamComponent
,独立开发一款简单而有趣的组件,学习完该教程后,你将对自定义组件的编写有更进一步的认识。并且可以试着编写自己的组件,来接入自己的算法或业务,而不需要多少代价。
目标:我们将自定义一款基于流的算法组件(PrintStreamComponent
),将在每帧打印日志,并将视频流显示出来,同时写入本地。
Tips:
为了避免与框架本身混淆,案例将放在samples
文件夹内,目录结构与工程目录基本一致
教程1中为了更好展示组件的工作机制,我们不会使用任何框架预定义的组件,所有继承均来自基类,因此代码量可能会较多一些,但是几乎每个函数或字段教程都配备了注释,方便阅读
1.编写配置文件 位置:samples/conf/algorithm/print_stream/
先确定算法可能需要用到的参数,通常我们会声明一个root.yaml
作为算法的根配置,作为该算法的公共配置。利用INCLUDE
关键字可以得到其他配置文件的内容。当前配置文件也可以对包含的相同字段进行重定义,自身拥有最高的优先级。
编写print_stream_root.yaml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 INCLUDE: - conf/global/log.yaml - conf/global/output.yaml stream: output_dir: output/samples save_video: enable: True draw_vis: enable: True print_stream: show_title: custom_sample show_width: 480 show_height: 360
编写print_stream.yaml
1 2 3 4 5 6 7 INCLUDE: - samples/conf/algorithm/print_stream/print_stream_root.yaml input_port: camera1 stream: output_dir: output/samples/print_stream print_stream: show_title: custom_stream_sample
Tips:
input_port依据:在conf/cam/stream1.yaml中,我们将stream_output_port
设置为camera1
,因此输入端口也要对应
2.编写Info 位置:samples/lib/business/print_stream/info/print_stream_info.py
根据步骤1中yaml配置的内容,编写info文件。遵循规则:每级YAML声明加一个_
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 from zero.core.info.base.base_info import BaseInfofrom zero.core.key.shared_key import SharedKeyclass PrintStreamInfo (BaseInfo ): def __init__ (self, data: dict = None ): self.input_port = None self.print_stream_show_title = "" self.print_stream_show_width = 0 self.print_stream_show_height = 0 super ().__init__(data) camera = self.input_port self.STREAM_FRAME_INFO = f"{SharedKey.STREAM_FRAME_INFO.name} -{camera} " self.STREAM_ORIGINAL_WIDTH = f"{SharedKey.STREAM_ORIGINAL_WIDTH.name} -{camera} " self.STREAM_ORIGINAL_HEIGHT = f"{SharedKey.STREAM_ORIGINAL_HEIGHT.name} -{camera} " self.STREAM_ORIGINAL_CHANNEL = f"{SharedKey.STREAM_ORIGINAL_CHANNEL.name} -{camera} " self.STREAM_ORIGINAL_FPS = f"{SharedKey.STREAM_ORIGINAL_FPS.name} -{camera} " self.STREAM_URL = f"{SharedKey.STREAM_URL.name} -{camera} " self.STREAM_CAMERA_ID = f"{SharedKey.STREAM_CAMERA_ID.name} -{camera} " self.STREAM_UPDATE_FPS = f"{SharedKey.STREAM_UPDATE_FPS.name} -{camera} "
Tips:
通常我们将针对特定业务解决问题的算法组件放在业务模块(algorithm/business)内,而像目标检测、多目标追踪等通用算法直接放在(algorithm/xxx)内,根据配置文件可以随时更换。
Info文件是可选的,本质是将yaml配置解析成dict,然后通过set_attrs
赋值。你可以直接使用加载出来的字典或只声明部分字段,不写或漏写是允许的,但是为了避免字符串拼写,推荐常用的字段手动声明一下。
3.编写Component 位置:samples/lib/business/print_stream/component/print_stream_comp.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 import osimport timeimport cv2import numpy as npfrom loguru import loggerfrom samples.lib.business.print_stream.info.print_stream_info import PrintStreamInfofrom zero.core.component.base.component import Componentfrom zero.core.component.helper.feature.save_video_helper_comp import SaveVideoHelperComponentfrom zero.core.key.shared_key import SharedKeyfrom zero.utility.config_kit import ConfigKitfrom zero.utility.timer_kit import TimerKitclass PrintStreamComponent (Component ): def __init__ (self, shared_data, config_path: str ): super ().__init__(shared_data) self.config: PrintStreamInfo = PrintStreamInfo(ConfigKit.load(config_path)) self.pname = f"[ {os.getpid()} :print_stream for {self.config.input_port} ]" self.frame = None self.update_timer = TimerKit() self._tic = False self.current_frame_id = 0 self.frame = None self.video_writer: SaveVideoHelperComponent = None self.stream_width = 0 self.stream_height = 0 self.stream_channel = 0 self.stream_fps = 0 self.stream_url = "" self.stream_cam_id = 0 self.update_fps = 0 def on_start (self ): """ 初始化时调用一次 :return: """ super ().on_start() self.stream_width = self.shared_data[self.config.STREAM_ORIGINAL_WIDTH] self.stream_height = self.shared_data[self.config.STREAM_ORIGINAL_HEIGHT] self.stream_channel = self.shared_data[self.config.STREAM_ORIGINAL_CHANNEL] self.stream_fps = self.shared_data[self.config.STREAM_ORIGINAL_FPS] self.stream_url = self.shared_data[self.config.STREAM_URL] self.stream_cam_id = self.shared_data[self.config.STREAM_CAMERA_ID] self.update_fps = self.shared_data[self.config.STREAM_UPDATE_FPS] if self.config.stream_save_video_enable: filename = os.path.basename(self.stream_url).split('.' )[0 ] output_dir = os.path.join(self.config.stream_output_dir, filename) os.makedirs(output_dir, exist_ok=True ) output_path = os.path.join(output_dir, f"{filename} .mp4" ) self.video_writer = SaveVideoHelperComponent(output_path, self.config.stream_save_video_width, self.config.stream_save_video_height, self.config.stream_save_video_fps) logger.info(f"{self.pname} 输出视频路径: {output_path} " ) def on_resolve_stream (self ) -> bool : """ 自定义生命周期函数,用于解析从流组件获取的信息 :return: 返回解析成功or失败 """ frame_info = self.shared_data[self.config.STREAM_FRAME_INFO] if frame_info is not None and self.current_frame_id != int (frame_info[SharedKey.STREAM_FRAME_ID]): self.current_frame_id = int (frame_info[SharedKey.STREAM_FRAME_ID]) self.frame = np.reshape(np.ascontiguousarray(np.copy(frame_info[SharedKey.STREAM_FRAME])), (self.stream_height, self.stream_width, self.stream_channel)) self.analysis(frame_info[SharedKey.STREAM_FRAME_ID]) return True else : return False def on_update (self ) -> bool : """ 每帧调用,主要用于运行算法逻辑 :return: bool。通常只有父类返回True,才会执行子类更新逻辑。 """ if super ().on_update(): if self.update_fps > 0 : time.sleep(1.0 / self.update_fps) ret = self.on_resolve_stream() if ret: if not self._tic: self.update_timer.tic() else : self.update_timer.toc() self._tic = not self._tic return True return False def on_draw_vis (self, frame, vis=False , window_name="window" , is_copy=True ): """ 可视化时调用,用于可视化内容 :param frame: 传入图像 :param vis: 是否可视化 :param window_name: 窗口名 :param is_copy: 是否拷贝图像 :return: """ if vis and frame is not None : frame = cv2.resize(frame, (self.config.print_stream_show_width, self.config.print_stream_show_height)) cv2.imshow(window_name, frame) if cv2.waitKey(1 ) & 0xFF == ord ('q' ): self.shared_data[SharedKey.EVENT_ESC].set () return frame def on_analysis (self ): logger.info(f"{self.pname} video fps: {1. / max (1e-5 , self.update_timer.average_time):.2 f} " ) def on_destroy (self ): """ 销毁时调用,用于释放资源 :return: """ if self.video_writer is not None : self.video_writer.destroy() super ().on_destroy() def start (self ): """ start操作,控制初始化 :return: """ super ().start() logger.info(f"{self.pname} 成功初始化!" ) self.shared_data[SharedKey.STREAM_WAIT_COUNTER] += 1 def update (self ): """ update操作,控制每帧更新。对于算法组件而言就是死循环,直到收到主进程的退出事件才结束 :return: """ while True : if self.enable: self.on_update() if self.config.stream_draw_vis_enable or self.config.stream_save_video_enable: if self.frame is not None : im = self.on_draw_vis(self.frame, self.config.stream_draw_vis_enable, self.config.print_stream_show_title) if self.config.stream_save_video_enable: self.save_video(im, self.video_writer) if self.esc_event.is_set(): self.destroy() return def save_video (self, frame, vid_writer: SaveVideoHelperComponent ): """ 保存视频 :param frame: :param vid_writer: :return: """ if frame is not None : vid_writer.write(frame) def create_process (shared_data, config_path: str ): countComp: PrintStreamComponent = PrintStreamComponent(shared_data, config_path) countComp.start() countComp.update()
4.接入框架 ①在自定义组件内编写create_process函数
1 2 3 4 def create_process (shared_data, config_path: str ): countComp: PrintStreamComponent = PrintStreamComponent(shared_data, config_path) countComp.start() countComp.update()
②编写setup.py脚本,安装自定义模块
位置:samples/lib/business/setup.py
(包所在目录)
1 2 3 4 5 6 7 8 9 10 11 12 13 import setuptoolssetuptools.setup( name="print_stream" , version="0.1.0" , author="cong tou" , python_requires=">=3.6" , long_description="count algorithm" , classifiers=["Programming Language :: Python :: 3" , "Operating System :: OS Independent" ], packages=setuptools.find_namespace_packages(), )
安装命令,在setup.py所在文件夹执行
③修改stream配置文件,接入自定义组件到框架
位置:conf/cam/stream1.yaml
新增算法,填写脚本path和配置文件path即可
1 2 3 algorithm: - path: samples/lib/business/print_stream/component/print_stream_comp.py conf: samples/conf/algorithm/print_stream/print_stream.yaml
5.运行 修改conf/cam/stream1.yaml
取流地址为本地视频
1 2 3 stream: cam_id: 1 url: res/videos/renlian/test.mp4
在工程根目录(ZeroAI)运行命令
6.最终效果 实时取流并打印日志信息

导出成本地视频
进阶:框架源码参考 框架已经预提供了一些组件实现,麻雀虽小,五脏俱全,这些组价的实现思路基本已经能应对日常开发中的各种情况
算法组件实现参考:
【目标检测算法组件】:lib/detection/yolox_module
【多目标追踪组件】:lib/mot/bytetrack_module
【业务组件】:lib/business
服务组件实现参考:
【人脸识别服务组件】:lib/face/insight_module
帮助组件实现参考:
【人脸识别帮助组件】:lib/face/insight_module
TODO
找工作(游戏开发)
拓展算法
接入Web后端
Tensor RT、Cython加速