ModelBox
新手入门
【2022 ModelBox实战营】展开-合并功能单元

发布于30个月以前

  • 1
  • 0
  • 686

发布于30个月以前

ModelBox AI应用开发——展开/合并功能单元

本文将介绍两类特殊的功能单元——展开/合并功能单元,两者总是结合在一起使用,我们将在多手关键点识别应用基础上,使用ModelBox开发一个多手关键点识别的AI应用:读取摄像头,解码出视频帧,在画面中检测出所有的手部并识别出每只手上的21个关键点,再将关键点结果输出画面到本地屏幕。读者将了解到ModelBox中展开/合并功能单元的基本结构和开发流程。

1. 传统开发模式

对于这样的应用,在手部检测之后,需要另一个循环语句对每只手进行关键点识别,伪代码如下:

import cv2
import onnx_model

# 初始化摄像头,参数0表示设备的第一个摄像头
cap = cv2.VideoCapture(0)

# 判断初始化是否成功
if not cap.isOpened():
    print('failed to open camera 0')
    exit()

# 初始化手部检测模型
hand_det = onnx_model('./hand_det.onnx')

# 初始化手部关键点检测模型
hand_pose = onnx_model('./hand_pose.onnx')

while True:
    # 读取一帧视频图像,ret表示读取是否成功
    ret, frame = cap.read()

    # 图像预处理
    img_pre = hand_det.preprocess(frame)

    # 手部检测推理
    output = hand_det.infer(img_pre)

    # 检测结果后处理
    hand_bboxes = hand_det.postprocess(output)

    # 对所有的手部检测框循环处理
    for hand_bbox in hand_bboxes:

        # 从画面中裁剪出手部区域
        hand_img = crop_hand_img(frame, hand_bbox)

        # 手部图像预处理
        img_pre2 = hand_pose.preprocess(hand_img)

        # 手部关键点检测推理
        output2 = hand_pose.infer(img_pre2)

        # 关键点检测结果后处理
        hand_landmarks = hand_pose.postprocess(output2)

        # 在原始图像上画出手部关键点信息
        draw_hand_landmarks(frame, hand_landmarks)

    # 打开一个名为hand_pose的窗口,显示图像
    cv2.imshow('hand_pose', frame)

    # 阻塞等待键盘响应1ms,如果按下q键则跳出循环
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

# 释放摄像头资源
cap.release()

# 关闭所有窗口
cv2.destroyAllWindows()

2. ModelBox Pipeline模式

对于这类一条数据产生多个输出,每个输出采用相同处理方式的应用,ModelBox设计了特殊的功能单元——展开/合并功能单元来处理。接下来我们看看使用ModelBox如何构建这一应用。
这个应用对应的ModelBox版本已经做成模板放在华为云OBS中,可以用sdk中的solution.bat工具下载,接下来我们给出该应用在ModelBox中的完整开发过程:

1)下载模板

执行.\solution.bat -l可看到当前公开的技能模板:

PS ███> .\solution.bat -l
...

Solutions name:
mask_det_yolo3
...
multi_hand_pose_yolox_mbv2

结果中的multi_hand_pose_yolox_mbv2即为多手关键点识别应用模板,可使用如下命令下载模板:

PS ███> .\solution.bat -s multi_hand_pose_yolox_mbv2
...

solution.bat工具的参数中,-l 代表list,即列出当前已有的模板名称;-s 代表solution-name,即下载对应名称的模板。下载下来的模板资源,将存放在ModelBox核心库的solution目录下。

2)创建工程

ModelBox sdk目录下使用create.bat创建multi_hand_pose工程

PS ███> .\create.bat -t server -n multi_hand_pose -s multi_hand_pose_yolox_mbv2
sdk version is modelbox-xxx
success: create multi_hand_pose in ███\modelbox\workspace

与之前创建工程的命令相比,末尾多了-s参数,表示将使用后面参数值代表的模板创建工程,而不是创建空的工程。
workspace目录下将创建出multi_hand_pose工程,工程内容如下所示:

