Skip to content

Latest commit

 

History

History
599 lines (537 loc) · 24.2 KB

算法管线开发10分钟入门.md

File metadata and controls

599 lines (537 loc) · 24.2 KB

算法管线开始10分钟入门

环境准备工作

开发者镜像

如果您已经在使用ANTGO的开发者镜像的话可以忽略。

  • 开发镜像
    registry.cn-hangzhou.aliyuncs.com/vibstring/antgo-env-dev:latest
    
  • 模型转换镜像(涉及模型多平台部署时需要)
    # snpe 引擎
    registry.cn-hangzhou.aliyuncs.com/vibstring/snpeconvert:latest
    # tnn 引擎
    registry.cn-hangzhou.aliyuncs.com/vibstring/tnnconvert:latest
    # rknn 引擎
    registry.cn-hangzhou.aliyuncs.com/vibstring/rknnconvert:latest
    
  • 启动开发环境
    docker run -d --shm-size="20G" -p 8080:8080 -e PASSWORD=123 -v /data:/dataset -v /tmp:/tmp -v $(pwd):/workspace -v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker --gpus all --privileged registry.cn-hangzhou.aliyuncs.com/vibstring/antgo-env-dev /opt/code-server-4.92.2-linux-amd64/bin/code-server --host 0.0.0.0 --auth password
    

下载配套代码

```
wget https://file.vibstring.com/yolo.tar && tar -xf yolo.tar
cd yolo
```

开始我们的算法吧

基于图的算法设计

    graph LR;
    id1[(采集)]-->无标签数据

Loading

我的第一个算法管线

在这里,我们以YOLO目标检测算法为例,介绍如何搭建管线

from antgo.pipeline import *
import cv2
import numpy as np
import math

# 加载自定义C++算子
op.load('yolodecode2op', './')

# 创建输出文件夹
os.makedirs('./output/', exist_ok=True)

def debug_show(image,obj_bboxes,obj_masks):
    image_h, image_w = image.shape[:2]

    obj_num = len(obj_bboxes)
    mask_h, mask_w = obj_masks.shape[1:3]
    obj_seg_canvas = np.zeros((obj_num*mask_h, mask_w), np.uint8)
    for obj_i, obj_bbox in enumerate(obj_bboxes):
        x0,y0,x1,y1,_,label = obj_bbox
        cv2.rectangle(image, (int(x0),int(y0)), (int(x1),int(y1)),2)
        obj_mask = obj_masks[obj_i]
        obj_seg_canvas[obj_i*mask_h:(obj_i+1)*mask_h, :] = (obj_mask*255).astype(np.uint8)
    
    cv2.imwrite("./output/mask.png", obj_seg_canvas)
    cv2.imwrite("./output/image.png", image)
    return image, obj_seg_canvas


# 基础管线搭建
# 目标:
# 1. 对管线设计有初步认识
# 2. 数据源概念(图像数据源)
#       imread_dc['image']('./data/demo.png')
# 3. 管线驱动
imread_dc['image']('./data/demo.png'). \
    resize_op['image', 'resized_image'](out_size=(640,640)). \
    inference_onnx_op['resized_image', ('output_0', 'output_1', 'output_2', 'output_3', 'output_4', 'output_5', 'output_6', 'output_7', 'output_8', 'output_9', 'output_10', 'output_11', 'output_12')](
        onnx_path='./models/yolov8n-seg.onnx', 
        mean=[0, 0, 0],
        std=[255, 255, 255],
    ). \
    deploy.YoloDecodeOp[('image', 'output_0', 'output_1', 'output_2', 'output_3', 'output_4', 'output_5', 'output_6', 'output_7', 'output_8', 'output_9', 'output_10', 'output_11', 'output_12'), ('obj_bboxes', 'obj_masks')](). \
    runas_op[('image', 'obj_bboxes', 'obj_masks'), ('image_det', 'mask_det')](func=debug_show).run()

视频数据源,帮助快速验证时序数据下算法效果

