TIOVX (TI OpenVX)简介及例程

1 篇文章 4 订阅

TIOVX

TIOVX(TI OpenVX)是TI针对OpenVX标准的实施,主要在TDA4上使用
TDA4是TI基于深度学习技术的 L2/L3 近场分析系统的下一代 SoC 系列

1. OpenVX

OpenVX是Khronos Group为实现计算机视觉应用程序的跨平台加速而提出的一种开放的、免版税的标准。

1.1 简介

OpenVX标准中提供了一系列的图像处理的API函数和运行框架,不同的芯片厂如TI,Intel,Nvida等针对这个标准,提供了适应于自己平台的底层驱动和硬件加速。

特点:

  • 高度抽象的API
  • 跨架构的可移植性
  • 低功耗视觉加速

1.2 相关资源

2. TIOVX详解

2.1 Overview

在这里插入图片描述

TIOVX Block Diagram

TIOVX Framework包含了官方OpenVX的标准API和TI扩展的API,其中

  • public: Context, Parameter, Kernel, Node, Graph Array, Image, Scalar, Pyramid, ObjectArray
  • TI: Target, Target Kernel, Obj Desc

TIOVX Platform提供了特定硬件(如TDAx, AM65x)的操作系统(如TI-rtos, linux)调用API。

TIOVX Kernel Wrapper提供了由硬件模块VPAC(Vision Pre-processing Accelerator)和DMPAC(Depth and Motion Perception Accelerator)封装成的Kernel,用户也可用Wrapper将自定义的算法(如OpenCV算法, DSP算法)封装成Kernel。

2.2 Framework API

标准API

Framework Object(框架对象)

  • Context(上下文):OpenVX context是OpenVX所有对象运行的对象域。所有数据对象以及所有框架对象都在context中运行。

  • Kernel(核):OpenVX中的内核是计算机视觉功能的抽象表示。

  • Parameter(参数):传递给计算机视觉功能的抽象输入或输出数据对象。该对象包含内核描述中该参数使用的签名。

  • Node(节点):节点是内核的一个实例,它将与一组特定的引用(参数)配对。节点仅从单个图创建并与单个图相关联。

  • Graph(图):一组以有向(仅单向)非循环(不循环)方式连接的节点。图可能具有与同一图中的其他节点集未连接的节点集。

Data Object(数据对象):

数据对象是由节点中的图形处理的对象。

  • Array(数组):一个不透明的数组对象,可以是原始数据类型数组或结构数组。
  • Convolution(卷积):一个不透明的对象,包含米× Nvx_int16值矩阵。还包含用于归一化的缩放因子。专门与vxuConvolve和 一起使用vxConvolveNode。
  • Delay(延迟):一个不透明的对象,包含手动控制的、时间延迟的对象列表。
  • Distribution(分布):包含频率分布(例如,直方图)的不透明对象。
  • Image(图像):一个不透明的图像对象,可能是vx_df_image_e.
  • LUT(查找表):与vxTableLookupNode和 一起使用的不透明查找表对象vxuTableLookup。
  • Matrix(矩阵):一个不透明的对象,包含米× N 一些标量值的矩阵。
  • Pyramid(金字塔):包含多级缩放vx_image对象的不透明对象。
  • Remap(重映射):一个不透明的对象,其中包含用于转换图像的源点到目标点的映射。
  • Scalar(标量):包含单一原始数据类型的不透明对象。
  • Threshold(阈值):包含阈值配置的不透明对象。
  • ObjectArray(数组对象):一个不透明的数组对象,可以是 OpenVX 的任何数据对象(非数据类型)的数组,但 Delay 和 ObjectArray 对象除外。
扩展API
  • Target:TI Soc上的CPU、DSP、HWA等
  • Target Kernel:在Target上运行的Kernel
  • Obj Desc(Object Descriptor)

2.3 Graph示例

TIOVX主要结构

/* create graph */
vx_context context = vxCreateContext();
vx_image input = vxCreateImage( context, 640, 480, VX_DF_IMAGE_U8 );
vx_image output = vxCreateImage( context, 640, 480, VX_DF_IMAGE_U8 );
vx_graph graph = vxCreateGraph( context );
vx_image intermediate = vxCreateVirtualImage( graph, 640, 480, VX_DF_IMAGE_U8 );
vx_node F1 = vxF1Node( graph, input, intermediate );
vx_node F2 = vxF2Node( graph, intermediate, output );
/* verify graph */
if ( vxVerifyGraph( graph ) == VX_SUCCESS) 
{
    while(...) 
    {
        // … write to input image …
		/* execute graph */
        vxProcessGraph( graph ); // or vxScheduleGraph(graph);
        // … read from output image …
    }
}

