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

news2024/11/24 14:23:15

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;//设置画线的宽度为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;
}


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1093627.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

001数据安全传输-多端协议传输平台:Openssl安装和配置 - EVP代码测试

001数据安全传输-多端协议传输平台&#xff1a;Openssl安装和配置 - EVP代码测试 文章目录 001数据安全传输-多端协议传输平台&#xff1a;Openssl安装和配置 - EVP代码测试1. 安装1.1 windows下安装openssl1.2 Linux下安装OpenSSL 2. VS中使用openssl3. 测试 1. 安装 1.1 win…

ionic+vue+capacitor系列笔记--常用操作代码合集(图片引用,axios跨域配置,去除按钮波纹)

1.单个图片引用 html <img :src"userImgSrc" />ts <script lang"ts"> import { defineComponent } from "vue"; export default defineComponent({name: "Tab1Page",components: {},setup(props, context) {let url &…

FFmpeg截图命令优化

由于项目要求&#xff0c;需要对摄像机的rtsp流进行截图。一开始我使用了命令&#xff1a; ./ffmpeg -ss 0 -i XXX -f image2 -vframes 1 -s 370*210 -y output.jpg 上述命令抓取rtsp流第0秒&#xff08;当前&#xff09;的图像&#xff0c;将其保存为370*210分辨率的jpg图片…

清理日志后出现 no boot device available,不能启动处理

3号LNS清理日志后出现 no boot device available&#xff0c;不能启动处理 然后 不知道为什么&#xff0c;清理系统日志后&#xff0c;就这样了&#xff0c; 然后按下F11&#xff0c;选择硬盘启动就好了&#xff0c;不要选择USB那个&#xff01; ...... 这样是能启动了&#xf…

延时盲注(CVE-2022-0948)

详解&#xff1a; 延时盲注&#xff0c;也称为时间盲注或延迟注入&#xff0c;是一种利用执行时间差判断是否执行成功的盲注手法。攻击者提交一个对执行时间敏感的SQL语句&#xff0c;通过执行时间的长短来判断注入是否成功。例如&#xff0c;如果注入成功&#xff0c;执行时间…

互联网Java工程师面试题·Java 并发编程篇·第七弹

目录 16、CAS 的问题 17、什么是 Future&#xff1f; 18、什么是 AQS 19、AQS 支持两种同步方式&#xff1a; 20、ReadWriteLock 是什么 21、FutureTask 是什么 22、synchronized 和 ReentrantLock 的区别 23、什么是乐观锁和悲观锁 24、线程 B 怎么知道线程 A 修改了…

零代码编程:用ChatGPT批量采集bookroo网页上的英文书目列表

bookroo网页上有很多不错的英文图书书目。比如这个关于儿童花样滑冰的书单&#xff1a; https://bookroo.com/explore/books/topics/ice-skating 怎么批量下载下来呢&#xff1f; 这个网页是动态网页&#xff0c;要爬取下来比较麻烦&#xff0c;可以先查看源代码&#xff0c;…

06-使用dockerfile构建nginx、redis镜像

主旨 本文使用上一篇文章中说到的dockerfile方式&#xff0c;分别构建一个nginx&#xff0c;一个redis镜像。 环境 linux环境 docker环境 nginx镜像构建 创建目录&#xff0c;并切换至对应目录&#xff1a; [yunweijialocalhost ~]$ mkdir -pv docker/nginx mkdir: 已创建目录 …

redis 缓存设计

1. 前言 学习redis 缓存&#xff0c;可以是为了技术面试&#xff1b;可以是为了应用实践&#xff0c;在开发设计过程中引入缓存&#xff0c;提高性能。比如常见的面试题&#xff1a; 2. 什么是缓存预热、击穿、穿透和雪崩 2.1 缓存预热 缓存预热就是系统上线后&#xff0c;…

Puppeteer结合测试工具jest使用(四)

Puppeteer结合测试工具jest使用&#xff08;四&#xff09; Puppeteer结合测试工具jest使用&#xff08;四&#xff09;一、简介二、与jest结合使用&#xff0c;集成到常规测试三、支持其他的几种四、总结 一、简介 Puppeteer是一个提供自动化控制Chrome或Chromium浏览器的Node…

09. 机器学习- 逻辑回归

文章目录 线性回归回顾逻辑回归 Hi&#xff0c;你好。我是茶桁。 上一节课&#xff0c;在结尾的时候咱们预约了这节课一开始对上一节课的内容进行一个回顾&#xff0c;并且预告了这节课内容主要是「逻辑回归」&#xff0c;那我们现在就开始吧。 线性回归回顾 在上一节课中&a…

【Linux】基本指令-入门级文件操作(二)

目录 基本指令 7 cp指令&#xff08;重要&#xff09; 8 mv指令&#xff08;重要&#xff09; 9 nano指令 10 cat指令 11 echo指令与重定向&#xff08;重要&#xff09; 12 more指令 13 less指令 基本指令 7 cp指令&#xff08;重要&#xff09; 功能&#xff1a;复…

React高级特性之context

例1&#xff1a; createContext // 跨组件通信Context引入createContext import React, { createContext } from react// App传数据给组件C App -- A -- C// 1. 创建Context对象 const { Provider, Consumer } createContext()function SonA () {return (<div>我是…

分布式存储系统Ceph应用详解

Ceph的应用 一、Ceph 存储池(Pool)1.1 Ceph存储池的基本概念1.2 原理1.3 一个Pool资源池应该包含多少PG数&#xff1f;1.4 Ceph 存储池相关管理命令1.4.1 创建1.4.2 查看1.4.3 修改1.4.4 删除 二、 CephFS文件系统MDS接口三、创建CephFS文件系统MDS接口3.1 服务端操作Step1 在管…

【Java学习之道】线程的概念与作用

引言 今天我们将探索多线程编程的基础概念和作用。对于初学者来说&#xff0c;掌握多线程编程是迈向Java高级技能的重要一步。通过本章的学习&#xff0c;你将了解线程是什么以及它在程序开发中的重要性&#xff0c;为你进一步深入学习和实际工作打下坚实的基础。让我们一起来…

学信息系统项目管理师第4版系列27_项目集管理和项目组合管理

1. 项目集发起人 1.1. 负责承诺将组织的资源应用于项目集&#xff0c;并致力于使项目集取得成功的人 1.2. 典型职责 1.2.1. 为项目集提供资金&#xff0c;确保项目集目标与战略愿景保持一致&#xff1b; 1.2.2. 使效益实现交付 1.2.3. 消除项目集管理与交付的困难和障碍 …

单目3D自动标注

这里介绍两种 1. 基于SAM的点云标注 Seal&#xff1a;是一个多功能的自监督学习框架&#xff0c;能够通过利用视觉基础模型的现成知识和2D-3D的时空约束分割自动驾驶数据集点云 Scalability&#xff1a;可拓展性强&#xff0c;视觉基础模型蒸馏到点云中&#xff0c;避免2D和…

应对互联网用户激增与IP地址短缺的挑战

互联网用户激增 随着互联网技术的飞速发展&#xff0c;互联网已经深刻改变了我们的生活方式和商业模式。无论是个人用户还是企业&#xff0c;都越来越依赖互联网进行沟通、娱乐、工作和学习。这一现象导致了互联网用户数量的快速激增。 IP地址的有限性 然而&#xff0c;与此…

码蹄集2230--square 高精度乘低精度,组合数

有种走法&#xff0c;因为需要向上走m步&#xff0c;向右走n步。 显然分子分母分别算出&#xff0c;再相除不太可能&#xff0c;那么分别求出分子和分母的质因子相乘的形式。分子存入up数组中&#xff0c;分母存入down数组中&#xff0c;数组中的元素对应之差final_数组即代表…

OJ项目——用户的登录拦截,我是如何实现的?

目录 前言 1、关于Session该如何处理 简单session回顾&#xff1a; 回顾session的setAttribute、getAttribute : 项目中如何做&#xff1f; 2、登陆拦截器实现 自定义拦截器&#xff1a; 自定义拦截&#xff1a; 前言 博主之前也有出过一期关于拦截器的&#xff0c;大…