# 替换为视频源,管线搭建
# 目标:
# 1. 数据源概念(视频数据源)
#       video_dc['image', 'index']('./data/99_1727154910.mp4')
# 2. 视频保存节点使用
video_dc['image', 'index']('./data/99_1727154910.mp4'). \
    resize_op['image', 'resized_image'](out_size=(640,640)). \
    inference_onnx_op['resized_image', ('output_0', 'output_1', 'output_2', 'output_3', 'output_4', 'output_5', 'output_6', 'output_7', 'output_8', 'output_9', 'output_10', 'output_11', 'output_12')](
        onnx_path='./models/yolov8n-seg.onnx', 
        mean=[0, 0, 0],
        std=[255, 255, 255],
    ). \
    deploy.YoloDecodeOp[('image', 'output_0', 'output_1', 'output_2', 'output_3', 'output_4', 'output_5', 'output_6', 'output_7', 'output_8', 'output_9', 'output_10', 'output_11', 'output_12'), ('obj_bboxes', 'obj_masks')](). \
    runas_op[('image', 'obj_bboxes', 'obj_masks'), ('image_det', 'mask_det')](func=debug_show). \
    select('image_det'). \
    to_video('./output/out.mp4')

尝鲜一下,快速生成WEB服务,让小伙伴看看算法效果

# 管线WEB服务
# 目标:
# 1. WEB服务上下文
# 2. 设定输入输入类型
#       输入类型支持'image', 'video', 'text', 'slider', 'checkbox', 'select', 'image-search'
#       输出类型支持'image', 'video', 'text', 'number', 'file'
with web['image'](name='demo') as handler:
    app = handler.resize_op['image', 'resized_image'](out_size=(640,640)). \
    inference_onnx_op['resized_image', ('output_0', 'output_1', 'output_2', 'output_3', 'output_4', 'output_5', 'output_6', 'output_7', 'output_8', 'output_9', 'output_10', 'output_11', 'output_12')](
        onnx_path='./models/yolov8n-seg.onnx', 
        mean=[0, 0, 0],
        std=[255, 255, 255],
    ). \
    deploy.YoloDecodeOp[('image', 'output_0', 'output_1', 'output_2', 'output_3', 'output_4', 'output_5', 'output_6', 'output_7', 'output_8', 'output_9', 'output_10', 'output_11', 'output_12'), ('obj_bboxes', 'obj_masks')](). \
    runas_op[('image', 'obj_bboxes', 'obj_masks'), ('image_det', 'mask_det')](func=debug_show). \
    demo(
        title="YOLO-Seg DEMO",
        description="YOLO-Seg DEMO",
		input=[
			{'data': 'image', 'type': 'image'},
		], 
		output=[
			{'data': 'image_det', 'type': 'image'},
			{'data': 'mask_det', 'type': 'image'}
		]
    )

在当前目录下,执行如下代码即可启动服务

antgo web --main=yolo_plugin:app --ip=0.0.0.0 --port=9002

访问一下试试,你将看到如下页面

测试x86-64下部署(SDK模式)

python模式下执行常常仅用于算法原型验证阶段使用。企业级交付,考虑到计算效率和上层应用集成问题常需要C++部署方案。 在这里,我们先试试在x86-64下的部署输出。x86-64易于测试内存泄漏等工程型问题,建议可以优先在此平台测试工程 执行后,会生成编译好的so库和demo可执行程序。如果涉及模型运行,在x86平台,需要指定tensorrt作为推理引擎。不同平台和推理引擎的对应关系,见下表格。

平台和推理引擎关系对应表

平台 推理引擎
linux/x86-64 tensorrt(Nvidia平台)
linux/arm64 rknn(RK芯片平台)
android/arm64-v8a tnn(通用基于OPENCL推理)/snpe(高通平台)/rknn(RK芯片平台)
# x86-64编译部署(SDK模式)
# 目标
# 1. 使用build节点进行部署
# 2. 设置工程编译配置,
#       目标平台(platform),目前支持linux/x86-64,linux/arm64,android/arm64-v8a
#       项目配置(project_config),包括输入和输出类型设置,工程名字(name="",需要采用project/pipeline_name格式),模式(mode='app/server'),调用模式(call_mode='sync/asyn')
#       环境配置(eagleeye_config),基础环境依赖项,包括opencv, rk, ffmpeg
placeholder['image'](np.zeros((640,640,3), dtype=np.uint8)). \
    resize_op['image', 'resized_image'](out_size=(640,640)). \
    inference_onnx_op['resized_image', ('output_0', 'output_1', 'output_2', 'output_3', 'output_4', 'output_5', 'output_6', 'output_7', 'output_8', 'output_9', 'output_10', 'output_11', 'output_12')](
        onnx_path='./models/yolov8n-seg.onnx', 
        mean=[0, 0, 0],
        std=[255, 255, 255],
        engine='tensorrt',
    ). \
    deploy.YoloDecodeOp[('image', 'output_0', 'output_1', 'output_2', 'output_3', 'output_4', 'output_5', 'output_6', 'output_7', 'output_8', 'output_9', 'output_10', 'output_11', 'output_12'), ('obj_bboxes', 'obj_masks')](). \
    build(
        platform='linux/x86-64',
        project_config={
            'input': [
                ('image', 'EAGLEEYE_SIGNAL_BGR_IMAGE'),
            ],
            'output': [
                ('obj_bboxes', 'EAGLEEYE_SIGNAL_TENSOR'),
                ('obj_masks', 'EAGLEEYE_SIGNAL_TENSOR')
            ],
            'name': 'leshislam/yolo',
            'git': '',
            'mode': 'app',
            'call_mode': 'sync',
        },
        eagleeye_config={
            'opencv': None,
        }
    )

