最简色彩平衡之加速算法

关于BMP文件

  • BMP文件包括以下几部分:
    文件头、信息头、调色板、图像数据
  • 文件头一般14个字节
  • 信息头一般40个字节
  • 彩色图像一般无调色板,灰度图像调色板一般为
    unsigned char clrPal[1024] = {0,0,0,0, 1,1,1,0, …, 255,255,255,0}

文件头

Field NameSize in BytesDescription
bfType2The characters “BM”
bfSize4The size of the file in bytes (bfOffBits+biSizeImage)
bfReserved12Unused - must be zero (0)
bfReserved22Unused - must be zero (0)
bfOffBits4Offset to start of Pixel Data (灰度图像 54+1024, 彩色图像 54)

图片信息头

Field NameSize in BytesDescription
biSize4Header Size - Must be at least 40 (40)
biWidth4Image width in pixels
biHeight4Image height in pixels
biPlanes2Must be 1 (1)
biBitCount2Bits per pixel - 1, 4, 8(灰度图像), 16, 24(彩色图像), or 32
biCompression4Compression type (0 = uncompressed)
biSizeImage4Image Size ( 灰度图像 ⌈biWidth/4⌉×4×biHeight )
biXPelsPerMeter4Preferred resolution in pixels per meter (0)
biYPelsPerMeter4Preferred resolution in pixels per meter (0)
biClrUsed4Number Color Map entries that are actually used (灰度256, 彩色0)
biClrImportant4Number of significant colors (0)

图像数据的表示

灰度图像

  • 表示为二维数组,数组元素称为像素,像素值为0~255的整数(0最暗, 255最亮)
  • 数组行数等于图像高度,数组列数等于图像宽度
  • 实际采用一维数组存储,逐行存储各像素,先存最后行;每行元素个数必为4的倍数,否则补充1~3个元素使满足此条件
    彩色图像
  • 等价于三个灰度图像,对应蓝绿红三原色
  • 表示为“二维数组” ,数组元素(即像素)为三个0~255的整数
  • 数组行数等于图像高度,数组列数等于图像宽度
  • 实际采用一维数组存储,逐行存储各像素(每个像素表示为3个0~ 255的整数);每行元素个数必为4的倍数,否则补充1~3个元素使满足此条件。
文件示例

在这里插入图片描述

最简色彩平衡算法

  • 像素 d a t a [ i ∗ s t r i d e + j ] data[i*stride+j] data[istride+j]平衡后为 d a t a X [ i ∗ s t r i d e + j ] dataX[i*stride+j] dataX[istride+j],算法如下:
  1. 对图像data的所有像素进行升序排序,得序列x
  2. 将序列头部%s的像素修改为0,尾部%t的像素修改为255,其中s和t一般相等,测试时建议取值为2,4,8,16
  3. 令其它(100-s-t)%的像素中,最小值为minV,最大值为maxV,那么对于其中的像素data[i*stride+j],修改后的值为:
    d a t a X [ i ∗ s t r i d e + j ] = 255 × d a t a [ i ∗ s t r i d e + j ] − m i n V m a x V − m i n V dataX[i*stride+j]=255×\frac {data[ i*stride+j ] -minV} {maxV-minV} dataX[istride+j]=255×maxVminVdata[istride+j]minV
  • 上面处理的是灰度图像,彩色图像则三原色分别如上处理。

最简色彩平衡之加速算法

上页算法的核心是计算minV和maxV,但涉及排序太耗时。

  • 加速算法如下:
  1. 对于图像的所有可能的像素值,即0,1,2,…,255,统计每个值出现的次数,保存在数组P(double P[256];)
  2. 计算每个像素值出现的频率,仍保存在数组P,像素值k出现的频率为P[k]=P[k]/count,其中count为图像包含的像素总数
  3. 满足下面条件的,最小的k,即为minV:
    P [ 0 ] + P [ 1 ] + P [ 2 ] + ⋯ + P [ k − 1 ] + P [ k ] ≥ s % P[0]+P[1]+P[2]+⋯+P[k−1]+P[k]≥s\% P[0]+P[1]+P[2]++P[k1]+P[k]s%
  4. 满足下面条件的,最大的k,即为maxV:
    P [ k ] + P [ k + 1 ] + ⋯ + P [ 253 ] + P [ 254 ] + P [ 255 ] ≥ t % P[k]+P[k+1]+⋯+P[253]+P[254]+P[255]≥t\% P[k]+P[k+1]++P[253]+P[254]+P[255]t%

效果对比

黑白

黑白照片色彩平衡之后效果对比

彩色

彩色照片色彩平衡之后效果对比

实现代码1

balance.h

