NV21图片格式深入解析与代码实战-RGB转NV21与画框-CSDN博客 (2024)

1.NV21格式图片解析

NV21图像格式属于 YUV颜色空间中的YUV420SP格式
每四个Y分量共用一组U分量和V分量,Y连续排序,U与V交叉排序

NV21图片格式深入解析与代码实战-RGB转NV21与画框-CSDN博客 (1)

重点总结

  • uv交错模式
  • 4Y共用一组uv(2个)
  • 大小:UV= Y 的一半

排列方式如下

Y Y   Y Y   Y Y   Y Y
Y Y   Y Y   Y Y   Y Y

Y Y   Y Y   Y Y   Y Y
Y Y   Y Y   Y Y   Y Y

V U  V U  V U  V U

V U  V U  V U  V U

2.RGB图片转NV21—逐像素

基本公式

  • yuv --> rgb

    R = (298*Y + 411 * V - 57344)>>8G = (298*Y - 101* U - 211* V+ 34739)>>8B = (298*Y + 519* U- 71117)>>8
  • rgb --> yuv

    Y= ( 66*R + 129*G + 25*B)>>8 + 16 U= (-38*R - 74*G + 112*B)>>8 +128V= (112*R - 94*G - 18*B)>>8 + 128

c++代码

#include <iostream>#include <opencv2/core/core.hpp>#include <opencv2/highgui/highgui.hpp>#include <opencv2/imgproc/imgproc.hpp>#include <vector>#include <fstream>#include <string> void RGB2NV21(){const char *filename = "yuv.yuv";cv::Mat Img = cv::imread("RGB.jpg");FILE *fp = fopen(filename,"wb");if (Img.empty()){std::cout << "empty!check your image";return;}int cols = Img.cols;int rows = Img.rows; int Yindex = 0;int UVindex = rows * cols; unsigned char* yuvbuff = new unsigned char[1.5 * rows * cols]; cv::Mat NV21(rows+rows/2, cols, CV_8UC1);cv::Mat OpencvYUV;cv::Mat OpencvImg;cv::cvtColor(Img, OpencvYUV, CV_BGR2YUV_YV12);int UVRow{ 0 };for (int i=0;i<rows;i++){for (int j=0;j<cols;j++){uchar* YPointer = NV21.ptr<uchar>(i); int B = Img.at<cv::Vec3b>(i, j)[0];int G = Img.at<cv::Vec3b>(i, j)[1];int R = Img.at<cv::Vec3b>(i, j)[2]; //计算Y的值int Y = (77 * R + 150 * G + 29 * B) >> 8;YPointer[j] = Y;yuvbuff[Yindex++] = (Y < 0) ? 0 : ((Y > 255) ? 255 : Y);uchar* UVPointer = NV21.ptr<uchar>(rows+i/2);//计算U、V的值,进行2x2的采样if (i%2==0&&(j)%2==0){int U = ((-44 * R - 87 * G + 131 * B) >> 8) + 128;int V = ((131 * R - 110 * G - 21 * B) >> 8) + 128;UVPointer[j] = V;UVPointer[j+1] = U;yuvbuff[UVindex++] = (V < 0) ? 0 : ((V > 255) ? 255 : V);yuvbuff[UVindex++] = (U < 0) ? 0 : ((U > 255) ? 255 : U);}}}for (int i=0;i< 1.5 * rows * cols;i++){fwrite(&yuvbuff[i], 1, 1, fp);}fclose(fp);std::cout << "write to file ok!" << std::endl;std::cout << "srcImg: " << "rows:" << Img.rows << "cols:" << Img.cols << std::endl;std::cout << "NV21: " << "rows:" << NV21.rows << "cols:" << NV21.cols << std::endl;std::cout << "opencv_YUV: " << "rows:" << OpencvYUV.rows << "cols:" << OpencvYUV.cols << std::endl; cv::imshow("src", Img);//原图cv::imshow("YUV", NV21);//转换后的图片cv::imshow("opencv_YUV", OpencvYUV); //opencv转换后的图片cv::imwrite("NV21.jpg", NV21);cv::waitKey(30000);} int main(){RGB2NV21();return 0;}

3.NV21图像逐个像素画框

结果展示

NV21图片格式深入解析与代码实战-RGB转NV21与画框-CSDN博客 (2)

整体流程

将原始图像保存到一维数组

2个水平画线

2个竖直画线

主要难点就是找2个uv起始坐标的迭代公式

水平画线

y :外循环更新下一行,内循环改变一行值

uv:外循环更新下一行,内循环改变一行值

因为交错模式,uv内循环更新要隔着2个

因为4个y对应一组uv,uv外循环次数比y外循环次数少一半

Y分量起始位置更新公式