2.4 Pipeline

在这里插入图片描述
如图所示,当一帧图像从ISP输出,经过DSP到CPU一共需要99ms,当使用Pipeline时,同一时间每个节点都能同时运行,通过增加硬件利用率的方式达到了增加Graph吞吐量的目的,实现了30FPS。

在Graph运行过程中,节点从共享内存中读取源数据(obj_desc)进行处理,处理完后将数据输出到目标(obj_desc)的共享内存地址。

// 创建capture节点,获取图像
status = app_create_graph_capture(obj->graph, &obj->captureObj);
// 创建ldc节点,进行摄像头校正和格式转换
status = app_create_graph_ldc(obj->graph, &obj->ldcObj, obj->captureObj.raw_image_arr[0]);
// 创建mosaic节点,将四个摄像头图像拼成田字格
status = app_create_graph_img_mosaic(obj->graph, &obj->imgMosaicObj);
// 创建display节点,显示输出
status = app_create_graph_display(obj->graph, &obj->displayObj, display_in_image);
// 设置Graph参数(输入和输出)
graph_parameter_index = 0;
// 设置Graph参数
add_graph_parameter_by_node_index(obj->graph, obj->captureObj.node, 1);// Graph中图像输入的节点,图像输出在node中的参数位置
obj->captureObj.graph_parameter_index = graph_parameter_index;
graph_parameters_queue_params_list[graph_parameter_index].graph_parameter_index = graph_parameter_index;
graph_parameters_queue_params_list[graph_parameter_index].refs_list_size = APP_BUFFER_Q_DEPTH; // 设置节点缓冲区个数
graph_parameters_queue_params_list[graph_parameter_index].refs_list = (vx_reference*)&obj->captureObj.raw_image_arr[0];// 输出的图像
graph_parameter_index++;
// 设置Graph运行模式
status = vxSetGraphScheduleConfig(obj->graph,
                VX_GRAPH_SCHEDULE_MODE_QUEUE_AUTO,
                graph_parameter_index,
                graph_parameters_queue_params_list);
status = tivxSetGraphPipelineDepth(obj->graph, APP_PIPELINE_DEPTH);

// 图像入队 次数等于缓冲区个数
status = vxGraphParameterEnqueueReadyRef(obj->graph, captureObj->graph_parameter_index, (vx_reference*)&obj->captureObj.raw_image_arr[obj->enqueueCnt], 1);

for(;;)
{
    // capture节点输出一帧图像
    status = vxGraphParameterDequeueDoneRef(obj->graph, captureObj->graph_parameter_index, (vx_reference*)&capture_input_arr, 1, &num_refs);
	status = vxGraphParameterEnqueueReadyRef(obj->graph, captureObj->graph_parameter_index, (vx_reference*)&capture_input_arr, 1);

}
          

2.5 IPC 通信

/**
 * @brief 向目标target发送obj_id
 * 
 * obj_desc_id: 要发送的数据ID号 例如图像或控制命令
 * dst_target_id: 接收消息的target,例如DSP,MSC硬件模块等
 */
vx_status tivxObjDescSend(uint32_t dst_target_id, uint16_t obj_desc_id)
"判断dst_target_id所在的核"
self_cpu_id: 
    vx_status tivxTargetQueueObjDesc(vx_enum target_id, uint16_t obj_desc_id)
