大家好,首先感谢阅读,如果您也对TDA4相关的开发感兴趣,可以私信联系博主,我们这边有个学习交流群,可以入群和大家一起交流学习。
也可以加博主WX :AIR_12 我会拉你入群。保持开源精神,共同分享、进步!
很久之前写了一篇关于图像相关操作的博客,经过一段时间的研究,有了比较深入的了解,现在将比较系统的总结一下,并修正之前版本的一些错误。
一、创建图像
目前在官方给出的版本内,有以下几种方法可以实现创建图像的操作。
函数名 | 说明 | |
vxCreateImage |
直接创建一个图像 | 根据图像格式定义 |
vxCreateImageFromHandle |
从一个句柄中创建一个图像 | 可以是一个文件的句柄/或者另一个图像在内存内的指针索引 |
vxCreateImageFromChannel |
从另一个图像的单个平面通道创建子图像。 子图像是指原始图像中的数据。 对此图像的更新会更新父图像,反之亦然。 该功能仅支持占据多平面图像整个平面的通道,如下所列。 不支持其他情况。 VX_CHANNEL_Y 来自 YUV4、IYUV、NV12、NV21 VX_CHANNEL_U 来自 YUV4、IYUV VX_CHANNEL_V 来自 YUV4、IYUV | 需要满足一定的条件 |
vxCreateImageFromROI |
给定一个矩形,从另一个图像创建一个图像。 第二个参考是指原始图像中的数据。 对此图像的更新会更新父图像。 矩形必须在父图像的像素空间内定义。 | 从另一个图像内部截取或者复制整个图像 |
vxCreateVirtualImage |
创建对图像缓冲区的不透明引用,用户无法直接访问。 此功能允许设置图像宽度、高度或格式。 | 暂时没研究 |
vxCreateUniformImage |
创建对在所有像素中具有单一、统一值的图像对象的引用。 创建的统一图像是只读的。 | 暂时没研究 |
1、直接创建图像:
vx_image image = vxCreateImage(context, width, height, <FORMAT>);
vxCreateImage 用于创建图像,输入参数
context:上下文
widht:所需创建图像的宽度
height:所需创建图像的高度
<FORMAT>:所需创建图像的格式。(TDA4 所有支持的格式如下所示)
目前我们比较常用的是NV12格式的图片。
enum vx_df_image_e {
/*! \brief A virtual image of no defined type. */
VX_DF_IMAGE_VIRT = VX_DF_IMAGE('V','I','R','T'),
/*! \brief A single plane of 24-bit pixel as 3 interleaved 8-bit units of
* R then G then B data. This uses the BT709 full range by default.
*/
VX_DF_IMAGE_RGB = VX_DF_IMAGE('R','G','B','2'),
/*! \brief A single plane of 32-bit pixel as 4 interleaved 8-bit units of
* R then G then B data, then a <i>don't care</i> byte.
* This uses the BT709 full range by default.
*/
VX_DF_IMAGE_RGBX = VX_DF_IMAGE('R','G','B','A'),
/*! \brief A 2-plane YUV format of Luma (Y) and interleaved UV data at
* 4:2:0 sampling. This uses the BT709 full range by default.
*/
VX_DF_IMAGE_NV12 = VX_DF_IMAGE('N','V','1','2'),
/*! \brief A 2-plane YUV format of Luma (Y) and interleaved VU data at
* 4:2:0 sampling. This uses the BT709 full range by default.
*/
VX_DF_IMAGE_NV21 = VX_DF_IMAGE('N','V','2','1'),
/*! \brief A single plane of 32-bit macro pixel of U0, Y0, V0, Y1 bytes.
* This uses the BT709 full range by default.
*/
VX_DF_IMAGE_UYVY = VX_DF_IMAGE('U','Y','V','Y'),
/*! \brief A single plane of 32-bit macro pixel of Y0, U0, Y1, V0 bytes.
* This uses the BT709 full range by default.
*/
VX_DF_IMAGE_YUYV = VX_DF_IMAGE('Y','U','Y','V'),
/*! \brief A 3 plane of 8-bit 4:2:0 sampled Y, U, V planes.
* This uses the BT709 full range by default.
*/
VX_DF_IMAGE_IYUV = VX_DF_IMAGE('I','Y','U','V'),
/*! \brief A 3 plane of 8 bit 4:4:4 sampled Y, U, V planes.
* This uses the BT709 full range by default.
*/
VX_DF_IMAGE_YUV4 = VX_DF_IMAGE('Y','U','V','4'),
/*! \brief A single plane of unsigned 8-bit data.
* The range of data is not specified, as it may be extracted from a YUV or
* generated.
*/
VX_DF_IMAGE_U8 = VX_DF_IMAGE('U','0','0','8'),
/*! \brief A single plane of unsigned 16-bit data.
* The range of data is not specified, as it may be extracted from a YUV or
* generated.
*/
VX_DF_IMAGE_U16 = VX_DF_IMAGE('U','0','1','6'),
/*! \brief A single plane of signed 16-bit data.
* The range of data is not specified, as it may be extracted from a YUV or
* generated.
*/
VX_DF_IMAGE_S16 = VX_DF_IMAGE('S','0','1','6'),
/*! \brief A single plane of unsigned 32-bit data.
* The range of data is not specified, as it may be extracted from a YUV or
* generated.
*/
VX_DF_IMAGE_U32 = VX_DF_IMAGE('U','0','3','2'),
/*! \brief A single plane of unsigned 32-bit data.
* The range of data is not specified, as it may be extracted from a YUV or
* generated.
*/
VX_DF_IMAGE_S32 = VX_DF_IMAGE('S','0','3','2'),
};
2、从句柄内创建图像
可以从已经打开的图像文件内创建图像,或者复制其他图像。
vx_imagepatch_addressing_t image_addr;
image_addr.dim_x = width;
image_addr.dim_y = height;
image_addr.stride_x = bpp; //每个像素所占字节数,根据不同格式的图像有所不同
image_addr.stride_y = bpp*width; //这个值需要设置为 bpp*width
image_addr.scale_x = VX_SCALE_UNITY;
image_addr.scale_y = VX_SCALE_UNITY;
image_addr.step_x = 1;
image_addr.step_y = 1;
ptrs[0] = data_ptr;
image = vxCreateImageFromHandle(context, df, &image_addr, ptrs, (vx_enum)VX_MEMORY_TYPE_HOST);
stride_x:代表在X 轴(宽度)上每一步的大小,比如RGB 图像每个像素为3个字节,则这里需要设置为3;
stride_y:代表在Y 轴(高度)上每一步的大小,即切换到下一行,所需要的字节个数。等于 stride_x * width。(这样解释应该比较清晰)。
ptrs:即读入图像文件的句柄,或者是已知图像的内存某个地址。
3、从图像通道内创建图像
vxCreateImageFromChanne (暂未研究,等待补充!)
4、从图像的矩形内创建图像
typedef struct _vx_rectangle_t {
volatile vx_uint32 start_x; /*!< \brief The Start X coordinate. */
volatile vx_uint32 start_y; /*!< \brief The Start Y coordinate. */
volatile vx_uint32 end_x; /*!< \brief The End X coordinate. */
volatile vx_uint32 end_y; /*!< \brief The End Y coordinate. */
} vx_rectangle_t;
vx_image vxCreateImageFromROI(vx_image img, const vx_rectangle_t *rect);
入参比较清晰,主要功能是实现从一个已知图像内截取一部分创建一个新的图像。举行代表了起始点和结束点的坐标。
5、创建虚图像
vxCreateVirtualImage(暂未研究,等待补充!)
6、 创建一个纯色的图(统一的值)
图像的入参主要需要设置一个图像格式和像素值;创建函数会根据这两个值,去填充整个图像。
创建对应格式的图像,需要设置对应的像素内的填充值!
typedef union _vx_pixel_value_t {
vx_uint8 RGB[3]; /*!< \brief <tt>\ref VX_DF_IMAGE_RGB</tt> format in the R,G,B order */
vx_uint8 RGBX[4]; /*!< \brief <tt>\ref VX_DF_IMAGE_RGBX</tt> format in the R,G,B,X order */
vx_uint8 YUV[3]; /*!< \brief All YUV formats in the Y,U,V order */
vx_uint8 U8; /*!< \brief <tt>\ref VX_DF_IMAGE_U8</tt> */
vx_uint16 U16; /*!< \brief <tt>\ref VX_DF_IMAGE_U16</tt> */
vx_int16 S16; /*!< \brief <tt>\ref VX_DF_IMAGE_S16</tt> */
vx_uint32 U32; /*!< \brief <tt>\ref VX_DF_IMAGE_U32</tt> */
vx_int32 S32; /*!< \brief <tt>\ref VX_DF_IMAGE_S32</tt> */
vx_uint16 P12; /*!< \brief <tt>\ref TIVX_DF_IMAGE_P12</tt> */
vx_uint16 YUV_12[3]; /*!< \brief <tt>\ref TIVX_DF_IMAGE_NV12_P12</tt> */
vx_uint8 reserved[16];
} vx_pixel_value_t;
vx_pixel_value_t value;
value.YUV_12[0] = 127;
value.YUV_12[1] = 127;
value.YUV_12[2] = 127;
vx_image vxCreateUniformImage(vx_context context, vx_uint32 width, vx_uint32 height, vx_df_image format, const vx_pixel_value_t *value)
二、图像的基本操作
首先要明确一点,创建了图像以后,是属于tiovx的内核空间的,在tiovx内部进行操作,这个是不需要将图像进行映射;如果需要对图像进行修改或者将图像读取出来,需要对图像进行映射,获取到对应的指针,才可以对图像进行操作。
重点哈:对于映射出来的指针地址,可以进行读取,也可以进行修改(覆盖原有的图像,更新图像的内容。)
1、vxMapImagePatch :图像映射获取图像tiovx内核空间的地址
status = vxMapImagePatch(image,
&rect,
0,
&map_id,
&image_addr,
&data_ptr,
(vx_enum)VX_READ_ONLY,
(vx_enum)VX_MEMORY_TYPE_HOST,
(vx_enum)VX_NOGAP_X
);
这里重点讲一下:
image:需要映射的图像
rect:需要映射的图像宽/高
plane_index:映射图像的通道索引(一个图像可能会映射多次,这里的索引需要递增,后面给的实际用例里面可以看到区别。)
map_id:图像映射的唯一标识符,这里是输出值。由调用的函数返回。
data_ptr:图像映射的指针地址,即起始点。
usage:内存空间的属性,只读/只写/读写等三种模式
后面两个参数,目前没有作研究,保持这个参数就可以了,待后面研究后,补充。(暂缺!)
示例:将图像数据读取出来,并写入一个文件内。(分了两个步骤进行映射,这里需要注意的是第三个参数,映射的索引。)
注意:需要及时释放这个图像的映射,否则会造成内存泄漏。
vxUnmapImagePatch(out_img, map_id);
vx_status writeMosaicOutput(char* file_name, vx_image out_img)
{
vx_status status;
status = vxGetStatus((vx_reference)out_img);
if(status == VX_SUCCESS)
{
FILE * fp = fopen(file_name,"wb");
if(fp == NULL)
{
printf("File could not be opened \n");
return (VX_FAILURE);
}
{
vx_rectangle_t rect;
vx_imagepatch_addressing_t image_addr;
vx_map_id map_id;
void * data_ptr;
vx_uint32 img_width;
vx_uint32 img_height;
vx_uint32 num_bytes;
vxQueryImage(out_img, VX_IMAGE_WIDTH, &img_width, sizeof(vx_uint32));
vxQueryImage(out_img, VX_IMAGE_HEIGHT, &img_height, sizeof(vx_uint32));
rect.start_x = 0;
rect.start_y = 0;
rect.end_x = img_width;
rect.end_y = img_height;
status = vxMapImagePatch(out_img,
&rect,
0,
&map_id,
&image_addr,
&data_ptr,
VX_READ_ONLY,
VX_MEMORY_TYPE_HOST,
VX_NOGAP_X);
//Copy Luma
num_bytes = fwrite(data_ptr,1,img_width*img_height, fp);
if(num_bytes != (img_width*img_height))
printf("Luma bytes written = %d, expected = %d", num_bytes, img_width*img_height);
vxUnmapImagePatch(out_img, map_id);
status = vxMapImagePatch(out_img,
&rect,
1,
&map_id,
&image_addr,
&data_ptr,
VX_READ_ONLY,
VX_MEMORY_TYPE_HOST,
VX_NOGAP_X);
//Copy CbCr
num_bytes = fwrite(data_ptr,1,img_width*img_height/2, fp);
if(num_bytes != (img_width*img_height/2))
printf("CbCr bytes written = %d, expected = %d", num_bytes, img_width*img_height/2);
vxUnmapImagePatch(out_img, map_id);
}
fclose(fp);
}
return(status);
}
2、vxCopyImagePatch :从 或者 向 图像对象平面复制矩形块
示例1:给出了一个将一个图像拷贝到另一个图像的示例:首先使用映射,获取到源图像的tiovx内核空间地址;再通过vxCopyImagePatch,拷贝到目标图像内。(目标图像不需要映射)
其中值得注意的地方:image_addr_1 这个参数,这个如果是从图像内拷贝图像,则这个参数是传出参数,由vxMapImagePatch传出,直接给vxCopyImageatch使用;
如果是直接使用vxCopyImagePatch 拷贝内存到图像内,则需要自行设置这个参数。(这里和1.2章节这里比较相似,详细见接下来的示例2)。
vx_imagepatch_addressing_t image_addr;
image_addr.dim_x = width;
image_addr.dim_y = height;
image_addr.stride_x = bpp; //每个像素所占字节数,根据不同格式的图像有所不同
image_addr.stride_y = bpp*width; //这个值需要设置为 bpp*width
image_addr.scale_x = VX_SCALE_UNITY;
image_addr.scale_y = VX_SCALE_UNITY;
image_addr.step_x = 1;
image_addr.step_y = 1;
示例 1:
vx_status ptkdemo_copy_image_to_image(vx_image srcImage, vx_image dstImage)
{
vx_map_id map_id;
vx_rectangle_t rect;
vx_imagepatch_addressing_t image_addr_1;
vx_imagepatch_addressing_t image_addr_2;
vx_df_image img_format;
vx_uint32 img_width;
vx_uint32 img_height;
vx_status vxStatus;
uint8_t *data_ptr_src_1;
uint8_t *data_ptr_src_2;
vxQueryImage(srcImage, VX_IMAGE_FORMAT, &img_format, sizeof(vx_df_image));
vxQueryImage(srcImage, VX_IMAGE_WIDTH, &img_width, sizeof(vx_uint32));
vxQueryImage(srcImage, VX_IMAGE_HEIGHT, &img_height, sizeof(vx_uint32));
rect.start_x = 0;
rect.start_y = 0;
rect.end_x = img_width;
rect.end_y = img_height;
// get source pointer
vxStatus = vxMapImagePatch(srcImage,
&rect,
0,
&map_id,
&image_addr_1,
(void **)&data_ptr_src_1,
VX_READ_ONLY,
VX_MEMORY_TYPE_HOST,
VX_NOGAP_X);
PTK_assert(VX_SUCCESS == vxStatus);
vxUnmapImagePatch(srcImage, map_id);
vxCopyImagePatch(dstImage, &rect, 0, &image_addr_1, data_ptr_src_1, VX_WRITE_ONLY, VX_MEMORY_TYPE_HOST);
// chroma
if (img_format == VX_DF_IMAGE_NV12)
{
// get source pointer
vxStatus = vxMapImagePatch(srcImage,
&rect,
1,
&map_id,
&image_addr_2,
(void **)&data_ptr_src_2,
VX_READ_ONLY,
VX_MEMORY_TYPE_HOST,
VX_NOGAP_X);
PTK_assert(VX_SUCCESS == vxStatus);
vxUnmapImagePatch(srcImage, map_id);
vxCopyImagePatch(dstImage, &rect, 1, &image_addr_2, data_ptr_src_2, VX_WRITE_ONLY, VX_MEMORY_TYPE_HOST);
}
return vxStatus;
}
示例2:image_addr.stride 这个参数是根据不同图像的类型而变动的。
比如YUYV 是422图像,大小是W*H*2;这里设置为2;
如果是RGB ,则大小为:W*H*3,所以设置为3;
特殊情况:如果是YUV420,则需要进行两次映射了;这里需要借助vxMapImagePatch;先获取两次映射的地址,然后再拷贝数据到这个内存里面。(注意那个映射的索引变化,在示例1内)
vx_status app_running_usbCamera(USBCameraObj * usbCameraObj)
{
vx_status status = VX_SUCCESS;
memset(&usbCameraObj->buf, 0, sizeof(usbCameraObj->buf));
usbCameraObj->buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
usbCameraObj->buf.memory = V4L2_MEMORY_MMAP;
ioctl(usbCameraObj->fd, VIDIOC_DQBUF, &usbCameraObj->buf); /* 将已经捕获好视频的内存拉出已捕获视频的队列 */
ioctl(usbCameraObj->fd, VIDIOC_QBUF, &usbCameraObj->buf); /* 将空闲的内存加入可捕获视频的队列 */
vx_rectangle_t rect;
vx_imagepatch_addressing_t image_addr;
vx_uint32 img_width;
vx_uint32 img_height;
vxQueryImage(usbCameraObj->imageInputYUYV, VX_IMAGE_WIDTH, &img_width, sizeof(vx_uint32));
vxQueryImage(usbCameraObj->imageInputYUYV, VX_IMAGE_HEIGHT, &img_height, sizeof(vx_uint32));
//拷贝图像到内核空间
rect.start_x = 0;
rect.start_y = 0;
rect.end_x = img_width;
rect.end_y = img_height;
image_addr.dim_x = img_width;
image_addr.dim_y = img_height;
image_addr.stride_x = 2; /* YUYV */
image_addr.stride_y = img_width * 2;
image_addr.scale_x = VX_SCALE_UNITY;
image_addr.scale_y = VX_SCALE_UNITY;
image_addr.step_x = 1;
image_addr.step_y = 1;
//执行拷贝操作,将USB 摄像头的YUYV图像拷贝到目标图像内
status = vxCopyImagePatch(usbCameraObj->imageInputYUYV, //目标图像
&rect,
0,
&image_addr,
(void *)usbCameraObj->buffers[usbCameraObj->buf.index].start, // 传入需要拷贝的图像
VX_WRITE_ONLY,
VX_MEMORY_TYPE_HOST);
return status;
}
3、图像查询函数
vxQueryImage;函数可以通过设置不同的入参,查询不同的图像属性:
比较简单,不做赘述。(图像格式/宽度/高度等等)
vxQueryImage(dstImage, VX_IMAGE_FORMAT, &img_format, sizeof(vx_df_image));
vxQueryImage(dstImage, VX_IMAGE_WIDTH, &img_width, sizeof(vx_uint32));
vxQueryImage(dstImage, VX_IMAGE_HEIGHT, &img_height, sizeof(vx_uint32));
可供查询的属性,见下列代码:
enum vx_image_attribute_e {
/*! \brief Queries an image for its width. Read-only. Use a <tt>\ref vx_uint32</tt> parameter. */
VX_IMAGE_WIDTH = VX_ATTRIBUTE_BASE(VX_ID_KHRONOS, VX_TYPE_IMAGE) + 0x0,
/*! \brief Queries an image for its height. Read-only. Use a <tt>\ref vx_uint32</tt> parameter. */
VX_IMAGE_HEIGHT = VX_ATTRIBUTE_BASE(VX_ID_KHRONOS, VX_TYPE_IMAGE) + 0x1,
/*! \brief Queries an image for its format. Read-only. Use a <tt>\ref vx_df_image</tt> parameter. */
VX_IMAGE_FORMAT = VX_ATTRIBUTE_BASE(VX_ID_KHRONOS, VX_TYPE_IMAGE) + 0x2,
/*! \brief Queries an image for its number of planes. Read-only. Use a <tt>\ref vx_size</tt> parameter. */
VX_IMAGE_PLANES = VX_ATTRIBUTE_BASE(VX_ID_KHRONOS, VX_TYPE_IMAGE) + 0x3,
/*! \brief Queries an image for its color space (see <tt>\ref vx_color_space_e</tt>). Read-write. Use a <tt>\ref vx_enum</tt> parameter. */
VX_IMAGE_SPACE = VX_ATTRIBUTE_BASE(VX_ID_KHRONOS, VX_TYPE_IMAGE) + 0x4,
/*! \brief Queries an image for its channel range (see <tt>\ref vx_channel_range_e</tt>). Read-only. Use a <tt>\ref vx_enum</tt> parameter. */
VX_IMAGE_RANGE = VX_ATTRIBUTE_BASE(VX_ID_KHRONOS, VX_TYPE_IMAGE) + 0x5,
/*! \brief Queries an image for its total number of bytes. Read-only. Use a <tt>\ref vx_size</tt> parameter. */
VX_IMAGE_SIZE = VX_ATTRIBUTE_BASE(VX_ID_KHRONOS, VX_TYPE_IMAGE) + 0x6,
/*! \brief Queries memory type if created using vxCreateImageFromHandle. If vx_image was not created using
vxCreateImageFromHandle, VX_MEMORY_TYPE_NONE is returned. Use a <tt>\ref vx_memory_type_e</tt> parameter. */
VX_IMAGE_MEMORY_TYPE = VX_ATTRIBUTE_BASE(VX_ID_KHRONOS, VX_TYPE_IMAGE) + 0x7,
};
4、释放/销毁图像
vxReleaseImage;即释放已有的图像。
其他操作,后续会不停的更新。
水平有限,欢迎指正!
【声明】
【欢迎转载转发,请注明出处。原创比较辛苦,请尊重原创,祝大家学习愉快!】
【博主专注嵌入式开发,具有多年嵌入式软、硬件开发经验,欢迎大家学习交流!】
【如有嵌入式相关项目需求,欢迎私信】