multi_hand_pose
|--bin
│  |--main.bat:应用执行入口
│  |--mock_task.toml:应用在本地执行时的输入输出配置,此应用默认使用本地视频文件为输入源,最终结果输出到另一本地视频文件,可根据需要修改
|--CMake:存放一些自定义CMake函数
|--data:存放应用运行所需要的图片、视频、文本、配置等数据
│  |--hand_test_video.mp4:多手关键点识别测试用视频文件
|--dependence
│  |--modelbox_requirements.txt:应用运行依赖的外部库在此文件定义
|--etc
│  |--flowunit:应用所需的功能单元存放在此目录
│  │  |--cpp:存放C++功能单元编译后的动态链接库,此应用没有C++功能单元
│  │  |--collapse_hand_landmarks:合并功能单元,将同一张图的多个手部关键点数据进行合并输出
│  │  |--draw_hand_landmarks:手部关键点画图功能单元
│  │  |--expand_hand_images:展开功能单元,将同一张图的多个手部检测框展开为多个输出
│  │  |--hand_condition:条件功能单元,根据图中是否检测到手输出不同的分支
│  │  |--landmarks_post:手部关键点检测模型的后处理功能单元
│  │  |--yolox_post:手部检测使用的是YOLOX模型,此处即为后处理功能单元
|--flowunit_cpp:存放C++功能单元的源代码,此应用没有C++功能单元
|--graph:存放流程图
│  |--multi_hand_pose.toml:默认流程图,使用本地视频文件作为输入源
│  |--multi_hand_pose_camera.toml:摄像头输入对应的流程图
│  |--modelbox.conf:modelbox相关配置
|--hilens_data_dir:存放应用输出的结果文件、日志、性能统计信息
|--model:推理功能单元目录
│  |--detect_hand:手部检测推理功能单元
│  │  |--detect_hand.toml:手部检测推理功能单元的配置文件
│  │  |--yolox_hand.onnx:手部检测onnx模型
│  |--detect_landmarks:手部关键点检测推理功能单元
│  │  |--detect_landmarks.toml:手部关键点检测推理功能单元的配置文件
│  │  |--pose_mbv2.onnx:手部关键点检测onnx模型
|--build_project.sh:应用构建脚本
|--CMakeLists.txt
|--rpm:打包rpm时生成的目录,将存放rpm包所需数据
|--rpm_copyothers.sh:rpm打包时的辅助脚本

与单手关键点识别应用相同,multi_hand_pose需要根据检测结果选择不同的分支进行后续操作:如果没有检测到手,直接输出原始图像;如果检测到手,需要进行手部关键点检测。不同的是,multi_hand_pose需要对图中检测到的每只手都识别关键点,因此增加了展开功能单元expand_hand_images,将图中的所有检测框展开为多个输出,传递到后面的功能单元分别做关键点识别;最后又增加了合并功能单元collapse_hand_landmarks,对同一张图的多个手部关键点数据进行合并输出,使得后面的画图功能单元能收集到同一张图片的完整数据。

3)查看展开/合并功能单元

展开/合并功能单元的开发流程与通用功能单元是相同的:

a. 创建功能单元

本应用已经包含了展开/合并功能单元,如果需要创建新的展开/合并功能单元,创建操作与通用功能单元相同,功能单元目录结构也完全一样(以Python版本的展开/合并功能单元为例):

[flowunit_name]
|--[flowunit_name].toml    # 功能单元属性配置文件
|--[flowunit_name].py      # 功能单元接口实现文件

b. 设置功能单元属性

打开展开功能单元的配置文件expand_hand_images.toml,看到其内容为:

# 基础配置
[base]
name = "expand_hand_images"                             # 功能单元名称
device = "cpu"                                          # 功能单元运行的设备类型,Python功能单元仅支持cpu类型
version = "1.0.0"                                       # 功能单元版本号
type = "python"                                         # 功能单元类型,Python功能单元此处为固定值python
group_type = "generic"                                  # 功能单元分组信息, 包括input/output/image/video/inference/generic等,默认为generic
description = "expand hand roi images from a frame"     # 功能单元的功能描述信息
entry = "expand_hand_images@expand_hand_imagesFlowUnit" # Python功能单元入口

# 工作模式,以下配置项默认全为false,表示通用功能单元;且配置项之间互斥,即最多只能设置其中一个为true
stream = false    # 是否是Stream类型功能单元
condition = false # 是否是条件功能单元
collapse = false  # 是否是合并功能单元
expand = true     # 是否是展开功能单元,此处设置为true,即表示expand_hand_images是一个展开功能单元

