参考:Xavier 下GMSL相机ROS驱动发布CompressedImage消息(基于NVJPG硬件编码)
对图像进行压缩的方法有很多,比如直接利用opencv的imwrite函数就可以直接对保存的图像进行压缩
std::vector<int> params;
params.push_back(cv::IMWRITE_JPEG_QUALITY);
params.push_back(10);
//params.push_back(50);
//params.push_back(100);
cv::imwrite("C:\\test.jpg", image, params);
但大多数都是利用cpu进行压缩,在使用Nvidia Jetson AGX Orin进行开发时,我们可以利用其自带的NVJPG对图像进行压缩
首先,我们要学习如何使用NVJPG进行图像压缩,我这边参考了一个博主的分享,除此之外我们可以到nvidia官方去找教程,参考https://docs.nvidia.com/jetson/l4t-multimedia/中的Multimedia APIs,我主要阅读了一下05_jpeg_encode这部分内容,要利用NVJPG进行图像压缩我们可以调用以下两个函数实现
根据我参考的这位博主我也选择了利用encodeFromBuffer这个函数来实现,我们点进去可以看到这个函数仅支持对输入图像格式为yuv420的图像进行压缩
接着,我们去系统下面找/usr/src/jetson_multimedia_api/samples/05_jpeg_encode,里面存放着示例代码,如果找不到jetson_multimedia_api,说明你的系统在刷机的时候可能没勾上这个模块的选项,我们可以在终端输入命令sudo apt-get install nvidia-l4t-jetson-multimedia-api
进行安装
然后,我们对它的源码进行分析,利用encodeFromBuffer实现步骤相对比较简单,首先创建一个NvBuffer并分配内存,接着读取图像数据到NvBuffer,然后调用encodeFromBuffer进行图像压缩,最后保存成文件
/**
* Case 1:
* Read YUV420 image from file system to CPU buffer, encode by
* encodeFromBuffer() then write to file system.
*/
if (!ctx.use_fd)
{
NvBuffer buffer(V4L2_PIX_FMT_YUV420M, ctx.in_width,
ctx.in_height, 0);
buffer.allocateMemory();
ret = read_video_frame(ctx.in_file, buffer);
TEST_ERROR(ret < 0, "Could not read a complete frame from file",
cleanup);
for (int i = 0; i < iterator_num; ++i)
{
ret = ctx.jpegenc->encodeFromBuffer(buffer, JCS_YCbCr, &out_buf,
out_buf_size, ctx.quality);
TEST_ERROR(ret < 0, "Error while encoding from buffer", cleanup);
}
ctx.out_file->write((char *) out_buf, out_buf_size);
goto cleanup;
}
其中ctx是一个配置参数的结构体,长这样
typedef struct
{
NvJPEGEncoder *jpegenc;
char *in_file_path;
std::ifstream * in_file;
uint32_t in_width;
uint32_t in_height;
uint32_t in_pixfmt;
char *out_file_path;
std::ofstream * out_file;
bool got_error;
bool use_fd;
bool perf;
uint32_t crop_left;
uint32_t crop_top;
uint32_t crop_width;
uint32_t crop_height;
int stress_test;
bool scaled_encode;
uint32_t scale_width;
uint32_t scale_height;
int quality;
} context_t;
而read_video_frame这个函数就是读取图像数据到NvBuffer的函数,我们可以参考源码自行实现
int read_video_frame(std::ifstream * stream, NvBuffer & buffer)
{
uint32_t i, j;
char *data;
for (i = 0; i < buffer.n_planes; i++)
{
NvBuffer::NvBufferPlane &plane = buffer.planes[i];
std::streamsize bytes_to_read =
plane.fmt.bytesperpixel * plane.fmt.width;
data = (char *) plane.data;
plane.bytesused = 0;
for (j = 0; j < plane.fmt.height; j++)
{
stream->read(data, bytes_to_read);
if (stream->gcount() < bytes_to_read)
return -1;
data += plane.fmt.stride;
}
plane.bytesused = plane.fmt.stride * plane.fmt.height;
}
return 0;
}
首先,准备环境,需要将一些源文件和头文件加入到我们自己的项目当中,将/usr/src/jetson_multimedia_api/samples目录下的common文件夹拷贝到自己的项目中,里面存放的是一些要用到的源文件,再将/usr/src/jetson_multimedia_api目录下的include文件夹拷贝到自己的项目中,并在CMakeLists.txt中编写相应的语句,这里提供一份CMakeLists.txt作为参考,注意:NvUtils.cpp、NvBuffer.cpp、NvElement.cpp、NvLogging.cpp、NvJpegEncoder.cpp、NvElementProfiler.cpp需要一起编译!!!对应的头文件路径也要添加上(特别注意要包含${PROJECT_SOURCE_DIR}/include/nv_include/libjpeg-8b 这个路径,是对应的libjpeg.h,否则可能报类似这种错:JPEG parameter struct mismatch: library thinks size is 664 caller expects 632)
project(nvjpg_test)
cmake_minimum_required(VERSION 3.5)
set(CMAKE_BUILD_TYPE Release)
MESSAGE("Build type: " ${CMAKE_BUILD_TYPE})
set(CMAKE_C_FLAGS_DEBUG "-O2 -g")
set(CMAKE_CXX_FLAGS_DEBUG "-O2 -g")
set(CMAKE_C_FLAGS_RELEASE "-O3")
set(CMAKE_CXX_FLAGS_RELEASE "-O3")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
#set(Boost_USE_STATIC_LIBS ON)
find_package(Boost REQUIRED COMPONENTS system filesystem)
find_package(OpenCV REQUIRED)
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR})
set(LIBRARY_OUTPUT_PATH /data/UGV_Program_jpeg/lib)
include_directories(
${PROJECT_SOURCE_DIR}/include
${PROJECT_SOURCE_DIR}/include/libyuv
${PROJECT_SOURCE_DIR}/include/nv_include
${PROJECT_SOURCE_DIR}/include/nv_include/libjpeg-8b
${OpenCV_INCLUDE_DIRS}
${CUDA_INCLUDE_DIRS}
)
link_directories(
${OpenCV_LIBRARY_DIRS}
${LIBRARY_OUTPUT_PATH}
/usr/local/lib
)
set(NVJPG_SRC
src/common/classes/NvUtils.cpp
src/common/classes/NvBuffer.cpp
src/common/classes/NvElement.cpp
src/common/classes/NvLogging.cpp
src/common/classes/NvJpegEncoder.cpp
src/common/classes/NvElementProfiler.cpp
)
add_executable(nvjpg_test
${NVJPG_SRC}
src/main.cpp
)
target_link_libraries(nvjpg_test
${PCL_LIBRARIES}
${Boost_LIBRARIES}
${OpenCV_LIBS}
pthread
protobuf
boost_serialization
libcamerav4l2.a
libpublic.a
/usr/local/lib/libyuv.a
/usr/lib/aarch64-linux-gnu/tegra/libnvjpeg.so
/usr/lib/aarch64-linux-gnu/tegra/libnvbufsurface.so
)
接着,开始编写我们的代码,还记得上面提到的实现步骤吗?有一步很关键的步骤上面没有提到,那就是要把输入encodeFromBuffer的图像数据转成yuv420格式的图像数据,这也是最坑的一步,为什么说它坑呢?听我给大家细细道来,
一开始,由于我的摄像头采集的数据格式是yuv422的,我在网上找了很多资料也没找到yuv422转yuv420的代码,于是我便根据自己对图像格式的了解自己编写函数实现YUV422toyuv420_i420实现,最终功能能够实现,但是技术有限,自己编写的函数效率太低了,于是我便想用opencv库去实现,但是opencv无法将yuv422直接转成yuv420,于是我只好先将yuv422转成bgr再将bgr转成yuv420,但是此时又有一个问题,就是输出的图像值域被截取了,理论上我的图像值域应该是[0-255],但是输出的图像值域却是[16-235],这里有两种可能,一是在yuv422转yuv420的时候被截取了,二是在调用encodeFromBuffer对图像进行压缩的时候被截取了,我通过查阅大量资料后发现众说纷纭,说这两种可能都有,没办法,我只好自己测试,最终发现是在yuv422转yuv420的时候被截取了值域,我只好尝试用其他方法转换图像格式了,最终在网上我找到了一个利用yuvlib开源库的RGB24ToJ420函数将rgb图像转成yuv420的方法可以输出完整的值域,但奇怪的是当我输入rgb图像时出来的结果是红色和蓝色正好对调了,反而我输入bgr图像时,输出的图像时正常的,难道yuvlib库的函数命名弄错了?不应该把,总之不管怎么样,这样最终是实现了我想要的效果,最后贴上源码
#include
#include
#include
#include
#include
#include
#include
#include
#include
typedef struct
{
NvJPEGEncoder *jpegenc;
char *in_file_path;
std::ifstream * in_file;
uint32_t in_width;
uint32_t in_height;
uint32_t in_pixfmt;
char *out_file_path;
std::ofstream * out_file;
bool got_error;
bool use_fd;
bool perf;
uint32_t crop_left;
uint32_t crop_top;
uint32_t crop_width;
uint32_t crop_height;
uint32_t scale_width;
uint32_t scale_height;
int stress_test;
bool scaled_encode;
int quality;
} context_t;
bool convertRGBtoYUV(const cv::Mat& rgb, NvBuffer& nvbuf)
{
NvBuffer::NvBufferPlane &planeY = nvbuf.planes[0];
NvBuffer::NvBufferPlane &planeU = nvbuf.planes[1];
NvBuffer::NvBufferPlane &planeV = nvbuf.planes[2];
const int strideY = planeY.fmt.stride;
const int strideU = planeU.fmt.stride;
const int strideV = planeV.fmt.stride;
uint8_t* dst_y = planeY.data;
uint8_t* dst_u = planeU.data;
uint8_t* dst_v = planeV.data;
return libyuv::RGB24ToJ420(rgb.data, rgb.step, dst_y, strideY, dst_u, strideU, dst_v, strideV, rgb.cols, rgb.rows);
}
int jpeg_encode_Ex(const cv::Mat& m_pYuv, unsigned char* out_buf, unsigned long &len, uint32_t width, uint32_t height, int quality)
{
context_t ctx;
memset(&ctx, 0, sizeof(context_t));
ctx.perf = false;
ctx.use_fd = false;
ctx.in_pixfmt = V4L2_PIX_FMT_YUV420M;
ctx.stress_test = 1;
ctx.quality = quality;
ctx.in_width = width;
ctx.in_height = height;
ctx.jpegenc = NvJPEGEncoder::createJPEGEncoder("jpenenc");
NvBuffer nvbuffer(V4L2_PIX_FMT_YUV420M, ctx.in_width, ctx.in_height, 0);
nvbuffer.allocateMemory();
int ret;
convertRGBtoYUV(m_pYuv,nvbuffer);
ret = ctx.jpegenc->encodeFromBuffer(nvbuffer, JCS_YCbCr, &out_buf, len, ctx.quality);
// std::cout << "len:" << len << std::endl;
nvbuffer.deallocateMemory();
delete ctx.jpegenc;
return ret;
}
int main(int argc, char *argv[])
{
cv::Mat img = cv::imread("/data/1.png", 1);
// cv::cvtColor(img, img, cv::COLOR_BGRA2BGR);
int width = img.cols, height = img.rows;
unsigned long out_buf_size;
out_buf_size = width * height * 3 / 2;
unsigned char *out_buf;
out_buf = new unsigned char[out_buf_size];
jpeg_encode_Ex(img, out_buf, out_buf_size, width, height, 95);
std::ofstream * out_file;
out_file = new std::ofstream("/data/bin/95.jpg");
out_file->write((char *) out_buf, out_buf_size);
out_file->close();
delete[] out_buf;
std::cout << "NVJPG OPEN" << std::endl;
}
demo在这里
gitcode
csdn资源下载