在运行完后,你会发现在当前目录下出现了deploy目录,在这个目录下是编译后的工程 “folder”

在这个目录下,需要解释一下自动创建的子目录含义,

  • linux_x86_64_build.sh

    编译整个项目

  • CMakeLists.txt

    编译时使用的CmakeLists文件。可以看一下文件内容,你会有更清晰的认识,整个项目依赖了哪些库。

  • cmake

    常用的cmake文件。用于简化依赖库的发现。

  • yolo_plugin.h/cpp

    根据python脚本里构建管线,转换成的C++代码构建管线。

  • leshislam_demo.cpp

    创建的如何调用管线的示例代码,可以帮助开发同学快速集成管线到自己的项目中。

  • extent

    extent文件夹里存放的是开发者自定义的C++计算节点代码

  • config

    用于存放管线基础信息的配置文件。

  • models

    用于存放转换后的模型文件(在端上平台运行,必须针对不同的预测引擎,将onnx模型转换成引擎接受的模型格式。)。

  • bin,build

    编译后的信息存放位置

  • setup.sh

    整理项目所有依赖so库打包到bin文件夹下,从而简化交付。

  • run.sh

    运行DEMO。支持图像输入。

进入,deploy/leshislam_plugin,运行

    bash setup.sh

会完成依赖库打包,将所有依赖信息均搜集到bin文件夹下。现在可以开始测试运行了,

    bash run.sh image demo.png

执行完成后,你将在bin/x86-64/data/output/目录下发现保存的管线输出结果。

在这里,我们可以继续详细分析一下,管线的对外接口。管线设计采用统一标准接口,也就是说无论什么项目接口模式完全一致。 注意管线的输入和输出配置,在接口范例中有所涉及。

"config"

接口范例如下:

// 第一步:初始化
const char* config_folder = NULL;
eagleeye_{project}_initialize(config_folder);

// 第二步:设置管线输入(管线设计时设置了几个输入,这里就要为几个输入赋值)
unsigned char* in_data_0 = ...;                                // 内存指针
std::vector<size_t> in_data_0_size = {image_h, image_w, 3};    // tensor dims
int in_data_0_type = 0;                                        // 数据类型
eagleeye_{project}_set_input(“placeholder_0”, in_data_0, in_data_0_size.data(), in_data_0_size.size(), 0, in_data_0_type);

// 第三步:管线执行
eagleeye_{project}_run();

// 第四步:获得返回目标框结果
// 这里可以获得管线的所有的输入数据
void* out_data;             // 内存指针
size_t* out_data_size;      // tensor dims
int out_data_dims=0;        // dim size
int out_data_type=0;        // 数据类型
eagleeye_{project}_get_output(“nnnode/0”,out_data, out_data_size, out_data_dims, out_data_type);

// 第五步:销毁管线
eagleeye_{project}_release();

注意,所涉及的数据类型,有如下定义

enum{
	EAGLEEYE_CHAR 	   				= 0,		// int8
	EAGLEEYE_UCHAR	   				= 1,		// uint8
	EAGLEEYE_SHORT	   				= 2,		// int16
	EAGLEEYE_USHORT	   				= 3,		// uint16
	EAGLEEYE_INT	   				= 4,		// int32
	EAGLEEYE_UINT	   				= 5,		// uint32
	EAGLEEYE_FLOAT	   				= 6,		// float32
	EAGLEEYE_DOUBLE	   				= 7,
}

可以结合如上通用接口,见本例中的leshislam_demo.cpp中的代码范例。

快速体验发布x86-64下GRPC服务发布

看看如何在不改任何代码的情况下,直接交付服务