# 输入端口描述
[input]
[input.input1] # 输入数据
name = "in_image" # 原图 + 手部检测框(检测框以属性方式附加在原图上)
type = "uint8"    # 原图数据格式为 uint8

# 输出端口描述
[output]
[output.output1] # 输出数据
name = "roi_images" # 裁剪出的手部roi图片
type = "uint8"      # roi图片数据格式为 uint8

打开合并功能单元的配置文件collapse_hand_landmarks.toml,看到其内容为:

# 基础配置
[base]
name = "collapse_hand_landmarks"                                  # 功能单元名称
device = "cpu"                                                    # 功能单元运行的设备类型,Python功能单元仅支持cpu类型
version = "1.0.0"                                                 # 功能单元版本号
type = "python"                                                   # 功能单元类型,Python功能单元此处为固定值python
group_type = "generic"                                            # 功能单元分组信息, 包括input/output/image/video/inference/generic等,默认为generic
description = "collapse all human joints in a frame"              # 功能单元的功能描述信息
entry = "collapse_hand_landmarks@collapse_hand_landmarksFlowUnit" # Python功能单元入口

# 工作模式,以下配置项默认全为false,表示通用功能单元;且配置项之间互斥,即最多只能设置其中一个为true
stream = false    # 是否是Stream类型功能单元
condition = false # 是否是条件功能单元
collapse = true   # 是否是合并功能单元,此处设置为true,即表示collapse_hand_landmarks是一个合并功能单元
expand = false    # 是否是展开功能单元

# 输入端口描述:转为json字符串格式的手部关键点数据
[input]
[input.input1]
name = "in_landmarks"
type = "string"

# 输出端口描述:一帧图像中多个手部的关键点合并后的数据
[output]
[output.output1]
name = "out_data"
type = "float"

可以看到,与通用功能单元不同的是,展开功能单元的base.expand属性设置成了true。合并功能单元的base.collapse属性设置成了true。

  • 通用功能单元
    通用功能单元输出的数据与输入的数据属于同一层级,例如图像缩放功能单元Resize,输入的是图片流,产生的也是图片流;另外,通用功能单元一次可处理多个Buffer(即batch size可以大于1),数据按batch size设置分成多组,多组数据并发处理,生成的结果数据与输入一致。下图展示了通用功能单元的数据流图:

  • 展开功能单元
    展开功能单元一次只处理一个Buffer(即batch size限制为1),多个Buffer并发处理,每个Buffer展开成一个子数据流。下图展示了展开功能单元的数据流图:

  • 合并功能单元
    多个子数据流进入合并功能单元处理,一次处理一个子流,多个子流的数据并发处理,每个子流只能合并输出一个Buffer。下图展示了合并功能单元的数据流图:

c. 实现功能单元接口

展开/合并功能单元需要实现FlowUnit::openFlowUnit::closeFlowUnit::process三个接口,与通用功能单元相同。open、close接口的处理逻辑也与通用功能单元一样,只有process接口中的数据处理有所不同。

  • 展开功能单元expand_hand_imagesFlowUnit::process接口
    展开功能单元的process接口实现逻辑与通用功能单元基本一致,只在最后输出Buffer设置上有所不同:
    def process(self, data_context):
        # 1. 从DataContext中获取输入输出BufferList对象
        in_image = data_context.input("in_image")
        roi_images = data_context.output("roi_images")

        # 2. 循环处理每一个输入Buffer数据
        for buffer_img in in_image:
            # 3.1 获取输入Buffer的属性信息
            width = buffer_img.get('width')
            height = buffer_img.get('height')
            channel = buffer_img.get('channel')

            # 3.2 将输入Buffer转换为numpy对象
            img_data = np.array(buffer_img.as_object(), dtype=np.uint8, copy=False)
            img_data = img_data.reshape(height, width, channel)

            bboxes = buffer_img.get("bboxes")
            bboxes = np.array(bboxes).reshape(-1,4)

            # 3.3. 业务处理,根据检测框裁剪出多个手部图像,展开成一个子数据流
            # 展开功能单元的输出处理在for循环中,即展开为多个输出Buffer
            for bbox in bboxes:
                img_roi = self.crop_bbox_img(bbox, img_data)

                h, w, c = img_roi.shape
                img_roi = img_roi.flatten()

                # 4. 将业务处理返回的结果数据转换为Buffer,这里可以使用modelbox.Buffer构造函数
                img_buffer = modelbox.Buffer(self.get_bind_device(), img_roi)

                # 5. 设置输出Buffer的Meta信息,此处拷贝输入Buffer的部分Meta信息,其余差异的部分再进行设置
                img_buffer.copy_meta(buffer_img)
                img_buffer.set("pix_fmt", "bgr")
                img_buffer.set("width", w)
                img_buffer.set("height", h)
                img_buffer.set("channel", c)
                img_buffer.set("width_stride", w)
                img_buffer.set("height_stride", h)

                # 6. 将输出Buffer放入输出BufferList中
                roi_images.push_back(img_buffer)

        # 7. 返回成功标志,ModelBox框架会将数据发送到后续的功能单元
        return modelbox.Status.StatusCode.STATUS_SUCCESS

