1.NV21 format image analysis
The NV21 image format belongs to the YUV420SP format in the YUV color space
Every four Y components share a set of U components and V components. Y is sorted continuously, and U and V are sorted cross-wise.
Key points summary
- uv staggered mode
- 4Y shares a set of uvs (2 pcs)
- Size: UV= half of Y
The arrangement is as follows
Y Y ? Y Y ? Y Y ? Y Y
Y Y ? Y Y ? Y Y ? Y YY Y ? Y Y ? Y Y ? Y Y
Y Y ? Y Y ? Y Y ? Y YV U ?V U ?V U? V U
V U ?V U ?V U? V U
2. Convert RGB images to NV21-pixel by pixel
Basic formula
-
yuv –> rgb
R = (298*Y + 411 * V - 57344)>>8 G = (298*Y - 101* U - 211* V + 34739)>>8 B = (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 + 128 V= (112*R - 94*G - 18*B)>>8 + 128
c++ code
#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"); \t 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); \t 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]; //Calculate the value of 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); //Calculate the values of U and V and perform 2x2 sampling if (i%2==0 & amp; & amp;(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( & amp;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);//original image cv::imshow("YUV", NV21);//Converted image cv::imshow("opencv_YUV", OpencvYUV); //Opencv converted picture cv::imwrite("NV21.jpg", NV21); cv::waitKey(30000); } int main() {<!-- --> RGB2NV21(); return 0; }
3.NV21 image pixel by pixel frame
Result display
Overall process
Save original image to 1D array
2 horizontal lines
2 vertical lines
The main difficulty is to find the iterative formula of the two uv starting coordinates
Horizontal line drawing
y: The outer loop updates the next row, and the inner loop changes the value of one row
uv: The outer loop updates the next row, and the inner loop changes the value of one row.
Because of the staggered mode, the uv inner loop update needs to be separated by 2
Because 4 y corresponds to a set of uv, the number of uv outer loops is less than half the number of y outer loops.
Y component starting position update formula
//Calculate the starting position of the Y component in the nv21Data array: Y data + draw the abscissa position yStartIndex = (y + i_r) * imageWidth + x;
yStartIndex: the starting position of the Y component in the nv21Data array i_r: row update value imageWidth: image width x: The abscissa position of the drawing starting point
UV component starting position update formula
//Calculate the starting position of the UV component in the nv21Data array: Y data + (UV data row/2*data width) + draw the abscissa (column) position uvStartIndex = imageWidth * imageHeight + ((y + i_r)/ 2 * imageWidth) + x;
uvStartIndex: the starting position of the uv component in the nv21Data array i_r: row update value imageWidth: image width imageHeight: image height x: The abscissa position of the drawing starting point
Draw vertical lines
y: The outer loop updates the next column, and the inner loop changes the value of one column – interval mageWidth rendering
uv: The outer loop updates the next column, and the inner loop changes the value of one column.
Because of the staggered mode, the uv outer loop updates + 2
Because 4 y corresponds to a set of uv, the number of loops within uv is less than half the number of loops within y.
Y component starting position update formula
//Calculate the starting position of the Y component in the nv21Data array: Y data + draw the abscissa position yStartIndex = (y) * imageWidth + x + i_c;
yStartIndex: the starting position of the Y component in the nv21Data array i_c: Column update value imageWidth: image width x: The abscissa position of the drawing starting point
UV component starting position update formula
//Calculate the starting position of the UV component in the nv21Data array: Y data + (UV data row/2*data width) + drawing column position uvStartIndex = imageWidth * imageHeight + (y/2 * imageWidth) + x + (i_c);
uvStartIndex: the starting position of the uv component in the nv21Data array i_c: Column update value imageWidth: image width imageHeight: image height x: The abscissa position of the drawing starting point
Complete code
#include <stdio.h> #include <stdlib.h> #include <string.h> /** * Draw horizontal lines * y: The outer loop updates the next row, and the inner loop changes the value of one row * uv: The outer loop updates the next row, and the inner loop changes the value of one row * Because of the staggered mode, the uv inner loop update must be separated by 2 * Because 4 y corresponds to a group of uv, the number of uv outer loops is less than half the number of y outer loops * * Parameters: * unsigned char *nv21Data, int imageWidth, int imageHeight: image data, width and height * int x: the abscissa coordinate of the starting point of the line * int y: vertical coordinate of the starting point of the line * int line_len: length of line */ void draw_line_Horizontal(unsigned char *nv21Data,int imageWidth, int imageHeight,int x,int y,int line_len){<!-- --> //Parameter judgment if(nv21Data==NULL || x>imageWidth || y>imageHeight||line_len>imageWidth){<!-- --> return; } int line_width = 3;//Set the width of the drawn line to 3 int y_width = line_width;//Change the width of Y // 4 y share a group of vu, and /2 + 1 is required when rendering int uv_width = y_width/2 + 1; //Set and change the width of UV to Y/2 + 1 int yStartIndex, uvStartIndex;//Define the starting position //Set Y component for(int i_r=0;i_r<y_width;i_r + + ){<!-- -->//i_r represents the row update value // Calculate the starting position of the Y component in the nv21Data array: Y data + draw the abscissa position yStartIndex = (y + i_r) * imageWidth + x; // start set for (int i = 0; i < line_len; i + + ) {<!-- --> //Set the Y component to blue nv21Data[yStartIndex + i] = 60; } } //Set UV component for(int i_r=0;i_r<uv_width;i_r + + ){<!-- -->//Update row position // Calculate the starting position of the UV component in the nv21Data array: Y data + (UV data row/2*data width) + draw the abscissa (column) position uvStartIndex = imageWidth * imageHeight + ((y + i_r)/ 2 * imageWidth) + x; // start set for (int i = 0; i < line_len; i + =2) {<!-- --> //Set the UV component to blue: because the UVs are staggered, + 2 nv21Data[uvStartIndex + i] = 100; nv21Data[uvStartIndex + i + 1] = 212; } } } /** * Draw vertical lines * Render at an interval of imageWidth * y: The outer loop updates the next column, and the inner loop changes the value of one column * uv: The outer loop updates the next column, and the inner loop changes the value of one column. * Because of the staggered mode, uv outer loop update + 2 * Because 4 y corresponds to a group of uv, the number of loops within uv is less than half the number of loops within y * * Parameters: * unsigned char *nv21Data, int imageWidth, int imageHeight: image data, width and height * int x: the abscissa coordinate of the starting point of the line * int y: vertical coordinate of the starting point of the line * int line_len: length of line */ void draw_line_Vertical(unsigned char *nv21Data,int imageWidth, int imageHeight,int x,int y,int line_len){<!-- --> //Parameter judgment if(nv21Data==NULL || x>imageWidth || y>imageHeight||line_len>imageWidth){<!-- --> return; } int line_width = 3;//Set the width of the drawn line to 3 int y_width = line_width;//Change the width of Y // 4 y share a group of vu, and /2 + 1 is required when rendering int uv_width = y_width/2 + 1; //Set and change the width of UV to Y int yStartIndex, uvStartIndex;//Define the starting position //Set Y component for(int i_c=0;i_c<y_width;i_c + + ){<!-- --> // Calculate the starting position of the Y component in the nv21Data array: Y data + draw the abscissa position yStartIndex = (y + i_c) * imageWidth + x; // start set for (int i = 0; i < line_len; i + + ) {<!-- --> //Set the Y component to blue int index_y = yStartIndex + imageWidth*i; nv21Data[index_y] = 65; } } //Set UV component for(int i_c=0;i_c<uv_width;i_c + =2){<!-- -->//When updating in the outer loop, because the staggered mode = each update column position + 2 // Calculate the starting position of the UV component in the nv21Data array: Y data + (UV data row/2*data width) + drawing column position uvStartIndex = imageWidth * imageHeight + (y/2 * imageWidth) + x + (i_c); // Start set: // Because the vertical direction uv is half of y, so line_len/2 // And because the vertical inner loop has no interleaving mode effect, there is no need to skip 2 for (int i = 0; i < line_len/2; i + + ) {<!-- --> int index_u = uvStartIndex + imageWidth*i;//Next row index: interval 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) {<!-- --> /* Parameter detection */ if(pImagePath==NULL || imageHeight==0 || imageWidth==0){<!-- --> printf("Error: Failed parameter\ "); return; } /* Program running body */ FILE *file = fopen(pImagePath, "rb"); if (file == NULL) {<!-- --> printf("Error: Failed fopen pImagePath\ "); return; } // Calculate the total size of NV21 images int imageSize = imageWidth * imageHeight * 3 / 2; fseek(file, 0, SEEK_END); int file_size = ftell(file); fseek(file, 0, SEEK_SET); // Determine whether the file is in NV21 format: file_size!=imageSize if(file_size!=imageSize){<!-- --> printf("Error: Failed imageSize!=1.5 * imageWidth * imageHeight \ "); fclose(file); return; } // Apply for space to store image yuv data unsigned char *nv21Data = (unsigned char *)malloc(imageSize); if (nv21Data == NULL) {<!-- --> printf("Error: Failed malloc\ "); fclose(file); return; } //Read NV21 image data fread(nv21Data, sizeof(unsigned char), imageSize, file); fclose(file); //Draw a rectangular box int rectWidth = right - left; int rectHeight = bottom - top; // top border draw_line_Horizontal(nv21Data,imageWidth,imageHeight,left,top,rectWidth); // bottom border draw_line_Horizontal(nv21Data,imageWidth,imageHeight,left,bottom,rectWidth); // left border draw_line_Vertical(nv21Data,imageWidth,imageHeight,left,top,rectHeight); // right border draw_line_Vertical(nv21Data,imageWidth,imageHeight,right,top,rectHeight); //Save the result as a new NV21 image 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\ "); free(nv21Data); return; } fwrite(nv21Data, sizeof(unsigned char), imageSize, outputFile); fclose(outputFile); free(nv21Data); printf("Program ok!\ "); printf("Output image save path:%s\ ",output_image_path); } int main() {<!-- --> const char *pImagePath = "input_nv21_iamge.nv21"; // NV21 image file path int imageWidth = 640; // Image width int imageHeight = 480; // Image height int left = 200; //x coordinate of the upper left corner of the rectangular box int top = 170; // Y coordinate of the upper left corner of the rectangular box int right = 430; // x coordinate of the lower right corner of the rectangular box int bottom = 380; // y coordinate of the lower right corner of the rectangular box drawRectOnNv21Image(pImagePath, imageWidth, imageHeight, left, top, right, bottom); return 0; }