BMP图片的旋转以及BMP转YUV(4:2:0)

一、简要介绍思路:

BMP文件简述:BMP文件由文件头、信息头、颜色信息、图形数据四部分组成。(此处由于是24位BMP不考虑颜色信息),则BMP文件里只剩下文件头、信息头、图形数据。

读取BMP时读取顺序则为:1.读取一个文件头 2.读取一个信息头 3.读取图形数据

预先定义的数据类型:BYTE(8位即一个字节)、WORD(16位即两个字节)、DWORD(32位即四个字节)

typedef unsigned char  BYTE;//一字节
typedef unsigned short WORD;//两字节
typedef unsigned long DWORD;//四字节

BMP文件的文件头数据结构如下:

//BMP图片的文件头(一共14个字节)
typedef struct HEAD
{
    WORD bfType; /*  文件类型*/
    DWORD bfSize; /*  文件大小*/
    WORD bfReserved1; /*  保留位*/
    WORD bfReserved2; /*  保留位*/
    DWORD bfOffBits; /*  数据偏移位置*/
}HEAD;

BMP文件的信息头数据结构如下:

//BMP图片信息头(40字节)
typedef struct INFOHEAD
{
    DWORD biSize; /*  此结构大小 */
    DWORD biWidth; /*  图像宽度 */
    DWORD biHeight; /*  图像高度 */
    WORD biPlanes; /*  调色板数量 */
    WORD biBitCount; /*  每个象素对应的位数,*/
    DWORD biCompression; /*  压缩 */
    DWORD biSizeImage; /*  图像大小 */
    DWORD biXPelsPerMeter;/*  横向分辨率 */
    DWORD biYPelsPerMeter;/*  纵向分辨率 */
    DWORD biClrUsed; /*  颜色使用数 */
    DWORD biClrImportant; /*  重要颜色数 */
}INFOHEAD;

思路:

1.首先读取原BMP图像数据:读取一个文件头 、读取一个信息头 、读取图形像素数据(要注意BMP文件图形像素数据里面,每行的数据都是4字节的整数倍,不是4字节整数倍的要补零字节,因此在读取、写入图形像素数据时要注意这部分零字节)。

2.进行BMP转为YUV文件时,只需运用YUV、BMP转换公式即可:

Y= 0.299*R+0.587*G+0.114*B

U= -0.168*R-0.332*G+0.5*G+128

V= 0.5*R-0.419*G-0.081*B+128

因为是要转为YUV(4:2:0),所以在当把所有的BMP像素点的B、G、R值转为Y、U、V的值后要将U、V的值进行4:1采样(本文采用了取平均值),Y则不需要变。

注意事项:(1)BMP的图形像素信息存储顺序为从左向右,从下向上(主要注意这里),因此在进行BMP转YUV时,先进行BMP图形像素信息的上下对称。

(2)BMP每个像素点的存储是按着B、G、R的顺序(不是R、G、B)。

3.进行180度翻转时,文件头、信息头的值都未改变,只要改变图形像素数据即可。

4.进行90度翻转时,文件头、信息头的部分值改变了,要注意重新计算这些值,之后再改变图形像素数据即4.可。

 二、完整代码:(BMP旋转、BMP转YUV420可以单独运行)

1.header.h:

#pragma once
#define _CRT_SECURE_NO_WARNINGS


typedef unsigned char  BYTE;//一字节
typedef unsigned short WORD;//两字节
typedef unsigned long DWORD;//四字节

//BMP图片的文件头(一共14个字节)
typedef struct HEAD
{
    //WORD bfType; /*  文件类型*/
    DWORD bfSize; /*  文件大小*/
    WORD bfReserved1; /*  保留位*/
    WORD bfReserved2; /*  保留位*/
    DWORD bfOffBits; /*  数据偏移位置*/
}HEAD;    // 这里的bfType需要单独取

//BMP图片信息头(40字节)
typedef struct INFOHEAD
{
    DWORD biSize; /*  此结构大小 */
    DWORD biWidth; /*  图像宽度 */
    DWORD biHeight; /*  图像高度 */
    WORD biPlanes; /*  调色板数量 */
    WORD biBitCount; /*  每个象素对应的位数,*/
    DWORD biCompression; /*  压缩 */
    DWORD biSizeImage; /*  图像大小 */
    DWORD biXPelsPerMeter;/*  横向分辨率 */
    DWORD biYPelsPerMeter;/*  纵向分辨率 */
    DWORD biClrUsed; /*  颜色使用数 */
    DWORD biClrImportant; /*  重要颜色数 */
}INFOHEAD;