remote_cpu_id:
    /**
     * @brief 向远程核发送消息
     * 
     * cpu_id:  目标cpu ID号,target_id所在的cpu_id
     * payload:  tivxIpcPayloadMake((int32_t)dst_target_id, obj_desc_id) 由dst_target_id和obj_desc_id组合而成
     * host_cpu_id:  obj_desc->host_cpu_id  创建obj_desc的核  openvx_host_cpu_id 指A72
     * host_port_id:  obj_desc->host_port_id[self_cpu_id] RTOS: 13 Linux: 1024 目标核接收的端口
     */
    vx_status tivxIpcSendMsg( vx_enum cpu_id, uint32_t payload, uint32_t host_cpu_id, uint32_t host_port_id)
    "判断目标核cpu_id"
    host_cpu_id: A72 Linux
        /**
         * @brief 向核发送消息
         * 
         * dest_cpu_id: 目标核
         * payload: 传递的数据 由dst_target_id和obj_desc_id组合而成
         * port_id: host_port_id: RTOS: 13 Linux: 1024
         */
        int32_t appIpcSendNotifyPort(uint32_t dest_cpu_id, uint32_t payload, uint32_t port_id)
    remote_cpu_id: rtos
        /**
         * @brief 向核发送消息
         * 
         * dest_cpu_id: 目标核
         * payload: 传递的数据 由dst_target_id和obj_desc_id组合而成
         */
        int32_t appIpcSendNotify(uint32_t dest_cpu_id, uint32_t payload)
            /**
             * @brief 向核发送消息
             * 
             * dest_cpu_id: 目标核
             * payload: 传递的数据 由dst_target_id和obj_desc_id组合而成
             * port_id: obj->prm.tiovx_rpmsg_port_id
             */
            int32_t appIpcSendNotifyPort(uint32_t dest_cpu_id, uint32_t payload, uint32_t port_id)
            "发送的核运行rtos"
                /**
                 * @brief 向远程核发送消息
                 * 
                 * handle: obj->rpmsg_rx_handle 初始化时创建的接收句柄 接收发送共用同一个
                 * procId: g_app_to_ipc_cpu_id[dest_cpu_id] 目标核id
                 * dstEndPt: port_id 目标核接收的端点
                 * srcEndPt: obj->prm.tiovx_rpmsg_port_id 当前核发送的端点
                 * data: &payload
                 * len: sizeof(payload)
                 */
            int32_t RPMessage_send(RPMessage_Handle handle, uint32_t procId, uint32_t dstEndPt, uint32_t srcEndPt, void* data, uint16_t len)
            "发送的核运行linux"
                /**
                 * @brief 向rpmsg设备写入数据
                 * 
                 * __fd: obj->tx_fds[dest_cpu_id] 初始化时根据rpmsg设备创建的fd
                 * __buf: &payload
                 * __n: sizeof(payload)
                 */
                ssize_t write(int __fd, const void *__buf, size_t __n)


/* 接收线程 RTOS */
static void appIpcRpmsgRxTaskMain(void *arg0, void *arg1)
while(!done)
{
    /**
     * @brief 接收消息
     * 
     * handle: obj->rpmsg_rx_handle 初始化时创建的句柄
     * data: &obj->rpmsg_rx_msg_buf 接收数据的缓冲区
     * len: &len 数据的长度
     * rplyEndPt: &reply_endpt  
     * rplyProcId: &src_cpu_id 发送数据的核ID
     * timeout: SemaphoreP_WAIT_FOREVER 永久等待接收消息
     */
    int32_t RPMessage_recv(RPMessage_Handle handle, void* data, uint16_t *len, uint32_t *rplyEndPt, uint32_t *rplyProcId, uint32_t timeout)
    /**
     * @brief 数据处理
     * 
     * rpmsg_handle: obj->rpmsg_rx_handle 初始化时创建的句柄
     * arg: obj 
     * data: obj->rpmsg_rx_msg_buf 接收数据的缓冲区
     * len: len 数据长度
     * src_cpu_id: src_cpu_id 发送数据的核ID
     * src_endpt: reply_endpt 发送数据的端口
     * dst_endpt: obj->prm.tiovx_rpmsg_port_id
     */
    static void appIpcRpmsgRxHandler(RPMessage_Handle rpmsg_handle, void *arg, void *data, uint16_t len, uint32_t src_cpu_id, uint16_t src_endpt, uint16_t dst_endpt)
    (payload & 0xFFFF0000) == 0xDEAD0000: 
        /**
         * @brief 回复消息 主要用于测试
         * 
         * dest_cpu_id: 目标核
         * payload: 数据回传
         * port_id: src_endpt 发送数据的端口
         */
        int32_t appIpcSendNotifyPort(uint32_t dest_cpu_id, uint32_t payload, uint32_t port_id)
    else: 
        /**
         * @brief 消息处理回调函数
         * 
         * 已注册为 tivxIpcHandler
         * appIpcRegisterNotifyHandler(tivxIpcHandler)
         */
        obj->ipc_notify_handler(app_cpu_id, payload)
        /**
         * @brief 回调函数
         * 
         */
        static void tivxIpcHandler(uint32_t src_cpu_id, uint32_t payload)
            /**
             * @brief 消息处理回调函数
             * 
             * 已注册为 tivxObjDescIpcHandler
             * tivxIpcRegisterHandler(tivxObjDescIpcHandler)
             */
            g_ipc_handler(payload)
            /**
             * @brief 消息处理回调函数
             * 
             * payload: 消息数据
             */
            static void tivxObjDescIpcHandler(uint32_t payload)
                /**
                 * @brief 从payload获取数据id并存入队列
                 * 
                 * target_id: tivxIpcPayloadGetTargetId(payload) 
                 * obj_desc_id: tivxIpcPayloadGetObjDescId(payload) 数据id
                 */
                vx_status tivxTargetQueueObjDesc(vx_enum target_id, uint16_t obj_desc_id)
}