# ifndef BMP_H
# define BMP_H
/*
BMP格式
这种格式内的数据分为三到四个部分,依次是:
文件信息头 (14字节)存储着文件类型,文件大小等信息
图片信息头 (40字节)存储着图像的尺寸,颜色索引,位平面数等信息
调色板 (由颜色索引数决定)【可以没有此信息】
位图数据 (由图像尺寸决定)每一个像素的信息在这里存储

一般的bmp图像都是24位,也就是真彩。每8位为一字节,24位也就是使用三字节来存储每一个像素的信息,三个字节对应存放r,g,b三原色的数据,
每个字节的存贮范围都是0-255。那么以此类推,32位图即每像素存储r,g,b,a(Alpha通道,存储透明度)四种数据。8位图就是只有灰度这一种信息,
还有二值图,它只有两种颜色,黑或者白。
*/
// 文件信息头结构体
typedef struct tagBITMAPFILEHEADER 
{
    // unsigned short bfType;        // 19778,必须是BM字符串,对应的十六进制为0x4d42,十进制为19778,否则不是bmp格式文件
    unsigned int   bfSize;        // 文件大小 以字节为单位(2-5字节)
    unsigned short bfReserved1;   // 保留,必须设置为0 (6-7字节)
    unsigned short bfReserved2;   // 保留,必须设置为0 (8-9字节)
    unsigned int   bfOffBits;     // 从文件头到像素数据的偏移  (10-13字节)
} BITMAPFILEHEADER;

//图像信息头结构体
typedef struct tagBITMAPINFOHEADER 
{
    unsigned int    biSize;          // 此结构体的大小 (14-17字节)
    long            biWidth;         // 图像的宽  (18-21字节)
    long            biHeight;        // 图像的高  (22-25字节)
    unsigned short  biPlanes;        // 表示bmp图片的平面属,显然显示器只有一个平面,所以恒等于1 (26-27字节)
    unsigned short  biBitCount;      // 一像素所占的位数,一般为24   (28-29字节)
    unsigned int    biCompression;   // 说明图象数据压缩的类型,0为不压缩。 (30-33字节)
    unsigned int    biSizeImage;     // 像素数据所占大小, 这个值应该等于上面文件头结构中bfSize-bfOffBits (34-37字节)
    long            biXPelsPerMeter; // 说明水平分辨率,用象素/米表示。一般为0 (38-41字节)
    long            biYPelsPerMeter; // 说明垂直分辨率,用象素/米表示。一般为0 (42-45字节)
    unsigned int    biClrUsed;       // 说明位图实际使用的彩色表中的颜色索引数(设为0的话,则说明使用所有调色板项)。 (46-49字节)
    unsigned int    biClrImportant;  // 说明对图象显示有重要影响的颜色索引的数目,如果是0,表示都重要。(50-53字节)
} BITMAPINFOHEADER;

//24位图像素信息结构体,即调色板
typedef struct _PixelInfo {
    unsigned char rgbBlue;   //该颜色的蓝色分量  (值范围为0-255)
    unsigned char rgbGreen;  //该颜色的绿色分量  (值范围为0-255)
    unsigned char rgbRed;    //该颜色的红色分量  (值范围为0-255)
    unsigned char rgbReserved;// 保留,必须为0
} PixelInfo;

#endif

背白照片实现:balance.c

#include<stdio.h>
#include <malloc.h>
#include "balance.h"


BITMAPFILEHEADER fileHeader;
BITMAPINFOHEADER infoHeader;


void showBmpHead(BITMAPFILEHEADER pBmpHead) 
{  //定义显示信息的函数,传入文件头结构体
    printf("BMP file size: %dkb\n", fileHeader.bfSize/1024);
    printf("reserved1 0: %d\n",  fileHeader.bfReserved1);
    printf("reserved2 0: %d\n",  fileHeader.bfReserved2);
    printf("off bit: %d\n",  fileHeader.bfOffBits);
}
void showBmpInfoHead(BITMAPINFOHEADER pBmpinfoHead) 
{//定义显示信息的函数,传入的是信息头结构体
   printf("info head:\n" );   
   printf("info size:%d\n" ,infoHeader.biSize);   
   printf("biWidth:%d\n" ,infoHeader.biWidth);   
   printf("biHeight:%d\n" ,infoHeader.biHeight);   
   printf("biPlanes:%d\n" ,infoHeader.biPlanes);   
   printf("biBitCount:%d\n" ,infoHeader.biBitCount);   
   printf("biCompression:%d\n" ,infoHeader.biCompression);   
   printf("biSizeImage:%d\n" ,infoHeader.biSizeImage);   
   printf("biXPelsPerMeter:%d\n" ,infoHeader.biXPelsPerMeter);   
   printf("biYPelsPerMeter:%d\n" ,infoHeader.biYPelsPerMeter);   
   printf("biClrUsed:%d\n" ,infoHeader.biClrUsed);   
   printf("biClrImportant:%d\n" ,infoHeader.biClrImportant);   
}