//计算Y分量在nv21Data数组中的起始位置:Y的数据+绘制横坐标位置yStartIndex = (y+i_r) * imageWidth + x;
yStartIndex:Y分量在nv21Data数组中的起始位置i_r:行更新值imageWidth:图像宽x:绘制起始点的横坐标位置

UV分量起始位置更新公式

// 计算UV分量在nv21Data数组中的起始位置:Y的数据+(UV数据行/2*数据宽度)+绘制横坐标(列)位置 uvStartIndex = imageWidth * imageHeight + ((y+i_r)/ 2 * imageWidth) + x ; 
uvStartIndex:uv分量在nv21Data数组中的起始位置i_r:行更新值imageWidth:图像宽imageHeight:图像高x:绘制起始点的横坐标位置

竖直画线

y :外循环更新下一列,内循环改变一列值-间隔mageWidth渲染

uv:外循环更新下一列,内循环改变一列值

因为交错模式,uv外循环更新+2

因为4个y对应一组uv,uv内循环次数比y内循环次数少一半

Y分量起始位置更新公式

// 计算Y分量在nv21Data数组中的起始位置:Y的数据+绘制横坐标位置 yStartIndex = (y) * imageWidth + x+i_c;
yStartIndex:Y分量在nv21Data数组中的起始位置i_c:列更新值imageWidth:图像宽x:绘制起始点的横坐标位置

UV分量起始位置更新公式

// 计算UV分量在nv21Data数组中的起始位置:Y的数据+(UV数据行/2*数据宽度)+绘制列位置uvStartIndex = imageWidth * imageHeight + (y/2 * imageWidth) + x+(i_c);
uvStartIndex:uv分量在nv21Data数组中的起始位置i_c:列更新值imageWidth:图像宽imageHeight:图像高x:绘制起始点的横坐标位置

完整代码