void turn180(WORD bfType, BYTE* image_array, HEAD* filehead, INFOHEAD* infohead, int off);
void turn90(WORD bfType, BYTE* image_array, HEAD* filehead, INFOHEAD* infohead);
void rgb2yuv(int width, int heigth, unsigned char* rgbbuf, unsigned char* ybuf, unsigned char* ubuf, unsigned char* vbuf);

2.Image_rollover.cpp:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include"header.h"


FILE* FP, * FP_yuv;
HEAD* filehead = (HEAD*)malloc(sizeof(HEAD));
INFOHEAD* infohead = (INFOHEAD*)malloc(sizeof(INFOHEAD));
WORD bfType;  //  文件类型
BYTE* store;  // 存储一个字节信息
int i, j, k;
BYTE* image_array, * ybuf, * ubuf, * vbuf, * rgbbuf;
BYTE zero_fill = 0;
FILE* FP1;
int off, width, height;

void shan(WORD bfType, BYTE* image_array, HEAD* filehead, INFOHEAD* infohead);


int main()
{
    if ((FP = fopen("1.bmp", "rb")) == NULL)
    {
        printf("cann't find the file!");
        return 0;
    }

    fread(&bfType, sizeof(WORD), 1, FP); //单独读取bfType
    fread(filehead, sizeof(HEAD), 1, FP); //读入文件头
    fread(infohead, sizeof(INFOHEAD), 1, FP);  //读入信息头
    /*
    printf("文件头大小:%d\n", sizeof(HEAD));
    printf("信息头大小:%d\n", sizeof(INFOHEAD));
    */
    int a = 0;
    if (bfType != 0X4d42)
    {
        printf("不是BMP图片");  //bfType为0X4d42时说明读取的时BMP图片
        return 0;
    }
  
    width = infohead->biWidth;
    height = infohead->biHeight;
    image_array = (BYTE*)malloc(sizeof(BYTE) * infohead->biHeight * infohead->biWidth * 3);//存储图片的像素数据
    store = (BYTE*)malloc(sizeof(BYTE));
    ybuf = (BYTE*)malloc(sizeof(BYTE) * infohead->biHeight * infohead->biWidth);
    ubuf = (BYTE*)malloc(sizeof(BYTE) * infohead->biHeight * infohead->biWidth / 4);
    vbuf = (BYTE*)malloc(sizeof(BYTE) * infohead->biHeight * infohead->biWidth / 4);
    rgbbuf= (BYTE*)malloc(sizeof(BYTE) * infohead->biHeight * infohead->biWidth * 3);
    
    off = (infohead->biWidth * 3) % 4;
    if (off != 0)
    {
        off - 4 - off;       //BMP图片每行的像素信息都要用零补成4个字节的整数倍,off即为补的
//零字节的个数
    }
    /*因为图像存储时每行为了补成4字节的整数倍补了零字节,所以一个字节一个字节地读取*/
    for (i = 0;i < infohead->biHeight;i++)    //图形像素数据存储到image_array数组中
    {
        for (j = 0;j < infohead->biWidth;j++)
        {
            fread(store, sizeof(BYTE), 1, FP);
            image_array[i * infohead->biWidth * 3 + j  * 3] = *store; 
            fread(store, sizeof(BYTE), 1, FP);
            image_array[i * infohead->biWidth * 3 + j  * 3 + 1] = *store;
            fread(store, sizeof(BYTE), 1, FP);
            image_array[i * infohead->biWidth * 3 + j  * 3 + 2] = *store;
        }
        for (k = 0;k < off;k++)
        {
            fread(store, sizeof(BYTE), 1, FP);   //取每行末尾补的零字节但是不存
        }
    }
    fclose(FP);

    /*读取的数据恢复图像*/
    /*
    FP1 = fopen("2.bmp", "wb");
    fwrite(&bfType, sizeof(WORD), 1, FP1);
    fwrite(filehead, sizeof(HEAD), 1, FP1);
    fwrite(infohead, sizeof(INFOHEAD), 1, FP1);
    for (i = 0;i < infohead->biHeight;i++)    //原始数据存储到数组里面
    {
        for (j = 0;j < infohead->biWidth;j++)
        {
            fwrite(&image_array[i * infohead->biWidth * 3 + j  * 3], sizeof(BYTE), 1, FP1);
            fwrite(&image_array[i * infohead->biWidth * 3 + j  * 3+1 ], sizeof(BYTE), 1, FP1);
            fwrite(&image_array[i * infohead->biWidth * 3 + j  * 3+2], sizeof(BYTE), 1, FP1);
        }
        for (k = 0;k < off;k++)
        {
            fwrite(&zero_fill, sizeof(BYTE), 1, FP1);
        }
    }
    */
    /*
    上下颠倒
    for (i = 0;i < infohead->biHeight;i++)    //原始数据存储到数组里面
    {
        for (j = 0;j < infohead->biWidth;j++)
        {
            fwrite(&image_array[(infohead->biHeight-i-1) * infohead->biWidth * 3 + j * 3], sizeof(BYTE), 1, FP1);
            fwrite(&image_array[(infohead->biHeight - i - 1) * infohead->biWidth * 3 + j * 3 + 1], sizeof(BYTE), 1, FP1);
            fwrite(&image_array[(infohead->biHeight - i - 1) * infohead->biWidth * 3 + j * 3 + 2], sizeof(BYTE), 1, FP1);
        }
        for (k = 0;k < off;k++)
        {
            fwrite(&zero_fill, sizeof(BYTE), 1, FP1);
        }
    }
    fclose(FP1);
    */
    /*rgbbuf为bmp上下翻转之后的,这样才是正常的顺序,因为bmp是从左向右,从下往上存数据的*/
    for (i = 0;i < height;i++)
        for (j = 0;j < width;j++)
        {
            rgbbuf[j * 3 + (height - 1 - i) * width * 3] = image_array[j * 3 + width * 3 * i];
            rgbbuf[j * 3 + (height - 1 - i) * width * 3+1] = image_array[j * 3 + width * 3 * i+1];
            rgbbuf[j * 3 + (height - 1 - i) * width * 3+2] = image_array[j * 3 + width * 3 * i+2];
        }

    /*bmp文件通过公式转为yuv文件*/
    rgb2yuv(width, height, rgbbuf, ybuf, ubuf, vbuf);  //调用转换函数
    FP_yuv = fopen("3.yuv", "wb");
    fwrite(ybuf, 1, sizeof(unsigned char) * width * height, FP_yuv);
    fwrite(ubuf, 1, sizeof(unsigned char) * width * height/4, FP_yuv);
    fwrite(vbuf, 1, sizeof(unsigned char)* width* height/4, FP_yuv);
    printf("%d*%d的bmp文件转成yuv文件完成\n", height, width);
    fclose(FP_yuv);

    /*选择进行bmp的翻转操作*/
    printf("请选择想对BMP图片的操作:输入1为旋转180度,输入2位旋转90度:\n");
    int select;
    scanf("%d", &select);
    if (select == 1)
    {
        turn180(bfType, image_array, filehead, infohead,off);
    }
    else if (select == 2)
    {
        turn90(bfType, image_array, filehead, infohead);
    }
    else
    {
        printf("选择出错了");
    }

}

