接上期图片处理后,还有图像进行色彩空间矩阵变换,伽马矫正,白平衡没讲。本期就来说说这三个操作对图片的处理作用以及效果。
白平衡:
白平衡是指相机或摄像机为了使图像中的白色看起来是真正的白色,需要对相机的感光元件进行调节的过程。白平衡的主要作用是消除光源色温对图像色彩的影响,确保图像中的白色看起来是真正的白色,从而使整个图像的色彩更加准确和自然。如果白平衡设置不正确,图像中的颜色就会偏色,影响图像的色彩还原效果。
白平衡有多种算法,可以在RAW域算白平衡系数,也可以在RGB域算白平衡系数,白平衡有两个系数GainA,GainB。白平衡的GainA和GainB是指在进行白平衡调节时,调整红色和蓝色通道的增益值。GainA代表红色通道的增益值,GainB代表蓝色通道的增益值。通过调整这两个通道的增益值,可以使得白色看起来更加真实自然,从而达到更好的色彩还原效果。 在进行白平衡调节时,相机会根据所拍摄的场景的光源色温来自动调整GainA和GainB的值。对于不同的光源,需要进行不同的白平衡调节来达到最佳的色彩还原效果。
例如,在拍摄日落时,为了强调日落的暖色调,可以将色温调低,使图像呈现出比较暖的色调;在拍摄蓝天白云的风景时,为了突出蓝色的色调,可以将色温调高,使图像呈现出比较冷的色调。
下面我们就来说说白平衡系数的计算,这里采用的方法是在RGB域计算,然后再返回RAW域,对RAW域图像进行白平衡处理。下面请看白平衡的计算方法。
第一步:求出每个点的亮度Y
第二步:对Y进行分类求权重,不同亮度所占的百分比可以自己调整
第三步:判断它是否接近灰色
第四步:统计灰色的点求 GainR GainB
由下面代码可以看出是西安调到RGB域去算出白平衡系数,然后再次跳回到RAW域算白平衡。下面是主函数的部分代码:
#if 1
image = thread_read_raw(RAW_FILE_NAME,5600,5600);//读取一张图片
seek_bad_Pixel(image);//去坏点
Black_Level_Correction_respective(image);//四通道黑电平
Shadow_Correction(image, 0.2);//阴影矫正
vector<vector<Pixel>> RGB = color_interpolation(image);//跳转到RGB域
Return_GainR_GrainB RB = calculate_white_balance_nums(RGB);//获取白平衡系数
white_balance(RB,image);//用拿到的系数做白平衡
RGB = color_interpolation(image);//重新跳转到RGB域
//matmul3x3_3x1(RGB); //色彩空间矩阵校正
//reduce_red(RGB);//去过曝光过度产生的红斑
//Gamma_correction(RGB,1.1); //伽马矫正
//cruculation_R_G_B(RGB);
//cout << RB.GainB << " " << RB.GainR <<endl;
#endif
#if 1
Raw_to_Bmp_Pixel16_Enablelow10(RGB,timer(BMP_FILE_NAME,(const char *)"bmp"));
#endif
白平衡参数计算函数:
Return_GainR_GrainB calculate_white_balance_nums(vector<vector<Pixel>> &image)
{
// 开辟返回值结构体
Return_GainR_GrainB return_GB;
// 算出传入RGB数组的大小
int width = image.size(), height = image[0].size();
// 返回一个八位的值
uint8_t balance_num = 0;
double GainR = 0, GainB = 0;
// 64位累加x像素值防止溢出 开辟三个结构体 存三组数据总和
RGB_total rgb_total[3];
rgb_total[0].total = 0, rgb_total[1].total = 0, rgb_total[2].total = 0;
rgb_total[0].R_total = 0, rgb_total[1].R_total = 0, rgb_total[2].R_total = 0;
rgb_total[0].G_total = 0, rgb_total[1].G_total = 0, rgb_total[2].G_total = 0;
rgb_total[0].B_total = 0, rgb_total[1].B_total = 0, rgb_total[2].B_total = 0;
// memset(&rgb_total, 0, sizeof(rgb_total)*3);
// 创建VUV分量数组
vector<vector<YUV_float>> yuvImage(width, vector<YUV_float>(height));
// 循环遍历所有像素
// 横轴
int count = 0;
for (int i = 0; i < width; i++)
{
// 竖轴
for (int j = 0; j < height; j++)
{
// 对每个像素利用矩阵算出它的 Y 值并且存入YUV_float
yuvImage[i][j].YUV_y = (double)Y((double)image[i][j].r, (double)image[i][j].g, (double)image[i][j].b);
// cout << yuvImage[i][j].YUV_y << " " <<(double)image[i][j].r << " " <<(double)image[i][j].g << " "<<(double)image[i][j].b<< endl;
// cout << (double)image[i][j].r /(double)image[i][j].g <<" " <<(double)image[i][j].b /(double)image[i][j].g<<endl;
// cout << (double)((double)image[i][j].r / yuvImage[i][j].YUV_y) <<" " <<(double)((double)image[i][j].b / yuvImage[i][j].YUV_y)<<endl;
// 判断灰度范围
// 改成大于0.6左右 R/G约等于0.6
if (yuvImage[i][j].YUV_y != 0)
{
// cout << ((double)image[i][j].r / (double)image[i][j].g) << endl;
// if (((double)((double)image[i][j].r / (double)image[i][j].g) > R_Y) && ((double)((double)image[i][j].b / (double)image[i][j].g) > B_Y))
if (((yuvImage[i][j].YUV_u < 30) && (yuvImage[i][j].YUV_u > -30)) && ((yuvImage[i][j].YUV_v > -30) && (yuvImage[i][j].YUV_v < 30)))
{
// 判断亮度范围
if ((yuvImage[i][j].YUV_y >= 0x40) && (yuvImage[i][j].YUV_y < 0xc0)) // 128±64
{
yuvImage[i][j].type = 64;
if ((yuvImage[i][j].YUV_y >= 0x58) && (yuvImage[i][j].YUV_y < 0xa8)) // 128±40
{
yuvImage[i][j].type = 40;
if ((yuvImage[i][j].YUV_y >= 0x6c) && (yuvImage[i][j].YUV_y < 0x94)) // 128±20
{
yuvImage[i][j].type = 20;
}
}
}
}
}
// 判断类型并且累计
if (yuvImage[i][j].type == 64)
{
rgb_total[0].R_total += image[i][j].r;
rgb_total[0].G_total += image[i][j].g;
rgb_total[0].B_total += image[i][j].b;
rgb_total[0].total++;
count++;
}
if (yuvImage[i][j].type == 40)
{
rgb_total[1].R_total += image[i][j].r;
rgb_total[1].G_total += image[i][j].g;
rgb_total[1].B_total += image[i][j].b;
rgb_total[1].total++;
}
if (yuvImage[i][j].type == 20)
{
rgb_total[2].R_total += image[i][j].r;
rgb_total[2].G_total += image[i][j].g;
rgb_total[2].B_total += image[i][j].b;
rgb_total[2].total++;
}
}
}
// cout << rgb_total[0].total <<endl;
// cout << rgb_total[1].total <<endl;
// cout << rgb_total[2].total <<endl;
// cout << rgb_total[0].R_total << " " << rgb_total[0].G_total << " " << rgb_total[0].B_total << " " << rgb_total[0].total << endl;
// cout << rgb_total[1].R_total << " " << rgb_total[1].G_total << " " << rgb_total[1].B_total << " " << rgb_total[1].total << endl;
// cout << rgb_total[2].R_total << " " << rgb_total[2].G_total << " " << rgb_total[2].B_total << " " << rgb_total[2].total << endl;
// 求平均值 128±64
rgb_total[0].avg_R = (double)rgb_total[0].R_total / (double)rgb_total[0].total;
rgb_total[0].avg_G = (double)rgb_total[0].G_total / (double)rgb_total[0].total;
rgb_total[0].avg_B = (double)rgb_total[0].B_total / (double)rgb_total[0].total;
// // 128±40
rgb_total[1].avg_R = (double)rgb_total[1].R_total / (double)rgb_total[1].total;
rgb_total[1].avg_G = (double)rgb_total[1].G_total / (double)rgb_total[1].total;
rgb_total[1].avg_B = (double)rgb_total[1].B_total / (double)rgb_total[1].total;
// // 128±20
rgb_total[2].avg_R = (double)rgb_total[2].R_total / (double)rgb_total[2].total;
rgb_total[2].avg_G = (double)rgb_total[2].G_total / (double)rgb_total[2].total;
rgb_total[2].avg_B = (double)rgb_total[2].B_total / (double)rgb_total[2].total;
// // // 配置权重 对64配置
rgb_total[0].avg_R = rgb_total[0].avg_R * 0.2;
rgb_total[0].avg_G = rgb_total[0].avg_G * 0.2;
rgb_total[0].avg_B = rgb_total[0].avg_B * 0.2;
// 对40配置
rgb_total[1].avg_R = rgb_total[1].avg_R * 0.5;
rgb_total[1].avg_G = rgb_total[1].avg_G * 0.5;
rgb_total[1].avg_B = rgb_total[1].avg_B * 0.5;
// 求平均
GainR = (rgb_total[0].avg_G + rgb_total[1].avg_G + rgb_total[2].avg_G) / (rgb_total[0].avg_R + rgb_total[1].avg_R + rgb_total[2].avg_R);
GainB = (rgb_total[0].avg_G + rgb_total[1].avg_G + rgb_total[2].avg_G) / (rgb_total[0].avg_B + rgb_total[1].avg_B + rgb_total[2].avg_B);
// 返回值
return_GB.GainR = GainR;
return_GB.GainB = GainB;
return return_GB;
}
白平衡操作:
void white_balance(Return_GainR_GrainB RB, vector<vector<uint16_t>> &image)
{
int height = image.size(), width = image[0].size(), i = 0, j = 0;
for (i = 0; i < height; i++)
{
for (j = 0; j < width; j++)
{
bool Red = (i % 2 == 1) && (j % 2 == 0);
bool Blue = (i % 2 == 0) && (j % 2 == 1);
if (Red)
{
image.at(i).at(j) = image.at(i).at(j) * RB.GainB;
}
if (Blue)
{
image.at(i).at(j) = image.at(i).at(j) * RB.GainR;
}
if (image.at(i).at(j) > 0x3ff)
{
image.at(i).at(j) = 0x3ff;
}
}
}
}
头文件:
/**
* @author. zhl 2023-4-11
* @brief RGB_YUV_H 头文件
* @note 以下这个头文件存放RAM——RGB变幻的一些需要的参数
*/
#ifndef RGB_YUV_H
#define RGB_YUV_H
#include <iostream>
#include <stdint.h>
#include <stdlib.h>
#include <vector>
#include <cmath>
using namespace std;
// 定义RGB图像的像素结构体
struct Pixel
{
uint8_t r;
uint8_t g;
uint8_t b;
};
struct YUV_float
{
double YUV_y;
double YUV_u;
double YUV_v;
int type;
};
//JPEG的亮度量化和色度量化函数 传入
struct Ycrcb
{
uint8_t y;
uint8_t cr;
uint8_t cb;
};
struct RGB_total
{
uint64_t R_total;
uint64_t G_total;
uint64_t B_total;
double avg_R;
double avg_G;
double avg_B;
int total;
};
struct Return_GainR_GrainB
{
double GainR;
double GainB;
};
struct YUYV
{
uint8_t Y;
uint8_t U;
uint8_t Y_;
uint8_t V;
};
const vector<vector<int>> LuminanceQuantizationTable = {
{ 16, 11, 10, 16, 24, 40, 51, 61 },
{ 12, 12, 14, 19, 26, 58, 60, 55 },
{ 14, 13, 16, 24, 40, 57, 69, 56 },
{ 14, 17, 22, 29, 51, 87, 80, 62 },
{ 18, 22, 37, 56, 68, 109, 103, 77 },
{ 24, 35, 55, 64, 81, 104, 113, 92 },
{ 49, 64, 78, 87, 103, 121, 120, 101 },
{ 72, 92, 95, 98, 112, 100, 103, 99 }
};
const vector<vector<int>> ChrominanceQuantizationTable = {
{ 17, 18, 24, 47, 99, 99, 99, 99 },
{ 18, 21, 26, 66, 99, 99, 99, 99 },
{ 24, 26, 56, 99, 99, 99, 99, 99 },
{ 47, 66, 99, 99, 99, 99, 99, 99 },
{ 99, 99, 99, 99, 99, 99, 99, 99 },
{ 99, 99, 99, 99, 99, 99, 99, 99 },
{ 99, 99, 99, 99, 99, 99, 99, 99 },
{ 99, 99, 99, 99, 99, 99, 99, 99 }
};
//DTC 量化啊表
// vector<vector<int>> q = {
// {16, 11, 10, 16, 24, 40, 51, 61},
// {12, 12, 14, 19, 26, 58, 60, 55},
// {14, 13, 16, 24, 40, 57, 69, 56},
// {14, 17, 22, 29, 51, 87, 80, 62},
// {18, 22, 37, 56, 68, 109, 103, 77},
// {24, 35, 55, 64, 81, 104, 113, 92},
// {49, 64, 78, 87, 103, 121, 120, 101},
// {72, 92, 95, 98, 112, 100, 103, 99}
// };
/*
1.546875 -0.397460938 -0.149414063
-0.247070313 1.258789063 -0.01171875
-0.102539063 -0.844726563 1.947265625
*/
//颜色矩阵
// 白平衡色度权重
#define Y128_Deviation_20_Weight 1
#define Y128_Deviation_40_Weight 0.5
#define Y128_Deviation_64_Weight 0.2
// RGBtoYUV
#define Y(r, g, b) 0.299 * (r)+0.587 * (g)+0.114 * (b)
#define U(r, g, b) -0.147 * (r)-0.298 * (g)+0.436 * (b)
#define V(r, g, b) 0.615 * (r)-0.515 * (g)-0.100 * (b)
// YUVtoRGB
#define R(y, v) (y) + 1.140 * (v)
#define G(y, u, v) (y) - 0.395 * (u)-0.581 * (v)
#define B(y, u) (y) + 2.032 * (u)
// 白平衡灰度判断标准
#define R_Y 0.60
#define B_Y 0.60
// UV标准判断
#define U_V 0.15
// 大小端转换,写成内联函数效率高
inline uint16_t swap_endian(uint16_t num)
{
return (num >> 8) | (num << 8);
}
#endif
下面是经过白平衡处理的图片,但是因为又亮度过高,有些地方回出现红斑,这个我们后面来处理,已经接近实际场景了。
色彩空间矩阵矫正:
色彩矫正是一种用于改善图像色彩的处理方法,其主要作用是消除图像中的色彩偏差,使图像的色彩更加准确、自然、真实。色彩偏差通常是由于光源、相机、显示器等硬件设备的差异或者环境因素等引起的。
- 消除色彩偏差:色彩矫正可以消除相机、显示器等设备造成的色彩偏差,使图像的色彩更加准确和自然。例如,可以通过白平衡矫正消除图像中的色温偏差,使图像中的白色看起来真正的白色。
- 提高图像质量:色彩矫正可以改善图像的色彩效果,使图像更加清晰、明亮、鲜艳,从而提高图像的质量和视觉效果。
- 统一色彩风格:对于一些需要保持一致色彩风格的场景,如商业广告、产品摄影等,色彩矫正可以使图像的色彩保持一致,达到统一的色彩风格。
- 降低色彩误差:色彩矫正可以降低图像中的色彩误差,从而提高图像的色彩还原度和准确性,使得图像更加符合人眼的视觉感受。 综上所述,色彩矫正在数字图像处理中具有重要的作用,可以消除图像中的色彩偏差,改善图像的色彩效果,提高图像质量和准确性。
可能保密,所以我删掉了精度,下面是代码:
void matmul3x3_3x1(vector<vector<Pixel>> &mat3x1)
{
// 检查输入矩阵的形状是否正确
vector<vector<double>> mat3x3(3, vector<double>(3));
mat3x3[0][0] = 1.5, mat3x3[0][1] = -0.3, mat3x3[0][2] = -0.1;
mat3x3[1][0] = -0.2, mat3x3[1][1] = 1.2, mat3x3[1][2] = -0.01;
mat3x3[2][0] = -0.1, mat3x3[2][1] = -0.8, mat3x3[2][2] = 1.9;
// 对每一个像素计算矩阵乘积
for (int i = 0; i < mat3x1.size(); i++)
{
for (int j = 0; j < mat3x1[0].size(); j++)
{
int number = (10 * (double)mat3x1[i][j].r * mat3x3[0][0]) / 10 + (10 * (double)mat3x1[i][j].g * mat3x3[0][1]) / 10 + (10 * (double)mat3x1[i][j].b * mat3x3[0][2]) / 10;
}
}
}
正在图片看起来略微的变得更加透亮。
伽马矫正:
伽马矫正是数字图像处理中常用的一种技术,其主要作用是对图像的亮度进行调整,使其在不同的显示设备上呈现出相同的亮度级别,从而达到更加准确和自然的显示效果。伽马矫正的意义主要有以下几点:
- 保证图像的亮度一致性:由于不同的显示设备具有不同的亮度响应曲线,同一个图像在不同设备上的亮度表现会有所不同。伽马矫正可以使图像的亮度表现在不同设备上更加一致,确保图像在不同设备上呈现出相同的亮度级别。
- 提高图像对比度:伽马矫正可以调整图像的亮度,使得图像中的细节更加清晰、明亮,从而提高图像的对比度和视觉效果。
- 改善图像的色彩鲜艳度:伽马矫正可以使图像中的颜色更加鲜艳、真实,从而提高图像的色彩还原度和准确性。
- 适应人眼的视觉特性:人眼对亮度的感受是非线性的,伽马矫正可以使图像的亮度响应与人眼的视觉特性相适应,使图像看起来更加自然和舒适。 综上所述,伽马矫正在数字图像处理中具有重要的意义,可以提高图像的亮度一致性、对比度和色彩鲜艳度,适应人眼的视觉特性,从而达到更加准确、自然和舒适的显示效果。
下面是代码:
vector<vector<Pixel>> Gamma_correction(vector<vector<Pixel>> &image, float gamma)
{
for (int i = 0; i < image.size(); i++)
{
for (int j = 0; j < image[i].size(); j++)
{
/*
/ 255.0f 表示将该像素值除以 255.0,将像素值归一化到 [0, 1] 的范围内。
这一步是为了将像素值转化为浮点类型,方便进行伽马矫正运算。
pow(image[i][j].r / 255.0f, gamma) 表示对归一化后的像素值进行伽马矫正运算,其中 pow() 函数表示对指定数值取幂。
第一个参数是要取幂的数值,第二个参数是幂指数,即伽马值。
*/
image[i][j].r = pow((double)image[i][j].r / 255.0f, gamma) * 255.0f;
image[i][j].g = pow((double)image[i][j].g / 255.0f, gamma) * 255.0f;
image[i][j].b = pow((double)image[i][j].b / 255.0f, gamma) * 255.0f;
}
}
return image;
}
下面是矫正后的图片,可以看见整张图片的对比度变高了。
去红:
可以看出,图片高亮部分存在红色,现在写代码将其去除。
void reduce_red(vector<vector<Pixel>> &RGB)
{
int height = RGB.size(), width = RGB[0].size(), i = 0, j = 0;
for (i = 0; i < height; i++)
{
for (j = 0; j < width; j++)
{
if(Y(RGB[i][j].r,RGB[i][j].g,RGB[i][j].b)>230)
{
RGB[i][j].r = RGB[i][j].g;
RGB[i][j].b = RGB[i][j].g;
}
}
}
}
可以看见红色减少了,但是总体来看,整张图片偏暗,这里吧伽马矫正的系数从1.5降低到1.1试试。让我们来看看调整过后的图片。
这就是最终的图片啦。还是比较不错的!
整个ISP大概的简单流程就已经结束啦,实际上ISP算法非常复杂,作者也是模仿简单化的处理,具体的还得问专业人士!