/* 接收线程 linux */
void *appIpcRpmsgRxTaskMain(void *arg)
while(!done)
{
    /* 初始化 fd集合 */
    FD_ZERO(&rfds);
    /* 找fd的最大值 */
    maxfd = MAX(maxfd, obj->tx_fds[i]);
    /* 在集合中添加 fd */
    FD_SET(obj->tx_fds[i], &rfds);
    /* fd最大值+1 */
    nfds = MAX(maxfd, obj->unblockfd) + 1;
    /* 监视fd集合 */
    status = select(nfds, &rfds, NULL, NULL, NULL);
    for(i = 0; i< APP_IPC_CPU_MAX; i++)
    {
        /* 判断哪个fd接收到了消息 */
        FD_ISSET(obj->tx_fds[i], &rfds)
        /**
         * @brief 从fd中获取消息
         * 
         * __fd: obj->tx_fds[i] 初始化时为每个核创建的fd
         * __buf: &payload 接收的数据
         * __nbytes: sizeof(uint32_t) 数据长度
         */
        ssize_t read(int __fd, void *__buf, size_t __nbytes)
        /**
         * @brief 消息处理回调函数
         * 
         * app_cpu_id: i 发送数据的核ID
         * payload: 获取到的数据
         */
        static void appIpcRpmsgRxHandler(uint32_t app_cpu_id, uint32_t payload)
        "后续同上"
    }
}

2.6 Memory

内存分配与获取
。。。

3. 示例

//C71 DEMO
/*
 * Graph,
 *
 * input_img_1 -> app_node_img_add -----> output_img
 *                  ^
 *                  |
 * input_img_2 -----+
 */

// 创建OpenVX Context
obj->context = vxCreateContext();
// 将要运行的Kernel注册到Target中
vx_status app_c7x_kernel_img_add_register(vx_context context)
{
    vx_kernel kernel = NULL;
    vx_status status;
    uint32_t index;

    // 动态分配一个kernel ID并存储在app_c7x_kernel_img_add_kernel_id中 
    status = vxAllocateUserKernelId(context, &app_c7x_kernel_img_add_kernel_id);
    if(status!=VX_SUCCESS)
    {
        printf(" ERROR: vxAllocateUserKernelId failed (%d)!!!\n", status);
    }
    if(status==VX_SUCCESS)
    {
        // 将Kernel注册到Context中
        // 注册后可以通过ID和Name来获取Kernel
        kernel = vxAddUserKernel(
                    context,
                    APP_C7X_KERNEL_IMG_ADD_NAME,
                    app_c7x_kernel_img_add_kernel_id,
                    NULL,
                    3, 		// kernel的参数个数
                    app_c7x_kernel_img_add_kernel_validate,// Kernel的验证函数
                    NULL, 	// Kernel的初始化函数
                    NUL);	// Kernel的取消初始化
        // 检查Kernel是否成功添加到Context
        status = vxGetStatus((vx_reference)kernel);
    }
    if ( status == VX_SUCCESS)
    {
        // 定义Kernel的输入,输出及配置参数
        index = 0;

        status = vxAddParameterToKernel(kernel,
            index,			// 参数目录
            VX_INPUT,		// 参数类型
            VX_TYPE_IMAGE, 	// 类型
            VX_PARAMETER_STATE_REQUIRED // 是否必须
            );
        index++;
        if ( status == VX_SUCCESS)
        {
            status = vxAddParameterToKernel(kernel,
                index,
                VX_INPUT,
                VX_TYPE_IMAGE,
                VX_PARAMETER_STATE_REQUIRED
                );
            index++;
        }
        if ( status == VX_SUCCESS)
        {
            status = vxAddParameterToKernel(kernel,
                index,
                VX_OUTPUT,
                VX_TYPE_IMAGE,
                VX_PARAMETER_STATE_REQUIRED
                );
            index++;
        }
        if ( status == VX_SUCCESS)
        {
            // 添加可运行Kernel的target 
            tivxAddKernelTarget(kernel, TIVX_TARGET_DSP_C7_1);
        }
        if ( status == VX_SUCCESS)
        {
            // Kernel 已经可以使用
            status = vxFinalizeKernel(kernel);
        }
        if( status != VX_SUCCESS)
        {
            printf(" ERROR: vxAddParameterToKernel, vxFinalizeKernel failed (%d)!!!\n", status);
            // 释放对Kernel的引用
            vxReleaseKernel(&kernel);
            kernel = NULL;
        }
    }
    else
    {
        kernel = NULL;
        printf(" ERROR: vxAddUserKernel failed (%d)!!!\n", status);
    }
    // 设置全局kernel句柄便于后面释放Kernel
    app_c7x_kernel_img_add_kernel = kernel;
    return status;
}