可以看到对于每一个Buffer(第一层for循环内),展开功能单元将其展开为多个Buffer进行输出(第二层for循环),即单张图片中的多个手部检测框,裁剪出多张手部图片,作为多条数据分别输出,这样后面的手部关键点检测等功能单元就可以针对每个Buffer分别处理。

  • 合并功能单元collapse_hand_landmarksFlowUnit::process接口
    合并功能单元的process接口实现逻辑与通用功能单元基本一致,只在最后输出Buffer设置上有所不同:
    def process(self, data_context):
        # 1. 从DataContext中获取输入输出BufferList对象
        in_landmarks = data_context.input("in_landmarks")
        out_data = data_context.output("out_data")

        # 2. 循环处理每一个输入Buffer数据,但输出合并到一个对象中(multi_landmarks)
        multi_landmarks = []
        for buffer_landmarks in in_landmarks:
            # 3.1. 将输入Buffer转换为字符串,从json字符串中解码出手部关键点数据
            landmarks_str = buffer_landmarks.as_object()
            landmarks = self.decode_landmarks(landmarks_str)

            # 3.2. 业务处理:将一帧图像中多个手部的关键点数据合并在一起
            multi_landmarks.append(landmarks)

        # 4. 将业务处理返回的结果数据转换为Buffer
        multi_landmarks = np.array(multi_landmarks)
        out_buffer = modelbox.Buffer(self.get_bind_device(), multi_landmarks)

        # 5/6. 将输出Buffer放入输出BufferList中,此处没有Meta设置
        out_data.push_back(out_buffer)

        # 7. 返回成功标志,ModelBox框架会将数据发送到后续的功能单元
        return modelbox.Status.StatusCode.STATUS_SUCCESS

可以看到它将接收到的所有Buffer收拢到一起,只产生一个Buffer的输出,这样就将单张图片中的多个手部关键点信息合并到一条数据中,后面的画图功能单元就可以画出所有手的关键点。

4)查看流程图

multi_hand_pose工程graph目录下包含两个流程图,其中与工程同名的multi_hand_pose.toml流程图,其内容为(以Windows版ModelBox为例):

[driver]
# 功能单元的扫描路径,包含在[]中,多个路径使用,分隔
# ${HILENS_APP_ROOT} 表示当前应用的实际路径
# ${HILENS_MB_SDK_PATH} 表示ModelBox核心库的实际路径
dir = [
    "${HILENS_APP_ROOT}/etc/flowunit",
    "${HILENS_APP_ROOT}/etc/flowunit/cpp",
    "${HILENS_APP_ROOT}/model",
    "${HILENS_MB_SDK_PATH}/flowunit",
]
skip-default = true

[profile]
# 通过配置profile和trace开关启用应用的性能统计
profile = false                       # 是否记录profile信息,每隔60s记录一次统计信息
trace = false                         # 是否记录trace信息,在任务执行过程中和结束时,输出统计信息
dir = "${HILENS_DATA_DIR}/mb_profile" # profile/trace信息的保存位置

[flow]
desc = "multi-hand pose estimation example using yolox and mobilenet-v2 for local video or rtsp video stream" # 应用的简单描述

