接上期图片处理后,还有图像进行色彩空间矩阵变换,伽马矫正,白平衡没讲。本期就来说说这三个操作对图片的处理作用以及效果。
白平衡:
白平衡是指相机或摄像机为了使图像中的白色看起来是真正的白色,需要对相机的感光元件进行调节的过程。白平衡的主要作用是消除光源色温对图像色彩的影响,确保图像中的白色看起来是真正的白色,从而使整个图像的色彩更加准确和自然。如果白平衡设置不正确,图像中的颜色就会偏色,影响图像的色彩还原效果。
白平衡有多种算法,可以在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算法非常复杂,作者也是模仿简单化的处理,具体的还得问专业人士!