static vx_status app_create_graph(AppObj *obj)
{
    vx_status status = VX_SUCCESS;
	// 在Context中创建Graph
    obj->graph = vxCreateGraph(obj->context);
    APP_ASSERT_VALID_REF(obj->graph);
	// 根据图像大小和图像类型创建图像
    obj->input_img1 = vxCreateImage(obj->context, obj->width, obj->height, VX_DF_IMAGE_U8);
    APP_ASSERT_VALID_REF(obj->input_img1);
    // 设置图像名
    vxSetReferenceName((vx_reference)obj->input_img1, "Input1ImageU8");

    obj->input_img2 = vxCreateImage(obj->context, obj->width, obj->height, VX_DF_IMAGE_U8);
    APP_ASSERT_VALID_REF(obj->input_img2);
    vxSetReferenceName((vx_reference)obj->input_img2, "Input2ImageU8");

    obj->output_img = vxCreateImage(obj->context, obj->width, obj->height, VX_DF_IMAGE_U8);
    APP_ASSERT_VALID_REF(obj->output_img);
    vxSetReferenceName((vx_reference)obj->output_img, "OutputImageU8");
	// 将节点添加到Graph中,并指定输入输出
    obj->node = app_c7x_kernel_img_add_kernel_node(
                            obj->graph,
                            obj->input_img1,
                            obj->input_img2,
                            obj->output_img
                            );
    APP_ASSERT_VALID_REF(obj->node);
	// 设置节点运行的Core
    status = vxSetNodeTarget(obj->node, VX_TARGET_STRING, TIVX_TARGET_DSP_C7_1);
    APP_ASSERT(status==VX_SUCCESS);
    // 设置节点名
    vxSetReferenceName((vx_reference)obj->node, "C7xImageAddition");
	// 验证图像,运行Kernel的验证函数app_c7x_kernel_img_add_kernel_validate
    status = vxVerifyGraph(obj->graph);
    APP_ASSERT(status==VX_SUCCESS);
	// 导出Graph结构到文件
    status = tivxExportGraphToDot(obj->graph,".", "vx_app_c7x_kernel");
    /* APP_ASSERT(status==VX_SUCCESS); */ /* dont assert on this error since its not critical */

    return status;
}
static vx_status app_run_graph(AppObj *obj)
{
    vx_status status = VX_SUCCESS;

    printf(" Loading [%s] ...\n", obj->input_file_1);
    // 读取文件数据到图像中
    tivx_utils_load_vximage_from_bmpfile(obj->input_img1, obj->input_file_1, vx_true_e);

    printf(" Loading [%s] ...\n", obj->input_file_2);
    tivx_utils_load_vximage_from_bmpfile(obj->input_img2, obj->input_file_2, vx_true_e);

    printf(" Running graph ...\n");
    #if 1
    // 执行Graph
    status = vxScheduleGraph(obj->graph);
    APP_ASSERT(status==VX_SUCCESS);
    // 等待Graph运行完
    status = vxWaitGraph(obj->graph);
    APP_ASSERT(status==VX_SUCCESS);
    #endif

    printf(" Saving [%s] ...\n", obj->output_file);
    tivx_utils_save_vximage_to_bmpfile(obj->output_file, obj->output_img);
    printf(" Done !!!\n");

    return status;
}
static void app_delete_graph(AppObj *obj)
{
    vxReleaseNode(&obj->node);
    vxReleaseGraph(&obj->graph);
    vxReleaseImage(&obj->input_img1);
    vxReleaseImage(&obj->input_img2);
    vxReleaseImage(&obj->output_img);
}