[graph]
format = "graphviz" # 流程图的格式,当前仅支持graphviz
graphconf = """digraph multi_hand_pose {
    node [shape=Mrecord]
    queue_size = 4
    batch_size = 1

    # 定义节点,即功能单元及其属性
    input1[type=input, flowunit=input, device=cpu, deviceid=0]
    data_source_parser[type=flowunit, flowunit=data_source_parser, device=cpu, deviceid=0]
    video_demuxer[type=flowunit, flowunit=video_demuxer, device=cpu, deviceid=0]
    video_decoder[type=flowunit, flowunit=video_decoder, device=cpu, deviceid=0, pix_fmt="bgr"]
    image_resize[type=flowunit, flowunit=resize, device=cpu, deviceid=0, image_width=320, image_height=320]
    color_transpose[type=flowunit flowunit=packed_planar_transpose device=cpu deviceid="0"]
    normalize[type=flowunit flowunit=normalize device=cpu deviceid="0" standard_deviation_inverse="1.,1.,1."]
    detect_hand[type=flowunit, flowunit=detect_hand, device=cpu, deviceid=0, batch_size = 1]
    yolox_post[type=flowunit, flowunit=yolox_post, device=cpu, deviceid=0]
    hand_condition[type=flowunit, flowunit=hand_condition, device=cpu, deviceid=0]
    expand_hand_images[type=flowunit, flowunit=expand_hand_images, device=cpu, deviceid=0]
    image_resize2[type=flowunit, flowunit=resize, device=cpu, deviceid=0, image_width=256, image_height=256]
    color_transpose2[type=flowunit flowunit=packed_planar_transpose device=cpu deviceid="0"]
    mean[type=flowunit flowunit=mean device=cpu deviceid="0" mean="116.28,103.53,123.68"]
    normalize2[type=flowunit flowunit=normalize device=cpu deviceid="0" standard_deviation_inverse="0.0175070,0.01742919,0.01712475"]
    detect_landmarks[type=flowunit, flowunit=detect_landmarks, device=cpu, deviceid=0, batch_size = 1]
    landmarks_post[type=flowunit, flowunit=landmarks_post, device=cpu, deviceid=0]
    collapse_hand_landmarks[type=flowunit, flowunit=collapse_hand_landmarks, device=cpu, deviceid=0]
    draw_hand_landmarks[type=flowunit, flowunit=draw_hand_landmarks, device=cpu, deviceid=0]
    video_out[type=flowunit, flowunit=video_out, device=cpu, deviceid=0]

    # 定义边,即功能间的数据传递关系
    input1:input -> data_source_parser:in_data
    data_source_parser:out_video_url -> video_demuxer:in_video_url
    video_demuxer:out_video_packet -> video_decoder:in_video_packet
    video_decoder:out_video_frame -> image_resize:in_image
    image_resize:out_image -> color_transpose:in_image
    color_transpose:out_image -> normalize:in_data
    normalize:out_data -> detect_hand:input
    detect_hand:output -> yolox_post:in_feat
    yolox_post:out_data -> hand_condition:in_bbox
    video_decoder:out_video_frame -> hand_condition:in_image
    hand_condition:no_hand -> video_out:in_video_frame
    hand_condition:has_hand -> expand_hand_images:in_image
    expand_hand_images:roi_images -> image_resize2:in_image
    image_resize2:out_image -> color_transpose2:in_image
    color_transpose2:out_image -> mean:in_data
    mean:out_data -> normalize2:in_data
    normalize2:out_data -> detect_landmarks:input
    detect_landmarks:output -> landmarks_post:in_feat
    landmarks_post:out_data -> collapse_hand_landmarks:in_landmarks
    hand_condition:has_hand -> draw_hand_landmarks:in_image
    collapse_hand_landmarks:out_data -> draw_hand_landmarks:in_landmarks
    draw_hand_landmarks:out_image -> video_out:in_video_frame
}"""

图中,可以看到条件功能单元hand_condition的两个输出分别对接到不同的功能单元,在未检测到手部时,no_hand分支直接对接到video_out进行视频编码;检测到手部时,has_hand对接到之后的展开/合并功能单元做全部手的关键点识别。而展开功能单元expand_hand_images与合并功能单元collapse_hand_landmarks之间其他功能单元的使用方式,与正常流程并无不同。
另外,可以看到预处理功能单元resizepacked_planar_transposenormalize等分别使用了两次(两次的属性不同),每种功能单元在图中也相应的定义了两个实例,使用不同的节点名称进行区分。

5)查看输入输出配置