# 目标
# 1. 了解发布server模式,mode='server'
# 2. 了解生成GRPC服务的通用proto定义
# 3. 了解同步模式和异步模式区别和接口使用,call_mode='sync/asyn'
placeholder['image'](np.zeros((640,640,3), dtype=np.uint8)). \
    resize_op['image', 'resized_image'](out_size=(640,640)). \
    inference_onnx_op['resized_image', ('output_0', 'output_1', 'output_2', 'output_3', 'output_4', 'output_5', 'output_6', 'output_7', 'output_8', 'output_9', 'output_10', 'output_11', 'output_12')](
        onnx_path='./models/yolov8n-seg.onnx', 
        mean=[0, 0, 0],
        std=[255, 255, 255],
        engine='tensorrt',
    ). \
    deploy.YoloDecodeOp[('image', 'output_0', 'output_1', 'output_2', 'output_3', 'output_4', 'output_5', 'output_6', 'output_7', 'output_8', 'output_9', 'output_10', 'output_11', 'output_12'), ('obj_bboxes', 'obj_masks')](). \
    build(
        platform='linux/x86-64',
        project_config={
            'input': [
                ('image', 'EAGLEEYE_SIGNAL_BGR_IMAGE'),
            ],
            'output': [
                ('obj_bboxes', 'EAGLEEYE_SIGNAL_TENSOR'),
                ('obj_masks', 'EAGLEEYE_SIGNAL_TENSOR')
            ],
            'name': 'leshislam/yolo',
            'git': '',
            'mode': 'server',
            'call_mode': 'sync',
        },
        eagleeye_config={
            'opencv': None,
        }
    )

进入,deploy/leshislam_plugin,运行

    bash setup.sh

会完成依赖库打包,将所有依赖信息均搜集到bin文件夹下。现在开始运行并指定服务端口

    bash run.sh 9002

不出意外,你将会看到如下信息输出, 恭喜你,服务已经成功启动,又可以交付服务了。工作如此简单。

下面可以详细解释一下,服务通用protobuf的定义见deploy/leshislam_plugin/proto/leshislam.proto。所有项目均采用统一proto定义。 框架也自动为您生成了python版本的调用示例,见deploy/leshislam_plugin/grpc_client.py。接下来为您解释标准接口定义,

# 同步服务接口(依赖构建服务时指定的call_mode决定)
# 服务启动接口
{project}GrpcSyncStart
所涉及的请求和响应参数定义如下
message {project}GrpcSyncStartRequest {
  string serverpipeline = 1;            // 需要设置管线名称(对于本教程中的例子,则为yolo)
  string serverid = 2;                  // 唯一ID(用于标识服务组,允许用户自己设定)
  string servercfg = 3;                 // 服务配置(用于配置服务启动参数,普通用户可忽略)
}
message {project}GrpcSyncStartReply {
  string message = 1;                   // 返回的消息例如 success
  int32 code = 2;                       // 返回的code 0 成功,非0失败
  string serverkey = 3;                 // 服务关键字(成功启动后,返回此服务唯一编码。在代用服务时需要使用。)
}

# 服务调用接口
{project}GrpcSyncCall
所涉及的请求和响应参数定义如下
message {project}GrpcSyncCallRequest{
  string serverkey = 1;                 // 服务关键字(在启动服务时,返回的服务唯一编码)
  string serverrequest = 2;             // 服务请求(构建json字符串,用于为管线赋值)
}
message {project}GrpcSyncCallReply{
  string message = 1;                   // 返回的消息例如 success
  int32 code = 2;                       // 返回的code 0 成功,非0失败
  string data = 3;                      // (服务返回数据,json格式)
}

# 服务停止接口
{project}GrpcSyncStop
所涉及的请求和响应参数定义如下
message {project}GrpcSyncStopRequest {
    string serverkey = 1;               // 服务关键字(在启动服务时,返回的服务唯一编码)
}
message {project}GrpcSyncStopReply {
  string message = 1;                   // 返回的消息例如 success
  int32 code = 2;                       // 返回的code 0 成功,非0失败
}

