对于特定光源下拍摄产生的色偏现象通过加强对应补色进行白色复原。
三种方法:1. 灰度世界 2. 完美反射 3.动态阈值
灰度世界:
原始gray world认为对于一幅有着大量的色彩变化的图像,其RGB三个分量的平均值趋于同一个灰度K,一般有两种方法确定该灰度:
1. 直接取127或128
2. RGB通道平均值的平均值
一般采用2.
流程为:
1. 计算RGB通道平均值的平均值
2. 计算各通道的增益,各通道的增益为——K/各通道平均值
3. 根据通道增益确定每个像素映射,例如Rnew = R * R增益
4. 如果像素溢出,1. 直接置为255 2.计算映射后的像素最大值,归一化到(0,1)后再*255
#include<iostream>
#include<vector>
#include<opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main() {
//原始gray world认为对于一幅有着大量的色彩变化的图像,其RGB三个分量的平均值趋于同一个灰度K,一般有两种方法确定该灰度。
//1. 直接取127或128 2. RGB通道平均值的平均值
//计算各通道的增益,各通道的增益为——K/各通道平均值
//根据通道增益确定每个像素映射,例如Rnew = R * R增益
//如果像素溢出,1. 直接置为255 2.计算映射后的像素最大值,归一化到(0,1)后再*255
Mat img_input = imread("wb.png");
imshow("source", img_input);
vector<Mat> imgRGB;
split(img_input, imgRGB);
//K
double R_ave, G_ave, B_ave;
B_ave = mean(imgRGB[0])[0];
G_ave = mean(imgRGB[1])[0];
R_ave = mean(imgRGB[2])[0];
double K = (B_ave + G_ave + R_ave) / 3.0;
//增益
double KR, KG, KB;
KB = K / B_ave;
KG = K / G_ave;
KR = K / R_ave;
//恢复
imgRGB[0] = imgRGB[0] * KB;
imgRGB[1] = imgRGB[1] * KG;
imgRGB[2] = imgRGB[2] * KR;
merge(imgRGB, img_input);
imshow("output", img_input);
waitKey(0);
}
效果:
input:
output:
完美反射:
//完美反射
//完美反射认为图像最亮点就是白点,能完美反射光照,基于白点对各通道进行调整,另外要统计最亮的一定区域三通道均值,与最大值差距决定了通道调整力度
//流程:
//1. 计算RGB三通道各自灰度最大值
//2. 利用三通道数值和,确定图像最亮区间的下限T
//3. 计算图像三通道数值和大于T的点的三通道均值
//4. 计算三通道的补偿系数,即单通道最大值除以单通道亮区平均值
Mat input = imread("wb.png");
imshow("source", input);
vector<Mat> imgRGB;
split(input, imgRGB);
int row = input.rows;
int col = input.cols;
int RGBsum[766] = { 0 };
uchar maxR, maxG, maxB;
//单通道最大值
for (int i = 0; i < row; i++) {
uchar* b = imgRGB[0].ptr<uchar>(i);
uchar* g = imgRGB[1].ptr<uchar>(i);
uchar* r = imgRGB[2].ptr<uchar>(i);
for (int j = 0; j < col; j++) {
int sum = b[j] + g[j] + r[j];
RGBsum[sum]++;
maxB = max(maxB, b[j]);
maxG = max(maxG, g[j]);
maxR = max(maxR, r[j]);
}
}
//最亮区间下限T,前ratio比例的白色参考点阈值
int T = 0;
int num = 0;
int K = static_cast<int>(row * col * 0.3);//0.1就是ratio
for (int i = 765; i >= 0; i--) {
num += RGBsum[i];
if (num > K) {
T = i;
break;
}
}
//单通道亮度平均值
double Bm = 0.0, Gm = 0.0, Rm = 0.0;
int count = 0;
for (int i = 0; i < row; i++) {
uchar* b = imgRGB[0].ptr<uchar>(i);
uchar* g = imgRGB[1].ptr<uchar>(i);
uchar* r = imgRGB[2].ptr<uchar>(i);
for (int j = 0; j < col; j++) {
int sum = b[j] + g[j] + r[j];
if (sum > T) {
Bm += b[j];
Gm += g[j];
Rm += r[j];
count++;
}
}
}
Bm /= count;
Gm /= count;
Rm /= count;
//调整
imgRGB[0] *= maxB / Bm;
imgRGB[1] *= maxG / Gm;
imgRGB[2] *= maxR / Rm;
Mat result;
merge(imgRGB, result);
imshow("1", result);
waitKey(0);
动态阈值:
void DynamicThreshold(Mat input, Mat& output) {
//1.转变到YCrCb颜色空间
Mat ycrcb;
cvtColor(input, ycrcb, COLOR_BGR2YCrCb);
//2.分离通道
vector<Mat> split_ycrcb;
split(ycrcb, split_ycrcb);
//3.图像分成3*4 12块
vector<Mat> split_Cr;
vector<Mat> split_Cb;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
int rowstart = i * (input.rows / 3);
int rowend = (i + 1) * (input.rows / 3) - 1;
int colstart = j * (input.cols / 4);
int colend = (j + 1) * (input.cols / 4);
Mat areaCr = split_ycrcb[1](Range(rowstart, rowend), Range(colstart, colend));
split_Cr.push_back(areaCr);
Mat areaCb = split_ycrcb[2](Range(rowstart, rowend), Range(colstart, colend));
split_Cb.push_back(areaCb);
}
}
//4.统计每个区域Cr,Cb的均值
float split_Cr_mean[12] = { 0. };
float split_Cb_mean[12] = { 0. };
for (int i = 0; i < 12; i++) {
split_Cb_mean[i] = mean(split_Cb[i])[0];
split_Cr_mean[i] = mean(split_Cr[i])[0];
}
//5.统计每块Cr和Cb的方差 1/N * (求和(Cr(i,j)-Mcr)方
float split_Cr_std[12] = { 0. };
float split_Cb_std[12] = { 0. };
int split_pixels[12] = { 0 };
for (int k = 0; k < 12; k++) {
for (int i = 0; i < split_Cb[k].rows; i++) {
for (int j = 0; j < split_Cb[k].cols; j++) {
split_Cb_std[k] += pow(split_Cb[k].at<uchar>(i, j) - split_Cb_mean[k], 2);
split_Cr_std[k] += pow(split_Cr[k].at<uchar>(i, j) - split_Cr_mean[k], 2);
split_pixels[k]++;
}
}
}
for (int k = 0; k < 12; k++) {
split_Cb_std[k] /= split_pixels[k];
split_Cr_std[k] /= split_pixels[k];
}
//去除过小的块(这里指方差小于0.01的块),根据每个分块的均值和偏差,计算整个图像Cb和Cr的均值方差
float mean_Cb = 0, mean_Cr=0, std_Cb = 0, std_Cr = 0;
int areaNum = 0;
for (int k = 0; k < 12; k++) {
if (split_Cb_std[k] > 0.01 && split_Cr_std[k] > 0.01) {
areaNum++;
mean_Cb += split_Cb_mean[k];
mean_Cr += split_Cr_mean[k];
std_Cb += split_Cb_std[k];
std_Cr += split_Cr_std[k];
}
}
mean_Cb /= areaNum;
mean_Cr /= areaNum;
std_Cb /= areaNum;
std_Cr /= areaNum;
//候选白点
vector<Vec2i> yHist[256];
int candinateWhitePixel = 0;
//后续保持图像亮度需要
int maxYVal = 0;
for (int i = 0; i < split_ycrcb[0].rows; i++) {
for (int j = 0; j < split_ycrcb[0].cols; j++) {
bool bcr = abs(split_ycrcb[1].at<uchar>(i, j) - (1.5 * mean_Cr + std_Cr)) <= 1.5 * std_Cr;
bool bcb = abs(split_ycrcb[2].at<uchar>(i, j) - (mean_Cb + std_Cb)) <= 1.5 * std_Cb;
int yvalue = split_ycrcb[0].at<uchar>(i, j);
maxYVal = maxYVal > yvalue ? maxYVal : yvalue;
//如果满足筛选条件,记录候选点坐标,候选点数+1
if (bcr && bcb) {
yHist[yvalue].push_back(Vec2i(i, j));
candinateWhitePixel++;
}
}
}
//候选点亮度前10%作为参考白点
float ratio = 0.1;
int cumNum = 0;
int yThreshold = 0;//参考白点的亮度值阈值
for (int i = 255; i >= 0; i--) {
cumNum += yHist[i].size();
if (cumNum > ratio * candinateWhitePixel) {
yThreshold = i;
break;
}
}
//计算参考白点的RGB通道均值
float avgR=0, avgB=0, avgG=0;
int whitePixel = 0;
for (int i = 255; i >= yThreshold; i--) {
for (int j = 0; j < yHist[i].size(); j++) {
avgB += input.at<Vec3b>(yHist[i][j][0], yHist[i][j][1])[0];
avgG += input.at<Vec3b>(yHist[i][j][0], yHist[i][j][1])[1];
avgR += input.at<Vec3b>(yHist[i][j][0], yHist[i][j][1])[2];
}
whitePixel += yHist[i].size();
}
avgB /= whitePixel;
avgG /= whitePixel;
avgR /= whitePixel;
//增益系数为Y通道最亮值 / channel_avg,是为了保持亮度一致
float gainR = maxYVal / avgR;
float gainG = maxYVal / avgG;
float gainB = maxYVal / avgB;
//调整图像
output = input.clone();
for (int i = 0; i < input.rows; i++) {
for (int j = 0; j < input.cols; j++) {
int B = (int)(input.at<Vec3b>(i, j)[0] * gainB);
output.at<Vec3b>(i, j)[0] = B > 255 ? 255 : B;
int G = (int)(input.at<Vec3b>(i, j)[1] * gainG);
output.at<Vec3b>(i, j)[1] = G > 255 ? 255 : G;
int R = (int)(input.at<Vec3b>(i, j)[2] * gainR);
output.at<Vec3b>(i, j)[2] = R > 255 ? 255 : R;
}
cout << i << endl;
}
}
int main(){
//动态阈值
//一样分为两个步骤:白点检测和白点调整
//白点检测:
//1. 将图像转换到YCrCb颜色空间,对图像进行分块,3*4
//2. 对每块统计Cr和Cb均值Mcr和Mcb
//3. 根据均值统计每块Cr和Cb的方差Dcr和Dcb
//4. 过滤方差较小的分块
//5. 统计所有分块均值和方差的均值作为图像整体的均值和方差
//6. 根据候选白点筛选条件筛选并记录白点在图像上的索引:|Cr(i,j) - (1.5*Mcr+Dcr)| <= 1.5*Dcr;|Cb(i,j) - (Mcb+Dcb)| <= 1.5*Dcb
//7. 选取候选白点亮度前10%的点作为参考白点
//8. 对参考白点计算RGB通道的均值R_ave,B_ave,B_ave
//9. 统计YCrCb中Y通道的最大值Ymax,计算三个通道的增益系数Gain = Ymax / Channel_ave
Mat input = imread("1.png");
imshow("input", input);
Mat output;
DynamicThreshold(input, output);
int a = 0;
imshow("result", output);
waitKey(0);
}