查看任务配置文件bin/mock_task.toml,可以看到其中的任务输入和任务输出配置为如下内容::

[input]
type = "url"
url = "${HILENS_APP_ROOT}/data/hand_test_video.mp4"  # 表示输入源为本地视频文件

[output]
type = "local"
url = "${HILENS_APP_ROOT}/hilens_data_dir/hand_test_result.mp4"  # 表示输出为本地视频文件

即,使用本地视频文件data/hand_test_video.mp4作为输入,解码、预处理、手部检测、后处理、手部关键点检测后,画图输出到本地视频文件data/hand_test_result.mp4中。

6)用启动脚本执行应用

启动应用前执行.\build_project.sh进行工程构建,该脚本将编译自定义的C++功能单元(本应用不涉及)、将应用运行时会用到的配置文件转码为Unix格式(防止执行过程中的格式错误):

PS ███> .\build_project.sh
...
PS ███>

然后执行.\bin\main.bat运行应用:

PS ███> .\bin\main.bat
...

运行结束后在hilens_data_dir目录下生成了hand_test_result.mp4文件,可以打开查看:

7)查看camera流程图

除了测试视频文件外,我们还可以使用PC自带或者外接的USB摄像头进行手部关键点检测,对应的流程图为multi_hand_pose_camera.toml,其内容如下:

[driver]
# 功能单元的扫描路径,包含在[]中,多个路径使用,分隔
# ${HILENS_APP_ROOT} 表示当前应用的实际路径
# ${HILENS_MB_SDK_PATH} 表示ModelBox核心库的实际路径
dir = [
    "${HILENS_APP_ROOT}/etc/flowunit",
    "${HILENS_APP_ROOT}/etc/flowunit/cpp",
    "${HILENS_APP_ROOT}/model",
    "${HILENS_MB_SDK_PATH}/flowunit",
]
skip-default = true

[profile]
# 通过配置profile和trace开关启用应用的性能统计
profile = false                       # 是否记录profile信息,每隔60s记录一次统计信息
trace = false                         # 是否记录trace信息,在任务执行过程中和结束时,输出统计信息
dir = "${HILENS_DATA_DIR}/mb_profile" # profile/trace信息的保存位置

[flow]
desc = "multi-hand pose estimation example using yolox and mobilenet-v2 for local USB camera" # 应用的简单描述

[graph]
format = "graphviz" # 流程图的格式,当前仅支持graphviz
graphconf = """digraph multi_hand_pose {
    node [shape=Mrecord]
    queue_size = 4
    batch_size = 1

    # 定义节点,即功能单元及其属性
    input1[type=input, flowunit=input, device=cpu, deviceid=0]
    data_source_parser[type=flowunit, flowunit=data_source_parser, device=cpu, deviceid=0]
    local_camera[type=flowunit, flowunit=local_camera, device=cpu, deviceid=0, pix_fmt=bgr, cam_width=1280, cam_height=720]
    image_resize[type=flowunit, flowunit=resize, device=cpu, deviceid=0, image_width=320, image_height=320]
    color_transpose[type=flowunit flowunit=packed_planar_transpose device=cpu deviceid="0"]
    normalize[type=flowunit flowunit=normalize device=cpu deviceid="0" standard_deviation_inverse="1.,1.,1."]
    detect_hand[type=flowunit, flowunit=detect_hand, device=cpu, deviceid=0, batch_size = 1]
    yolox_post[type=flowunit, flowunit=yolox_post, device=cpu, deviceid=0]
    hand_condition[type=flowunit, flowunit=hand_condition, device=cpu, deviceid=0]
    expand_hand_images[type=flowunit, flowunit=expand_hand_images, device=cpu, deviceid=0]
    image_resize2[type=flowunit, flowunit=resize, device=cpu, deviceid=0, image_width=256, image_height=256]
    color_transpose2[type=flowunit flowunit=packed_planar_transpose device=cpu deviceid="0"]
    mean[type=flowunit flowunit=mean device=cpu deviceid="0" mean="116.28,103.53,123.68"]
    normalize2[type=flowunit flowunit=normalize device=cpu deviceid="0" standard_deviation_inverse="0.0175070,0.01742919,0.01712475"]
    detect_landmarks[type=flowunit, flowunit=detect_landmarks, device=cpu, deviceid=0, batch_size = 1]
    landmarks_post[type=flowunit, flowunit=landmarks_post, device=cpu, deviceid=0]
    collapse_hand_landmarks[type=flowunit, flowunit=collapse_hand_landmarks, device=cpu, deviceid=0]
    draw_hand_landmarks[type=flowunit, flowunit=draw_hand_landmarks, device=cpu, deviceid=0]
    video_out[type=flowunit, flowunit=video_out, device=cpu, deviceid=0]

    # 定义边,即功能间的数据传递关系
    input1:input -> data_source_parser:in_data
    data_source_parser:out_video_url -> local_camera:in_camera_packet
    local_camera:out_camera_frame -> image_resize:in_image
    image_resize:out_image -> color_transpose:in_image
    color_transpose:out_image -> normalize:in_data
    normalize:out_data -> detect_hand:input
    detect_hand:output -> yolox_post:in_feat
    yolox_post:out_data -> hand_condition:in_bbox
    local_camera:out_camera_frame -> hand_condition:in_image
    hand_condition:no_hand -> video_out:in_video_frame
    hand_condition:has_hand -> expand_hand_images:in_image
    expand_hand_images:roi_images -> image_resize2:in_image
    image_resize2:out_image -> color_transpose2:in_image
    color_transpose2:out_image -> mean:in_data
    mean:out_data -> normalize2:in_data
    normalize2:out_data -> detect_landmarks:input
    detect_landmarks:output -> landmarks_post:in_feat
    landmarks_post:out_data -> collapse_hand_landmarks:in_landmarks
    hand_condition:has_hand -> draw_hand_landmarks:in_image
    collapse_hand_landmarks:out_data -> draw_hand_landmarks:in_landmarks
    draw_hand_landmarks:out_image -> video_out:in_video_frame
}"""