vx_status app_c7x_kernel_img_add_unregister(vx_context context)
{
    vx_status status;

    /**
     * - Remove user kernel from context and set the global 'app_c7x_kernel_img_add_kernel' to NULL
     *
     */
    status = vxRemoveKernel(app_c7x_kernel_img_add_kernel);
    app_c7x_kernel_img_add_kernel = NULL;

    if(status!=VX_SUCCESS)
    {
        printf(" Unable to remove kernel (%d)!!!\n", status);
    }

    return status;
}

vxReleaseContext(&obj->context);

4. 后记

开发TDA4快一年了,TIOVX也接触有些时间了,写篇文章记录一下,后续待补充 2021.8.23
添加pipeline 2021.11.15
添加ipc通信相关 2022.1.7

5. 参考资源

  • 17
    点赞
  • 86
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
### 回答1: OpenVX编程指南PDF是一本非常实用的指南,旨在为需要使用OpenVX库进行图像和视觉处理的开发人员提供指导。OpenVX是一个跨平台的高性能计算机视觉库,它可以让开发人员更轻松地构建图像,视频和视觉信号处理应用程序。 这本指南涵盖了从基础知识到高级应用程序的各种主题,包括OpenVX框架,数据结构,语法和算法。它还提供了有关如何在OpenVX中使用功能块,滤波和其他图像处理工具的详细说明。 无论您是计算机视觉领域的专业人员还是初学者,OpenVX编程指南PDF都可以帮助您更加深入地了解OpenVX,并掌握其基本原理和使用方法。通过仔细学习这些概念和技术,您可以更好地应用OpenVX编程,并将其用于您的项目中。 此外,OpenVX编程指南PDF还包含丰富的实例和代码,可以帮助您更好地理解OpenVX的各种概念和技术,并加快您的开发速度。总之,如果您需要学习OpenVX编程,那么这本指南是您的理想选择。 ### 回答2: OpenVX编程指南(PDF)是一份介绍OpenVX图像处理框架的指南。该框架是一个跨平台的、高度优化的库,旨在提供一系列的函数,使图像、视频和视觉算法的开发变得更加容易。OpenVX框架使用图形计算来加速算法,并提供许多特定领域的算法,包括图像识别、人脸识别、深度学习和目标跟踪等。它具有许多有点,包括简化了编程过程、提高了计算效率、减少了开发时间和硬件开销等。在OpenVX编程指南中,介绍了如何使用OpenVX API和使用OpenVX图形计算框架来构建高效的计算图。此外,该指南提供了丰富的示例代码和代码片段,以便读者可以更好地理解OpenVX API库的功能和使用方式。总之,OpenVX编程指南非常实用和有用,对于想要学习或掌握OpenVX框架的人来说,是一份必备的指南。 ### 回答3: OpenVX编程指南是一本详细的文档,用于指导OpenVX图像处理库的使用和编程。OpenVX库提供了一种高效的图像处理方法,可以帮助开发者轻松地实现复杂的图像处理算法。 这本编程指南包含了OpenVX库的各种特性、数据结构和函数的详细介绍。开发者可以通过这本指南了解OpenVX库的工作原理和实现方式,从而更好地了解如何使用OpenVX库来解决各种图像处理问题。 指南包括了大量的示例代码和详细的说明,使得开发者可以快速上手使用OpenVX库来编写图像处理应用程序。此外,指南中还提供了一些最佳实践和优化技巧,可以帮助开发者编写出更高效和可维护的代码。 总之,OpenVX编程指南是一本非常实用和详细的资料,可以帮助有志于使用OpenVX库开发图像处理应用程序的开发者快速掌握相关的知识和技能,从而更好地完成自己的工作。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值