昇腾 - AscendCL C++应用开发 图像文件的解码时硬件对图像的宽度和高度的处理方式
flyfish
假如是这样的
输入图片格式(YUV分量比例)
jpeg(420)
输出图片格式
YUV420SP NV12 8bit
输出图片宽、高对齐要求
宽2对齐
高2对齐
输出图片宽Stride、高Stride、内存大小要求
宽Stride为宽64对齐后的值。
高Stride为高16对齐后的值。
内存大小(单位Byte)≥ 宽Stride * 高Stride * 3/2
支持的硬件
Atlas 推理系列产品(Ascend 310P处理器)
Atlas 200/500 A2推理产品
Atlas A2训练系列产品
举例子
将ImageData 转cv::Mat格式
JPEGD(JPEG Decoder)对JPG图像文件的解码时,硬件对图像的宽度和高度存在对齐的要求
cv::Mat ImageDataToMat(const ImageData &imgData)
{
// 确保图像格式是 YUV420SP
if (imgData.format != PIXEL_FORMAT_YUV_SEMIPLANAR_420)
{
throw std::invalid_argument("Unsupported image format");
}
std::cout << imgData.width << std::endl;
std::cout << imgData.height << std::endl;
// 创建 YUV420SP 的 cv::Mat
cv::Mat yuv420spMat(imgData.height + imgData.height / 2, imgData.width, CV_8UC1, imgData.data.get());
// 将 YUV420SP 转换为 BGR
cv::Mat bgrMat;
cv::cvtColor(yuv420spMat, bgrMat, cv::COLOR_YUV2BGR_NV12);
return bgrMat;
}
使用
string imgPath = "../data/1.jpg";
ImageProc imageProcess;
ImageData frame = imageProcess.Read(imgPath);
cv::Mat bgrMat = ImageDataToMat(frame);
cv::imwrite("../data/2.jpg", bgrMat);
分析代码
ImageData 结构体
struct ImageData {
std::shared_ptr<uint8_t> data = nullptr;
uint32_t size = 0;
uint32_t width = 0;
uint32_t height = 0;
uint32_t alignWidth = 0;
uint32_t alignHeight = 0;
acldvppPixelFormat format = PIXEL_FORMAT_YUV_SEMIPLANAR_420;
ImageData() {}
ImageData(std::shared_ptr<uint8_t> buf, uint32_t bufSize,
uint32_t x, uint32_t y, acldvppPixelFormat fmt) : data(buf), size(bufSize),
width(x), height(y), format(fmt) {}
};
ImageProc的读取图像文件
可以是jpg或者png,Read函数中调用了ReadBinFile函数
ImageData ImageProc::Read(const string& filePath, acldvppPixelFormat imgFormat)
{
// read file to host
void* hostDataBuf = nullptr;
uint32_t hostDataSize = 0;
ReadBinFile(filePath, hostDataBuf, hostDataSize);
int splitPos = filePath.find_last_of('/');
string fileName = filePath.substr(splitPos + 1);
// get file type
int pos = fileName.find('.');
string fileType = fileName.substr(pos + 1);
if (fileType=="jpg" || fileType=="jpeg" || fileType=="JPG" || fileType=="JPEG") {
return JpegD(hostDataBuf, hostDataSize, imgFormat);
} else if (fileType=="png") {
return PngD(hostDataBuf, hostDataSize, imgFormat);
} else {
LOG_PRINT("[ERROR] Read file type not supported.");
ImageData dst;
return dst;
}
}
bool ReadBinFile(const string& fileName, void*& data, uint32_t& size)
{
struct stat sBuf;
int fileStatus = stat(fileName.data(), &sBuf);
CHECK_RET(fileStatus == 0, LOG_PRINT("[ERROR] Failed to get input file."); return false);
CHECK_RET(S_ISREG(sBuf.st_mode) != 0, LOG_PRINT("[ERROR] %s is not a file, please enter a file.", fileName.c_str()); return false);
std::ifstream binFile(fileName, std::ifstream::binary);
CHECK_RET(binFile.is_open() == true, LOG_PRINT("[ERROR] Open file %s failed", fileName.c_str()); return false);
binFile.seekg(0, binFile.end);
uint32_t binFileBufferLen = binFile.tellg();
CHECK_RET(binFileBufferLen != 0, LOG_PRINT("[ERROR] Binfile is empty, filename is %s", fileName.c_str()); return false);
binFile.seekg(0, binFile.beg);
uint8_t* binFileBufferData = new(std::nothrow) uint8_t[binFileBufferLen];
CHECK_RET(binFileBufferData != nullptr, LOG_PRINT("[ERROR] Malloc binFileBufferData failed"); return false);
binFile.read((char *)binFileBufferData, binFileBufferLen);
binFile.close();
data = binFileBufferData;
size = binFileBufferLen;
return true;
}
解释 YUV420SP 的 cv::Mat
变量 cv::Mat yuv420spMat(imgData.height + imgData.height / 2, imgData.width, CV_8UC1, imgData.data.get());
YUV420SP NV12 8bit 表示一种图像数据格式,其中:
图像数据使用YUV颜色空间。
色度抽样比是4:2:0(每4个亮度样本对应2个色度样本)。
数据以半平面的方式存储,Y分量单独存储,而UV交替存储在一起。
每个分量的数据用8位表示。
详细点说就是
YUV颜色空间
- Y 代表亮度(Luminance),即图像的明暗信息。
- U 和 V 代表色度(Chrominance),分别存储蓝色差(Cb)和红色差(Cr)信息。U和V共同决定了图像的颜色。
4:2:0 表示色度抽样的比例。它表明色度(U和V)的分辨率是亮度(Y)的一半,也就是说,每4个Y样本(即亮度值)只存储2个U和2个V样本。这样可以减少存储空间,同时保持相对较好的图像质量。
- 4 :代表每4个像素的亮度样本。
- 2 :代表在水平方向上每2个像素共享一个色度样本。
- 0 :代表在垂直方向上每2行像素共享一个色度样本。
SP (Semi-Planar) 表示Y、U、V数据的存储方式。在YUV420SP中,Y分量的数据存储在一块连续的内存区域中,而U和V分量的数据交替存储在另一块连续的内存区域中。即,首先是Y数据,然后紧接着是交替存储的UV数据(UVUVUV…)。
NV12 是YUV420SP格式的一种具体形式。它规定在存储时,Y分量紧跟着UV分量存储,其中U在V之前排列(即UVUVUV…的顺序)。因此,NV12格式的图像数据首先是一个单独的Y平面,后面跟着交错的UV平面。
8bit 表示每个像素的亮度(Y)和色度(U和V)分量用8位表示。具体来说,Y、U、V的每个分量都有256个可能的值(从0到255),分别表示不同的亮度或颜色信息。
图像数据的存储结构
在 NV12 格式下,图像的存储结构如下:
Y 平面:首先存储所有像素的亮度(Y)信息,占据了整个图像的高度和宽度的内存空间。
UV 平面:紧接在 Y 平面之后,存储交替的 U 和 V 色度信息。因为色度分辨率是亮度的四分之一(在4:2:0抽样中),所以 UV 平面高度是原始图像高度的一半。
计算方式
硬件对图像的宽度和高度存在对齐的要求,因此引入了宽Stride和高Stride两个概念。宽Stride用于表示对齐后的宽度,高Stride用于表示对齐后的高度。假设宽Stride按照宽度的64倍对齐,高Stride按照高度的16倍对齐。那么,内存大小(单位Byte)需要满足以下公式:内存大小 ≥ 宽Stride * 高Stride * 3/2。
例如,对于一个500 × 300(宽度 × 高度)的图像,对齐后的宽高分别为512 × 304。具体计算如下:
-
500 / 64 = 7.8125,由于需要向上对齐到最近的64的倍数,所以结果为64 × 8 = 512。
-
300 / 16 = 18.75,由于需要向上对齐到最近的16的倍数,所以结果为16 × 19 = 304。
同样,对于一个1920 × 1080(宽度 × 高度)的图像,对齐后的宽高分别为1920 × 1088。
-
宽度对齐:1920 / 64 = 30,直接为64 × 30 = 1920,因为1920已经是64的倍数。
-
高度对齐:1080 / 16 = 67.5,需要向上取整到16的倍数,所以结果为16 × 68 = 1088。
这样可视化结果会存在高8像素的绿边
原图
保存后 (如果按照1920 * 1080 直接存储,不进行任何处理, 会有一个8像素的绿边)
关于功能及约束说明的参考