3. rgb2yuv.cpp:

#include "stdlib.h"
#include"header.h"

void rgb2yuv(int width, int heigth, unsigned char* rgbbuf, unsigned char* ybuf, unsigned char* ubuf, unsigned char* vbuf)
{
	int i, j;
	unsigned char* sub_ubuf, * sub_vbuf;//未丢弃的uv量
	sub_ubuf = (unsigned char*)malloc(sizeof(unsigned char) * width * heigth);  
	sub_vbuf= (unsigned char*)malloc(sizeof(unsigned char) * width * heigth);

	/*通过YUV和RGB的转换公式来计算每个像素Y、U、V的值,注意RGB里面每个像素点是按着B、G、R(不是R、G、B)的顺序存的*/
	for(i=0;i<heigth;i++)
		for (j = 0;j < width;j++)
		{
			ybuf[i * width + j] = 0.299 * rgbbuf[i * width * 3 + j * 3+2] + 0.587 * rgbbuf[i * width * 3 + j * 3 + 1] + 0.114 * rgbbuf[i * width * 3 + j * 3 ];
			if (ybuf[i * width + j] > 235)
				ybuf[i * width + j] = 235;
			else if (ybuf[i * width + j] < 16)
				ybuf[i * width + j] = 16;
			sub_ubuf[i * width + j] = -0.168 * rgbbuf[i * width * 3 + j * 3+2] - 0.332 * rgbbuf[i * width * 3 + j * 3 + 1] + 0.5 * rgbbuf[i * width * 3 + j * 3 ]+128;
			sub_vbuf[i * width + j] = 0.5 * rgbbuf[i * width * 3 + j * 3+2] - 0.419 * rgbbuf[i * width * 3 + j * 3 + 1] - 0.081*rgbbuf[i * width * 3 + j * 3 ]+128;
		}
	/*将sub_ubuf sub_vbuf减少到1/4(4:2:0)*/
	for(i=0;i<heigth/2;i++)
		for (j = 0;j < width/2;j++)
		{
			ubuf[i * width / 2 + j] = (sub_ubuf[i * 2 * width + j * 2] + sub_ubuf[i * 2 * width + j * 2 + 1] + sub_ubuf[i * 2 * width + j * 2 + width] + sub_ubuf[i * 2 * width + j * 2 + width + 1]) / 4;
			if (ubuf[i * width / 2 + j] > 235)
				ubuf[i * width / 2 + j] = 235;
			else if (ubuf[i * width / 2 + j] < 16)
				ubuf[i * width / 2 + j] = 16;
			vbuf[i * width / 2 + j]= (sub_vbuf[i * 2 * width + j * 2] + sub_vbuf[i * 2 * width + j * 2 + 1] + sub_vbuf[i * 2 * width + j * 2 + width] + sub_vbuf[i * 2 * width + j * 2 + width + 1]) / 4;
			if (vbuf[i * width / 2 + j] > 235)
				vbuf[i * width / 2 + j] = 235;
			else if (vbuf[i * width / 2 + j] < 16)
				vbuf[i * width / 2 + j] = 16;
		}
}

