目标是将NV12转换为BGR24图像,更确切地说是将图像块转换为(x:0,y:0,w:220,h:220)。
问题是转换后的补丁右侧的未定义像素列,如下所示:
问题是为什么会发生(即使补丁的坐标和尺寸具有偶数值)? (有趣的是,对于一个奇数宽度值,这个问题不存在)
该修补程序具有以下边界框:(x:0,y:0,w:220,h:220)。
该行为应可与任何图像重现。可以使用ppm conversion page完成转换。
以下代码从bgr24映像创建nv12映像,然后将nv12修补程序转换回bgr24修补程序。 如果一切正常,则输出应该与源图像相同。
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
void readPPM(const char* filename, uint8_t** bgrData, int* stride, int* w, int* h)
{
FILE* fp = fopen(filename, "rb");
fscanf(fp, "%*s\n"); //skip format check
fscanf(fp, "%d %d\n", w, h);
fscanf(fp, "%*d\n"); //skip max value check
*stride = *w * 3;
*bgrData = av_malloc(*h * *stride);
for (int r = 0; r < *h; r++)
{
uint8_t* rowData = *bgrData + r * *stride;
for (int c = 0; c < *w; c++)
{
//rgb -> bgr
fread(&rowData[2], 1, 1, fp);
fread(&rowData[1], 1, 1, fp);
fread(&rowData[0], 1, 1, fp);
rowData += 3;
}
}
fclose(fp);
}
void writePPM(const char* filename, uint8_t* bgrData, int stride, int w, int h)
{
FILE* fp = fopen(filename, "wb");
fprintf(fp, "P6\n");
fprintf(fp, "%d %d\n", w, h);
fprintf(fp, "%d\n", 255);
for (int r = 0; r < h; r++)
{
uint8_t* rowData = bgrData + r * stride;
for (int c = 0; c < w; c++)
{
//bgr -> rgb
fwrite(&rowData[2], 1, 1, fp);
fwrite(&rowData[1], 1, 1, fp);
fwrite(&rowData[0], 1, 1, fp);
rowData += 3;
}
}
fclose(fp);
}
void bgrToNV12(uint8_t* srcData[4], int srcStride[4],
uint8_t* tgtData[4], int tgtStride[4],
int w, int h)
{
struct SwsContext* context = sws_getContext(w, h, AV_PIX_FMT_BGR24,
w, h, AV_PIX_FMT_NV12, SWS_POINT, NULL, NULL, NULL);
{
sws_scale(context,
srcData, srcStride, 0, h,
tgtData, tgtStride);
}
sws_freeContext(context);
}
void nv12ToBgr(uint8_t* srcData[4], int srcStride[4],
uint8_t* tgtData[4], int tgtStride[4],
int w, int h)
{
struct SwsContext* context = sws_getContext(w, h, AV_PIX_FMT_NV12,
w, h, AV_PIX_FMT_BGR24, SWS_POINT, NULL, NULL, NULL);
{
sws_scale(context,
srcData, srcStride, 0, h,
tgtData, tgtStride);
}
sws_freeContext(context);
}
int main()
{
//load BGR image
uint8_t* bgrData[4]; int bgrStride[4]; int bgrW, bgrH;
readPPM("sample.ppm", &bgrData[0], &bgrStride[0], &bgrW, &bgrH);
//create NV12 image from the BGR image
uint8_t* nv12Data[4]; int nv12Stride[4];
av_image_alloc(nv12Data, nv12Stride, bgrW, bgrH, AV_PIX_FMT_NV12, 16);
bgrToNV12(bgrData, bgrStride, nv12Data, nv12Stride, bgrW, bgrH);
//convert nv12 patch to bgr patch
nv12ToBgr(nv12Data, nv12Stride, bgrData, bgrStride, 220, 220); //invalid result (random column stripe)
//nv12ToBgr(nv12Data, nv12Stride, bgrData, bgrStride, 221, 220); //valid result
//save bgr image (should be exactly as original BGR image)
writePPM("sample-out.ppm", bgrData[0], bgrStride[0], bgrW, bgrH);
//cleanup
av_freep(bgrData);
av_freep(nv12Data);
return 0;
}
答案 0 :(得分:3)
sws_scale同时进行颜色转换和缩放。
大多数使用的算法需要在目标像素的计算中包括相邻像素。当然,如果图像尺寸不是x的倍数,可能会导致边缘出现问题。 x取决于使用的算法。
如果您在此处将图像尺寸设置为8的倍数(8的下一个倍数= 224),那么它将没有伪影。
nv12ToBgr(nv12Data, nv12Stride, bgrData, bgrStride, 224, 224);
演示
使用左侧的图像尺寸220 x 220,在转换后的色块的右侧边缘上出现伪像。
如果选择224 x 224不会给出伪像,请在屏幕快照中比较两个步骤,查看正确的图像。
理论上要求的最小对齐方式
让我们看一下YVU420格式:
确定每个像素的亮度值。从2x2像素块中计算出分为Cb和Cr的颜色信息。因此,最小图像尺寸为2 x 2图像块,结果为6个字节(即每个字节12个像素= 12 * 4 = 48位= 6个字节),请参见此处的图形:
因此,最低技术要求是图像的宽度和高度均匀。
您已经定义了SWS_POINT标志用于缩放,即,使用了最近的邻居方法。因此,理论上,对于每个输出像素,将确定并使用最接近的输入像素,这不会引起任何对齐限制。
性能
但是,算法实际实现的一个重要方面通常是性能。在这种情况下,例如可以一次处理几个相邻的像素。同样不要忘记硬件加速操作的可能性。
替代解决方案
如果出于某些原因需要坚持使用220x220格式,则可以使用SWS_BITEXACT标志。
它可以:
启用精确位输出。
请参阅https://ffmpeg.org/ffmpeg-scaler.html#scaler_005foptions
因此在nv12ToBgr中,您将使用类似以下内容:
struct SwsContext* context = sws_getContext(w, h, AV_PIX_FMT_NV12,
w, h, AV_PIX_FMT_BGR24, SWS_POINT | SWS_BITEXACT, NULL, NULL, NULL);
这也不给出任何伪像。如果您必须转换很多帧,我会看看它的性能。