由於 cnblogs 不支持科學公式,完整內容請移步原文鏈接
原文地址:從 AVFrame 中取出幀(YUV)保存為 Mat 格式
從 AVFrame 中取出幀(YUV)保存為 Mat 格式
本文檔針對 YUV420p 編碼進行記錄
AVFrame 結構體解析
這里列出一些重點變量
|
|
備注 |
---|---|---|
uint8_t *data[AV_NUM_DATA_POINTERS] | 解碼后原始數據 | |
int linesize[AV_NUM_DATA_POINTERS] | data中“一行”數據的大小 | 一般大於圖像的寬 |
int width, height | 視頻幀寬和高 | |
int nb_samples | 一個 AVFrame 中包含多少個音頻幀 | 一個 AVFrame 中只包含一個視頻幀,但可能包含多個音頻幀 |
int format | 解碼后原始數據類型 | YUV420, YUV422, RGB24... |
int key_frame | 是否是關鍵幀 | |
enum AVPictureType pict_type | 幀類型 | I, B, P... |
AVRational sample_aspect_ratio | 寬高比 | 16:9, 4:3... |
int64_t pts | 顯示時間戳 | |
int coded_picture_number | 編碼幀序號 | |
int display_picture_number | 幀序號 | |
int interlaced_frame | 是否是隔行掃描 |
YUV 轉 RGB 原理
YUV 圖像有兩種編碼格式:
緊縮格式(packed formats): Y、U、V 三通道像素值依次排列,即 Y0 U0 V0 Y1 U1 V1 ...
平面格式(planar formats): 先排列 Y 的所有像素值,再排列 U,最后排列 V
YUV420p 中使用平面格式,水平 2:1 取樣,垂直 2:1 采樣,即每 4 個 Y 分量對應一個 U、V 分量
綜上,YUV 與 RGB 的轉換公式如下
R = Y + 1.4075 * (V - 128)
G = Y - 0.3455 * (U - 128) - (0.7169 * (V - 128))
B = Y + 1.7790 * (U - 128)
Y = R * .299000 + G * .587000 + B * .114000
U = R * -.168736 + G * -.331264 + B * .500000 + 128
V = R * .500000 + G * -.418688 + B * -.081312 + 128
下面的公式中各個符號都帶了 '
,表示該符號在原值基礎上進行了伽馬校正,伽馬校正有助於彌補在抗鋸齒的過程中,線性分配伽馬值所帶來的細節損失,使圖像細節更加豐富。在沒有采用伽馬校正的情況下,暗部細節不容易顯現出來,而采用了這一圖像增強技術以后,圖像的層次更加清晰
Y' = 0.257*R' + 0.504*G' + 0.098*B' + 16
Cb' = -0.148*R' - 0.291*G' + 0.439*B' + 128
Cr' = 0.439*R' - 0.368*G' - 0.071*B' + 128
R' = 1.164*(Y’-16) + 1.596*(Cr'-128)
G' = 1.164*(Y’-16) - 0.813*(Cr'-128) - 0.392*(Cb'-128)
B' = 1.164*(Y’-16) + 2.017*(Cb'-128)
實現代碼
在 AVFrame2Img 中輸入需要轉換格式的 AVFrame,返回 Mat
Mat AVFrame2Img(AVFrame *frame) {
// 獲取幀寬、高及通道數量
int frame_height = frame->height;
int frame_width = frame->width;
int frame_channels = 3;
// 初始化 Mat
Mat img = Mat::zeros(frame_height, frame_width, CV_8UC3);
// 初始化存放 YUV 編碼圖片的 buffer 內存空間
uchar* yuv_buffer = (uchar*)malloc(frame_height * frame_width * sizeof(uchar)*frame_channels);
// 獲取圖片原始數據
// Y
for (int i = 0; i < frame_height; i++) {
memcpy(yuv_buffer + frame_width * i,
frame->data[0] + frame->linesize[0] * i,
frame_width);
}
// U
for (int j = 0; j < frame_height / 2; j++) {
memcpy(yuv_buffer + frame_width * frame_height + frame_width / 2 * j,
frame->data[1] + frame->linesize[1] * j,
frame_width / 2);
}
// V
for (int k = 0; k < frame_height / 2; k++) {
memcpy(yuv_buffer + frame_width * frame_height + frame_width / 2 * (frame_height / 2) + frame_width / 2 * k,
frame->data[2] + frame->linesize[2] * k,
frame_width / 2);
}
// 轉換為 RGB 編碼
YUV420P2RGB32(yuv_buffer, img.data, frame_width, frame_height);
// 釋放 buffer 內存空間
free(yuv_buffer);
return img;
}
void YUV420P2RGB32(const uchar *yuv_buffer_in, const uchar *rgb_buffer_out, int width, int height) {
uchar *yuv_buffer = (uchar *)yuv_buffer_in;
uchar *rgb_buffer = (uchar *)rgb_buffer_out;
int channels = 3;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int index_Y = y * width + x;
int index_U = width * height + y / 2 * width / 2 + x / 2;
int index_V = width * height + width * height / 4 + y / 2 * width / 2 + x / 2;
// 取出 YUV
uchar Y = yuv_buffer[index_Y];
uchar U = yuv_buffer[index_U];
uchar V = yuv_buffer[index_V];
// YCbCr420
int R = Y + 1.402 * (V - 128);
int G = Y - 0.34413 * (U - 128) - 0.71414*(V - 128);
int B = Y + 1.772*(U - 128);
// Y'Cb'Cr'420
// int R = 1.164 * (Y - 16) + 1.596 * (V - 128);
// int G = 1.164 * (Y - 16) - 0.813 * (V - 128) - 0.392 * (U - 128);
// int B = 1.164 * (Y - 16) + 2.017 * (U - 128);
// 確保取值范圍在 0 - 255 中
R = (R < 0) ? 0 : R;
G = (G < 0) ? 0 : G;
B = (B < 0) ? 0 : B;
R = (R > 255) ? 255 : R;
G = (G > 255) ? 255 : G;
B = (B > 255) ? 255 : B;
rgb_buffer[(y*width + x)*channels + 2] = uchar(R);
rgb_buffer[(y*width + x)*channels + 1] = uchar(G);
rgb_buffer[(y*width + x)*channels + 0] = uchar(B);
}
}
}