4.turn90.cpp:

#include"header.h"
#include<stdlib.h>
#include<stdio.h>


void turn90(WORD bfType, BYTE* image_array, HEAD* filehead, INFOHEAD* infohead)
{


    FILE* FP = fopen("1.bmp", "wb");
    int i, j, k;
    int off_new;
    BYTE zero_fill = 0;
    DWORD width, heigth;
    width = infohead->biWidth;
    heigth = infohead->biHeight;   //原图的宽高
    off_new = infohead->biHeight * 3 % 4;
    if (off_new != 0)
    {
        off_new = 4 - off_new;   //每行需要补零的字节数
    }

    //翻转90度时BMP文件头、信息头的部分值有所改变,要重新计算
    filehead->bfSize = sizeof(WORD) + sizeof(HEAD) + sizeof(INFOHEAD) + infohead->biHeight * infohead->biWidth * 3 + off_new * infohead->biWidth;
    DWORD swsw;
    /*原图像的宽和高要互换*/
    swsw = infohead->biHeight;
    infohead->biHeight = infohead->biWidth;
    infohead->biWidth = swsw;
    infohead->biSizeImage = filehead->bfSize - (sizeof(WORD) + sizeof(HEAD) + sizeof(INFOHEAD));

    fwrite(&bfType, sizeof(WORD), 1, FP);    
    fwrite(filehead, sizeof(HEAD), 1, FP);
    fwrite(infohead, sizeof(INFOHEAD), 1, FP);
    /*写入翻转的BMP像素信息*/
    for (i = 0;i < width;i++)
    {
        for (j = heigth - 1;j >= 0;j--)
        {
            fwrite(&image_array[j * width * 3 + i * 3], sizeof(BYTE), 1, FP);
            fwrite(&image_array[j * width * 3 + i * 3 + 1], sizeof(BYTE), 1, FP);
            fwrite(&image_array[j * width * 3 + i * 3 + 2], sizeof(BYTE), 1, FP);
        }
        for (k = 0;k < off_new;k++)
        {
            fwrite(&zero_fill, sizeof(BYTE), 1, FP);  //每行为了成为4字节的整数倍要补的零字节
        }
    }

    fclose(FP);

}

5.turn180cpp:

#include"header.h"
#include<stdlib.h>
#include<stdio.h>

void turn180(WORD bfType, BYTE* image_array, HEAD* filehead, INFOHEAD* infohead, int off)
{

    FILE* FP = fopen("1.bmp", "wb");
    BYTE zero_fill = 0;
    int i, j, k;
    /*因为是翻转180度,图片的长宽都没变,文件头、信息头的值都未改变,则直接写入原图像的文件头和信息头*/
    fwrite(&bfType, sizeof(WORD), 1, FP);
    fwrite(filehead, sizeof(HEAD), 1, FP);
    fwrite(infohead, sizeof(INFOHEAD), 1, FP);
    for (i = infohead->biHeight - 1;i >= 0;i--)
    {
        for (j = infohead->biWidth - 1;j >= 0;j--)
        {
            fwrite(&image_array[i * infohead->biWidth * 3 + j * 3], sizeof(BYTE), 1, FP);
            fwrite(&image_array[i * infohead->biWidth * 3 + j * 3 + 1], sizeof(BYTE), 1, FP);
            fwrite(&image_array[i * infohead->biWidth * 3 + j * 3 + 2], sizeof(BYTE), 1, FP);
        }
        if (off != 0)
        {
            for (k = 0;k < off;k++)
            {
                fwrite(&zero_fill, sizeof(BYTE), 1, FP);  //每行为了成为4字节的整数倍补的零字节
            }
        }
    }
    fclose(FP);
}

上一篇:typedef,文件操作


下一篇:Python文件处理简介