NV21图片格式深入解析与代码实战-RGB转NV21与画框

2023-10-15 06:12

本文主要是介绍NV21图片格式深入解析与代码实战-RGB转NV21与画框,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1.NV21格式图片解析

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

在这里插入图片描述

重点总结

  • 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)>>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++代码

#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图像逐个像素画框

结果展示

在这里插入图片描述

整体流程

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

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;//设置画线的宽度为3int y_width = line_width;//改变Y的宽度// 4个y共用一组vu,渲染的时候要/2+1int uv_width = y_width/2 + 1; //设置改变UV的宽度为 Y/2+1int 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;// 开始setfor (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 ;// 开始setfor (int i = 0; i < line_len; i+=2) {// 设置UV分量为蓝色:因为UV交错分布,故+2nv21Data[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;//设置画线的宽度为3int y_width = line_width;//改变Y的宽度// 4个y共用一组vu,渲染的时候要/2+1int uv_width = y_width/2+1; //设置改变UV的宽度为 Yint yStartIndex, uvStartIndex;//定义起始位置// 设置Y分量for(int i_c=0;i_c<y_width;i_c++){// 计算Y分量在nv21Data数组中的起始位置:Y的数据+绘制横坐标位置yStartIndex = (y+i_c) * imageWidth + x;// 开始setfor (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//    又因为竖直方向内循环没有交错模式影响,不用跳过2for (int i = 0; i < line_len/2; i++) {int index_u = uvStartIndex + imageWidth*i;//下一行index:间隔imageWidth*iint 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!=imageSizeif(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与画框的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/215868

相关文章

网页解析 lxml 库--实战

lxml库使用流程 lxml 是 Python 的第三方解析库,完全使用 Python 语言编写,它对 XPath表达式提供了良好的支 持,因此能够了高效地解析 HTML/XML 文档。本节讲解如何通过 lxml 库解析 HTML 文档。 pip install lxml lxm| 库提供了一个 etree 模块,该模块专门用来解析 HTML/XML 文档,下面来介绍一下 lxml 库

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

深入探索协同过滤:从原理到推荐模块案例

文章目录 前言一、协同过滤1. 基于用户的协同过滤(UserCF)2. 基于物品的协同过滤(ItemCF)3. 相似度计算方法 二、相似度计算方法1. 欧氏距离2. 皮尔逊相关系数3. 杰卡德相似系数4. 余弦相似度 三、推荐模块案例1.基于文章的协同过滤推荐功能2.基于用户的协同过滤推荐功能 前言     在信息过载的时代,推荐系统成为连接用户与内容的桥梁。本文聚焦于

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

活用c4d官方开发文档查询代码

当你问AI助手比如豆包,如何用python禁止掉xpresso标签时候,它会提示到 这时候要用到两个东西。https://developers.maxon.net/论坛搜索和开发文档 比如这里我就在官方找到正确的id描述 然后我就把参数标签换过来

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount

poj 1258 Agri-Net(最小生成树模板代码)

感觉用这题来当模板更适合。 题意就是给你邻接矩阵求最小生成树啦。~ prim代码:效率很高。172k...0ms。 #include<stdio.h>#include<algorithm>using namespace std;const int MaxN = 101;const int INF = 0x3f3f3f3f;int g[MaxN][MaxN];int n

滚雪球学Java(87):Java事务处理:JDBC的ACID属性与实战技巧!真有两下子!

咦咦咦,各位小可爱,我是你们的好伙伴——bug菌,今天又来给大家普及Java SE啦,别躲起来啊,听我讲干货还不快点赞,赞多了我就有动力讲得更嗨啦!所以呀,养成先点赞后阅读的好习惯,别被干货淹没了哦~ 🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,助你一臂之力,带你早日登顶🚀,欢迎大家关注&&收藏!持续更新中,up!up!up!! 环境说明:Windows 10

计算机毕业设计 大学志愿填报系统 Java+SpringBoot+Vue 前后端分离 文档报告 代码讲解 安装调试

🍊作者:计算机编程-吉哥 🍊简介:专业从事JavaWeb程序开发,微信小程序开发,定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事,生活就是快乐的。 🍊心愿:点赞 👍 收藏 ⭐评论 📝 🍅 文末获取源码联系 👇🏻 精彩专栏推荐订阅 👇🏻 不然下次找不到哟~Java毕业设计项目~热门选题推荐《1000套》 目录 1.技术选型 2.开发工具 3.功能