multi_hand_pose.toml相比,这个流程图使用local_camera替换了video_demuxervideo_decoder功能单元,其他部分是一致的。

8)修改输入和输出配置

打开工程目录下bin/mock_task.toml文件,修改其中的任务输入和任务输出配置为如下内容:

[input]
type = "url"
url = "0"  # 表示0号摄像头,即PC自带摄像头,若PC无摄像头需外接USB摄像头

[output]
type = "local"
url = "0:multi_hand_pose"  # 表示名为```multi_hand_pose```的本地窗口

即使用编号为0的摄像头(默认为PC自带的摄像头),输出画面显示到名为multi_hand_pose的本地屏幕窗口中。

9)运行应用

执行.\bin\main.bat camera运行应用,将会自动弹出实时的手部关键点检测的画面:

3. 小结

通过本教程,我们学习了ModelBox中展开/合并功能单元的开发,有一类常见的视频AI应用是检测+分类,或者检测+识别,需要对检测到的所有对象分别处理,如人脸识别、车牌识别等,此时可以使用ModelBox中的展开/合并功能单元,它能保证大部分模块的处理逻辑与正常数据流相同,又能对展开的子数据流进行高效的并发处理,提高运行速度。

课程打卡

根据本次课程学习的内容,在自己的环境进行复现,并将复现的程序运行界面,连同网页右上方的华为云账号截图发至打卡贴上,详细打卡规则参见活动页面或参考2022ModelBox实战营活动打卡 (huaweicloud.com)

LLM初学者

作者相关内容

【2022 ModelBox实战营】第一个应用
发布于30个月以前
【2022 ModelBox实战营】通用Python功能单元
发布于30个月以前
【ModelBox客流分析实战营】ModelBox端云协同AI开发套件(Windows)SDK安装篇
发布于28个月以前
【2022 ModelBox实战营】推理功能单元
发布于30个月以前
ModelBox开发案例 - 使用YOLO v3做口罩检测
发布于34个月以前

暂无数据

热门内容推荐

ModelArts JupyterLab常见问题解决办法
ModelArts开发者 发布于45个月以前
为医生打造专属数字分身!华为云联合万木健康打造医疗医学科普和患者教育数字人引擎
HWCloudAI 发布于20个月以前
图数据库 | 聊聊超级快的图上多跳过滤查询
弓役是也 发布于23个月以前
ModelArts准备工作_简易版
ModelArts开发者 发布于46个月以前
”智蔗见智·向新而生”广西第二届人工智能大赛baseline使用教程
追乐小王子 发布于31个月以前

暂无数据