int main()
{
    FILE* fp;    
    fp = fopen("picture/bwp1.bmp", "rb");//读取同目录下的bmp文件。
    if(fp == NULL)
    {
        printf("open succussful!\n");
        return -1;
    }
    //如果不先读取bifType,根据C语言结构体Sizeof运算规则——整体大于部分之和,从而导致读文件错位
    unsigned short  fileType;

    PixelInfo clrPal[256];
    

    fread(&fileType,1,sizeof (unsigned short), fp);  
    if (fileType = 0x4d42)   
    {   
        printf("file type ok!" );  
        printf("\nfile type is: %d\n", fileType); 
        fread(&fileHeader, 1, sizeof(BITMAPFILEHEADER), fp);
        showBmpHead(fileHeader);
        fread(&infoHeader, 1, sizeof(BITMAPINFOHEADER), fp);
        showBmpInfoHead(infoHeader);
        fread(&clrPal, 256, sizeof(PixelInfo), fp);
        unsigned char data[infoHeader.biSizeImage];
        unsigned char new_data[infoHeader.biSizeImage];

        fread(&data, 1, sizeof(data), fp);
        fclose(fp);
        int i, j;
        double P[256];
        
        //初始化
        for(i = 0; i<256;i++)
        {
            P[i] = 0;
        }
        int stride = infoHeader.biSizeImage/infoHeader.biHeight;
        int count = 0;
        for( i = 0; i<infoHeader.biHeight; i++){
            for ( j = 0;j < infoHeader.biWidth; j++){
                P[data[i*stride+j]] += 1;
                count += 1;
            }
        }
        // 获取频率
        for(i = 0; i<256;i++)
            P[i] /= count;
        
        // 设置参数
        int s = 10;
        double sp = s*0.01; 
        int t = 10;
        double tp = t*0.01; 
        double sum = 0;
        int minV, maxV;
        for (i=0;i<256;i++)
        {
            sum += P[i];
            if(sum>=sp){
                minV = i;
                break;
            }        
        }
        sum = 0;
        for (i=255;i>-1;i--)
        {
            sum += P[i];
            if(sum>=tp){
                maxV = i;
                break;
            }        
        }

        for( i = 0; i<infoHeader.biHeight; i++){
            for ( j = 0;j < infoHeader.biWidth; j++){
                if(data[i*stride+j]<minV)
                    new_data[i*stride+j] = 0;
                else if (data[i*stride+j]<=maxV)
                    new_data[i*stride+j] = (int)(255 * ((double)(data[i*stride+j] - minV)/(maxV- minV)));
                else
                    new_data[i*stride+j] = 255;            
            }
        }
        FILE *wfp= fopen("picture/bwp3.bmp", "wb");
        printf("%d\n", sizeof(fileType));
        fwrite(&fileType, sizeof(fileType) , 1, wfp);
        printf("%d\n", sizeof(fileHeader));
        fwrite(&fileHeader, sizeof(fileHeader) , 1, wfp);
        printf("%d\n", sizeof(infoHeader));
        fwrite(&infoHeader, sizeof(infoHeader) , 1, wfp);
        printf("%d\n", sizeof(clrPal));
        fwrite(&clrPal, sizeof(clrPal) , 1, wfp);
        printf("%d\n", sizeof(new_data));
        fwrite(&new_data, sizeof(new_data) , 1, wfp);

        fclose(wfp);
    }
}

彩色照片实现:balance_cp.c

#include<stdio.h>
#include <malloc.h>
#include "balance.h"


BITMAPFILEHEADER fileHeader;
BITMAPINFOHEADER infoHeader;


void showBmpHead(BITMAPFILEHEADER pBmpHead) 
{  //定义显示信息的函数,传入文件头结构体
    printf("BMP file size: %dkb\n", fileHeader.bfSize/1024);
    printf("reserved1 0: %d\n",  fileHeader.bfReserved1);
    printf("reserved2 0: %d\n",  fileHeader.bfReserved2);
    printf("off bit: %d\n",  fileHeader.bfOffBits);
}
void showBmpInfoHead(BITMAPINFOHEADER pBmpinfoHead) 
{//定义显示信息的函数,传入的是信息头结构体
   printf("info head:\n" );   
   printf("info size:%d\n" ,infoHeader.biSize);   
   printf("biWidth:%d\n" ,infoHeader.biWidth);   
   printf("biHeight:%d\n" ,infoHeader.biHeight);   
   printf("biPlanes:%d\n" ,infoHeader.biPlanes);   
   printf("biBitCount:%d\n" ,infoHeader.biBitCount);   
   printf("biCompression:%d\n" ,infoHeader.biCompression);   
   printf("biSizeImage:%d\n" ,infoHeader.biSizeImage);   
   printf("biXPelsPerMeter:%d\n" ,infoHeader.biXPelsPerMeter);   
   printf("biYPelsPerMeter:%d\n" ,infoHeader.biYPelsPerMeter);   
   printf("biClrUsed:%d\n" ,infoHeader.biClrUsed);   
   printf("biClrImportant:%d\n" ,infoHeader.biClrImportant);   
}