#include <stdio.h>#include <stdlib.h>#include <string.h>/** * 水平画线 * y :外循环更新下一行,内循环改变一行值 * uv:外循环更新下一行,内循环改变一行值 * 因为交错模式,uv内循环更新要隔着2个 * 因为4个y对应一组uv,uv外循环次数比y外循环次数少一半 * * 参数: * unsigned char *nv21Data,int imageWidth, int imageHeight:图像的数据和宽高 * int x:线条起始点的横坐标 * int y:线条起始点的纵坐标 * int line_len:线条的长度*/void draw_line_Horizontal(unsigned char *nv21Data,int imageWidth, int imageHeight,int x,int y,int line_len){ //参数判断 if(nv21Data==NULL || x>imageWidth || y>imageHeight||line_len>imageWidth){ return; } int line_width = 3;//设置画线的宽度为3 int y_width = line_width;//改变Y的宽度 // 4个y共用一组vu,渲染的时候要/2+1 int uv_width = y_width/2 + 1; //设置改变UV的宽度为 Y/2+1 int yStartIndex, uvStartIndex;//定义起始位置 // 设置Y分量 for(int i_r=0;i_r<y_width;i_r++){//i_r表示行更新值 // 计算Y分量在nv21Data数组中的起始位置:Y的数据+绘制横坐标位置 yStartIndex = (y+i_r) * imageWidth + x; // 开始set for (int i = 0; i < line_len; i++) { // 设置Y分量为蓝色 nv21Data[yStartIndex + i] = 60; } } // 设置UV分量 for(int i_r=0;i_r<uv_width;i_r++){//更新行位置 // 计算UV分量在nv21Data数组中的起始位置:Y的数据+(UV数据行/2*数据宽度)+绘制横坐标(列)位置 uvStartIndex = imageWidth * imageHeight + ((y+i_r)/ 2 * imageWidth) + x ; // 开始set for (int i = 0; i < line_len; i+=2) { // 设置UV分量为蓝色:因为UV交错分布,故+2 nv21Data[uvStartIndex + i] = 100; nv21Data[uvStartIndex + i + 1] = 212; } }}/** * 竖直画线 * 间隔一个imageWidth渲染 * y :外循环更新下一列,内循环改变一列值 * uv:外循环更新下一列,内循环改变一列值 * 因为交错模式,uv外循环更新+2 * 因为4个y对应一组uv,uv内循环次数比y内循环次数少一半 * * 参数: * unsigned char *nv21Data,int imageWidth, int imageHeight:图像的数据和宽高 * int x:线条起始点的横坐标 * int y:线条起始点的纵坐标 * int line_len:线条的长度*/void draw_line_Vertical(unsigned char *nv21Data,int imageWidth, int imageHeight,int x,int y,int line_len){ //参数判断 if(nv21Data==NULL || x>imageWidth || y>imageHeight||line_len>imageWidth){ return; } int line_width = 3;//设置画线的宽度为3 int y_width = line_width;//改变Y的宽度 // 4个y共用一组vu,渲染的时候要/2+1 int uv_width = y_width/2+1; //设置改变UV的宽度为 Y int yStartIndex, uvStartIndex;//定义起始位置 // 设置Y分量 for(int i_c=0;i_c<y_width;i_c++){ // 计算Y分量在nv21Data数组中的起始位置:Y的数据+绘制横坐标位置 yStartIndex = (y+i_c) * imageWidth + x; // 开始set for (int i = 0; i < line_len; i++) { // 设置Y分量为蓝色 int index_y = yStartIndex + imageWidth*i; nv21Data[index_y] = 65; } } // 设置UV分量 for(int i_c=0;i_c<uv_width;i_c+=2){//外循环更新的时候因为交错模式=每次更新列位置+2 // 计算UV分量在nv21Data数组中的起始位置:Y的数据+(UV数据行/2*数据宽度)+绘制列位置 uvStartIndex = imageWidth * imageHeight + (y/2 * imageWidth) + x+(i_c); // 开始set: // 因为竖直方向 uv是y的一半,所以line_len/2 // 又因为竖直方向内循环没有交错模式影响,不用跳过2 for (int i = 0; i < line_len/2; i++) { int index_u = uvStartIndex + imageWidth*i;//下一行index:间隔imageWidth*i int index_v = uvStartIndex + imageWidth*i+1; nv21Data[index_u] = 100; nv21Data[index_v] = 212; } }}void drawRectOnNv21Image(const char *pImagePath, int imageWidth, int imageHeight, int left, int top, int right, int bottom) { /* 参数检测 */ if(pImagePath==NULL || imageHeight==0 || imageWidth==0){ printf("Error: Failed parameter\n"); return; } /* 程序运行主体 */ FILE *file = fopen(pImagePath, "rb"); if (file == NULL) { printf("Error: Failed fopen pImagePath\n"); return; } // 计算NV21图片的总大小 int imageSize = imageWidth * imageHeight * 3 / 2; fseek(file, 0, SEEK_END); int file_size = ftell(file); fseek(file, 0, SEEK_SET); // 判断文件是否为NV21格式:file_size!=imageSize if(file_size!=imageSize){ printf("Error: Failed imageSize!=1.5 * imageWidth * imageHeight \n"); fclose(file); return; } // 申请空间存储图片yuv数据 unsigned char *nv21Data = (unsigned char *)malloc(imageSize); if (nv21Data == NULL) { printf("Error: Failed malloc\n"); fclose(file); return; } // 读取NV21图片数据 fread(nv21Data, sizeof(unsigned char), imageSize, file); fclose(file); // 画矩形框 int rectWidth = right - left; int rectHeight = bottom - top; // 上边框 draw_line_Horizontal(nv21Data,imageWidth,imageHeight,left,top,rectWidth); // 下边框 draw_line_Horizontal(nv21Data,imageWidth,imageHeight,left,bottom,rectWidth); // 左边框 draw_line_Vertical(nv21Data,imageWidth,imageHeight,left,top,rectHeight); // 右边框 draw_line_Vertical(nv21Data,imageWidth,imageHeight,right,top,rectHeight); // 保存结果为新的NV21图片 char output_image_path[] = "output_nv21_image.nv21"; FILE *outputFile = fopen(output_image_path, "wb"); if (outputFile == NULL) { printf("Error: Failed fopen output_image_path\n"); free(nv21Data); return; } fwrite(nv21Data, sizeof(unsigned char), imageSize, outputFile); fclose(outputFile); free(nv21Data); printf("Program ok!\n"); printf("Output image save path:%s\n",output_image_path);}int main() { const char *pImagePath = "input_nv21_iamge.nv21"; // NV21图片文件路径 int imageWidth = 640; // 图片宽度 int imageHeight = 480; // 图片高度 int left = 200; // 矩形框左上角x坐标 int top = 170; // 矩形框左上角y坐标 int right = 430; // 矩形框右下角x坐标 int bottom = 380; // 矩形框右下角y坐标 drawRectOnNv21Image(pImagePath, imageWidth, imageHeight, left, top, right, bottom); return 0;}
NV21图片格式深入解析与代码实战-RGB转NV21与画框-CSDN博客 (2024)

References

Top Articles
Latest Posts
Article information

Author: Fredrick Kertzmann

Last Updated:

Views: 5521

Rating: 4.6 / 5 (66 voted)

Reviews: 81% of readers found this page helpful

Author information

Name: Fredrick Kertzmann

Birthday: 2000-04-29

Address: Apt. 203 613 Huels Gateway, Ralphtown, LA 40204

Phone: +2135150832870

Job: Regional Design Producer

Hobby: Nordic skating, Lacemaking, Mountain biking, Rowing, Gardening, Water sports, role-playing games

Introduction: My name is Fredrick Kertzmann, I am a gleaming, encouraging, inexpensive, thankful, tender, quaint, precious person who loves writing and wants to share my knowledge and understanding with you.