# 异步服务接口(依赖构建服务时指定的call_mode决定)
# 服务启动接口
{project}GrpcAsynStart
所涉及的请求和响应参数定义如下
message {project}GrpcAsynStartRequest {
  string serverpipeline = 1;            // 服务管线(对于本教程中的例子,则为yolo)
  string serverid = 2;                  // 唯一ID(用于标识服务组,允许用户自己设定)
  string servercfg = 3;                 // 服务配置(用于配置服务启动参数,普通用户可忽略)
}
message {project}GrpcAsynStartReply {
  string message = 1;                   // 返回的消息例如 success
  int32 code = 2;                       // 返回的code 0 成功,非0失败
  string serverkey = 3;                 // 服务关键字(成功启动后,返回此服务唯一编码。在代用服务时需要使用。)
}

# 数据传输接口
{project}GrpcAsynPush
所涉及的请求和响应参数定义如下
message {project}GrpcAsynPushRequest{
  string serverkey = 1;                 // 服务关键字(在启动服务时,返回的服务唯一编码)
  string serverrequest = 2;             // 服务请求(构建json字符串,用于为管线赋值)
}
message {project}GrpcAsynPushReply{
  string message = 1;                   // 返回的消息例如 success
  int32 code = 2;                       // 返回的code 0 成功,非0失败
  string data = 3;                      // 忽略
}


# 消息接收接口
{project}GrpcAsynMessage
所涉及的请求和响应参数定义如下
message {project}GrpcAsynMessageRequest{
  string serverkey = 1;                 // 服务关键字(在启动服务时,返回的服务唯一编码)
  string serverrequest = 2;             // 服务请求
}
message {project}GrpcAsynMessageReply{
  string message = 1;                   // 返回的消息例如 success
  int32 code = 2;                       // 返回的code 0 成功,非0失败
  string data = 3;                      // (服务返回数据,json格式)
}

# 服务停止接口
{project}GrpcAsynStop
所涉及的请求和响应参数定义如下
message {project}GrpcAsynStopRequest {
    string serverkey = 1;               // 服务关键字(在启动服务时,返回的服务唯一编码)
}
message {project}GrpcAsynStopReply {
  string message = 1;                   // 返回的消息例如 success
  int32 code = 2;                       // 返回的code 0 成功,非0失败
}

其中{project}会根据你构建时设置的project_config下的name(格式含义project/pipeline)自动替换。 接下来我们看一下,调用服务时参数是如何传递的。参数体采用json格式进行传递,

info = {
    'data': [
        # 图像参数(需要编码成base64)
        {
            'type': 'image',
            'content': base64.b64encode(image bytes).decode()
        },
        # 字符串参数
        {
            'type': 'string',
            'content': 'xxx'
        },
        # 整数参数(需要以矩阵格式给出)
        {
            'type': 'matrix/int32',
            'width': 1,
            'height': 1,
            'content': [1],
        }
    ]    
}

下面以python语言进行举例,可以在自动生成的grpc_client.py基础上进行修改

with grpc.insecure_channel(f'{ip}:{port}') as channel:
    stup = leshislam_pb2_grpc.LeshislamGrpcStub(channel)

    # step 1: 启动服务
    response = stup.LeshislamGrpcSyncStart(leshislam_pb2.LeshislamGrpcSyncStartRequest(serverpipeline="yolo",serverid="xyz", servercfg=""))
    serverkey = response.ListFields()[0][1]
    print(f'server key {serverkey}')

    # step 2: 调用服务
    # 读取图像,并构造接送请求串
    with open('./data/demo.png', 'rb') as fp:
        image_content = fp.read()
    info = {
        'data': [
            {
                'type': 'image',
                'content': base64.b64encode(image_content).decode()
            },
        ]
    }
    serverrequest = json.dumps(info)		
    response = stup.LeshislamGrpcSyncCall(leshislam_pb2.LeshislamGrpcSyncCallRequest(serverkey=serverkey, serverrequest=serverrequest))
    print(response)

接下来我们运行吧

python3 grpc_client.py --ip=127.0.0.1 --port=9002

跨平台发布之 linux/arm64-v8a

需要注意,如果涉及模型运行,则要指定服务目标平台的预测引擎。具体平台和预测引擎对应关系需要见上表。