int main()
{
    FILE* fp;    
    fp = fopen("picture/cp1.bmp", "rb");//读取同目录下的bmp文件。
    if(fp == NULL)
    {
        printf("open succussful!\n");
        return -1;
    }
    //如果不先读取bifType,根据C语言结构体Sizeof运算规则——整体大于部分之和,从而导致读文件错位
    unsigned short  fileType;
    

    fread(&fileType,1,sizeof (unsigned short), fp);  
    if (fileType = 0x4d42)   
    {   
        printf("file type ok!" );  
        printf("\nfile type is: %d\n", fileType); 
        fread(&fileHeader, 1, sizeof(BITMAPFILEHEADER), fp);
        showBmpHead(fileHeader);
        fread(&infoHeader, 1, sizeof(BITMAPINFOHEADER), fp);
        showBmpInfoHead(infoHeader);
        unsigned char data[infoHeader.biSizeImage];
        unsigned char new_data[infoHeader.biSizeImage];

        fread(&data, 1, sizeof(data), fp);
        fclose(fp);
        int i, j;
        double P[3][256];
        
        //初始化
        for(i = 0; i<3;i++)
            for(j = 0; j<256;j++)
        {
            P[i][j] = 0;
        }
        int stride = infoHeader.biSizeImage/infoHeader.biHeight;
        int count = 0;
        for( i = 0; i<infoHeader.biHeight; i++){
            for ( j = 0;j < infoHeader.biWidth; j++){
                P[0][data[i*stride+3*j]] += 1;
                P[1][data[i*stride+3*j+1]] += 1;
                P[2][data[i*stride+3*j+2]] += 1;
                count += 1;
            }
        }
        // 获取频率
        for(i = 0; i<256;i++)
        {
            P[0][i] /= count;
            P[1][i] /= count;
            P[2][i] /= count;

        }
        
        // 设置参数
        int s = 10;
        double sp = s*0.01; 
        int t = 10;
        double tp = t*0.01; 
        double sum = 0;
        int minV[3] = {0, 0, 0};
        int maxV[3] = {0, 0, 0};
        for (j = 0; j < 3; j++)
        {
            sum = 0;
            for (i=0;i<256;i++)
            {
                sum += P[j][i];
                if(sum>=sp){
                    minV[j] = i;
                    break;
                }        
            }
        }
        

        for (j = 0; j < 3; j++)
        {
            sum = 0;
            for (i=255;i>-1;i--)
            {
                sum += P[j][i];
                if(sum>=tp){
                    maxV[j] = i;
                    break;
                }        
            }
        }
        int k;
        for( i = 0; i<infoHeader.biHeight; i++){
            for ( j = 0;j < infoHeader.biWidth; j++){
                for (k = 0; k < 3; k++)
                {
                    if(data[i*stride+3*j+k] < minV[k])
                        new_data[i*stride+3*j+k] = 0;
                    else if (data[i*stride+3*j+k]<= maxV[k])
                        new_data[i*stride+3*j+k] = (int)(255 * ((double)(data[i*stride+3*j+k] - minV[k])/(maxV[k]- minV[k])));
                    else
                        new_data[i*stride+3*j+k] = 255; 
                }
                           
            }
        }
        
        FILE *wfp= fopen("picture/cp2.bmp", "wb");
        printf("%d\n", sizeof(fileType));
        fwrite(&fileType, sizeof(fileType) , 1, wfp);
        printf("%d\n", sizeof(fileHeader));
        fwrite(&fileHeader, sizeof(fileHeader) , 1, wfp);
        printf("%d\n", sizeof(infoHeader));
        fwrite(&infoHeader, sizeof(infoHeader) , 1, wfp);
        printf("%d\n", sizeof(new_data));
        fwrite(&new_data, sizeof(new_data) , 1, wfp);
        fclose(wfp);
    }
}


  1. 参考文章:https://blog.csdn.net/qq_39400113/article/details/104750460 ↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值