# 目标
# 1. 了解指定rknn引擎
# 2. 了解指定目标平台
placeholder['image'](np.zeros((640,640,3), dtype=np.uint8)). \
    resize_op['image', 'resized_image'](out_size=(640,640)). \
    inference_onnx_op['resized_image', ('output_0', 'output_1', 'output_2', 'output_3', 'output_4', 'output_5', 'output_6', 'output_7', 'output_8', 'output_9', 'output_10', 'output_11', 'output_12')](
        onnx_path='./models/yolov8n-seg.onnx', 
        mean=[0, 0, 0],
        std=[255, 255, 255],
        engine='rknn',
        engine_args={
            'device':'rk3588',
        }
    ). \
    deploy.YoloDecodeOp[('image', 'output_0', 'output_1', 'output_2', 'output_3', 'output_4', 'output_5', 'output_6', 'output_7', 'output_8', 'output_9', 'output_10', 'output_11', 'output_12'), ('obj_bboxes', 'obj_masks')](). \
    build(
        platform='linux/arm64',    # 需要支持linux/arm64
        project_config={
            'input': [
                ('image', 'EAGLEEYE_SIGNAL_BGR_IMAGE'),
            ],
            'output': [
                ('obj_bboxes', 'EAGLEEYE_SIGNAL_TENSOR'),
                ('obj_masks', 'EAGLEEYE_SIGNAL_TENSOR')
            ],
            'name': 'leshislam/yolo',
            'git': '',
            'mode': 'app',
            'call_mode': 'sync',
        },
        eagleeye_config={
            'opencv': None,
            'rk': None
        }
    )

进入到deploy/leshislam_plugin后,运行

bash setup.sh

将所有依赖项打包到bin/目录下。此时将整个项目leshislam_plugin推送到目标平台,就可以直接运行了。快快试试吧!在目标平台下,依然通过如下代码执行

    bash run.sh image demo.png

执行完成后,你将在bin/arm64-v8a/data/output/目录下发现保存的管线输出结果。

跨平台发布之 android/arm64-v8a

# 目标
# 1. 了解指定rknn引擎
# 2. 了解指定目标平台
# 3. 了解android平台so的测试过程
placeholder['image'](np.zeros((640,640,3), dtype=np.uint8)). \
    resize_op['image', 'resized_image'](out_size=(640,640)). \
    inference_onnx_op['resized_image', ('output_0', 'output_1', 'output_2', 'output_3', 'output_4', 'output_5', 'output_6', 'output_7', 'output_8', 'output_9', 'output_10', 'output_11', 'output_12')](
        onnx_path='./models/yolov8n-seg.onnx', 
        mean=[0, 0, 0],
        std=[255, 255, 255],
        engine='rknn',
        engine_args={
            'device':'rk3588',
        }
    ). \
    deploy.YoloDecodeOp[('image', 'output_0', 'output_1', 'output_2', 'output_3', 'output_4', 'output_5', 'output_6', 'output_7', 'output_8', 'output_9', 'output_10', 'output_11', 'output_12'), ('obj_bboxes', 'obj_masks')](). \
    build(
        platform='android/arm64-v8a',
        project_config={
            'input': [
                ('image', 'EAGLEEYE_SIGNAL_BGR_IMAGE'),
            ],
            'output': [
                ('obj_bboxes', 'EAGLEEYE_SIGNAL_TENSOR'),
                ('obj_masks', 'EAGLEEYE_SIGNAL_TENSOR')
            ],
            'name': 'leshislam/yolo',
            'git': '',
            'mode': 'app',
            'call_mode': 'sync',
        },
        eagleeye_config={
            'opencv': None,
            'rk': None
        }
    )

进入到deploy/leshislam_plugin后,我们看一下setup.sh脚本

adb shell "if [ ! -d '/data/local/tmp/leshislam' ]; then mkdir /data/local/tmp/leshislam; fi;"
adb push /root/.3rd/eagleeye/android-install/libs/arm64-v8a/* /data/local/tmp/leshislam/
adb push /root/.3rd/eagleeye/android-install/3rd/opencv/lib/arm64-v8a/* /data/local/tmp/leshislam/
adb push /root/.3rd/eagleeye/android-install/3rd/libyuv/lib/arm64-v8a/* /data/local/tmp/leshislam/
adb push ./bin/arm64-v8a/* /data/local/tmp/leshislam/
adb push ./3rd/arm64-v8a/* /data/local/tmp/leshislam/
adb shell "if [ ! -d '/data/local/tmp/leshislam/data' ]; then mkdir /data/local/tmp/leshislam/data; fi;"

可以看到,运行此setup.sh脚本后,会搜集所有依赖库并把项目编译产物推送到真机平台的/data/local/tmp目录下。然后可以主机上直接运行(前提是,需要把测试图片同时推送到目标目录)

bash run.sh image demo.png

如果,运行成功,便可以放心的将所有编译产物进行交付了。

相信至此,作为算法研发人员的你便可以无任何障碍进行高性能算法交付了。