自动色阶
第一步,分别统计各通道(红/绿/蓝)的直方图。
第二步,分别计算各通道按照给定的参数所确定的上下限值。什么意思呢,比如对于蓝色通道,我们从色阶0开始向上累加统计直方图,当累加值大于LowCut所有像素数时,以此时的色阶值计为BMin。然后从色阶255开始向下累计直方图,如果累加值大于HighCut所有像素时,以此时的色阶值计为BMax。
第三步,按照我们刚刚计算出的MinBlue/MaxBlue构建一个隐射表,隐射表的规则是,对于小于MinBlue的值,则隐射为0(实际上这句话也不对,隐射为多少是和那个自动颜色校正选项对话框中的阴影所设定的颜色有关,默认情况下是黑色,对应的RGB分量都为0,所以我们这里就隐射为0,有兴趣你们也可以指定为其他的参数),对于大于MaxBlue的值,则隐射为255(同理,这个值和高光的颜色设置有关),对于介于MinBlue和MaxBlue之间的值,则进行线性隐射,默认是隐射为0到255之间(当然实际是和我们的暗调和高光的设置有关,并且这里其实也不是线性隐射,是有一个Gamma校正,为了简便,用线性替代效果也没太大的问题)。
最后一步,对各通道图像数据进行隐射。
1 // AutoLevel.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
2 //
3
4 #include <iostream>
5 #include<opencv2corecore.hpp>
6 #include<opencv2highguihighgui.hpp>
7 #include<opencv2imgprocimgproc.hpp>
8 #include<iostream>
9 #include<vector>
10 #include <algorithm>
11 using namespace cv;
12
13 void AutoLevelsAdjust(cv::Mat& src, cv::Mat& dst)
14 {
15 CV_Assert(!src.empty() && src.channels() == 3);
16
17 //统计灰度直方图
18 int BHist[256] = { 0 }; //B分离
19 int GHist[256] = { 0 }; //G分量
20 int RHist[256] = { 0 }; //R分量
21 cv::MatIterator_<Vec3b> its, ends;
22 for (its = src.begin<Vec3b>(), ends = src.end<Vec3b>(); its != ends; its++)
23 {
24 BHist[(*its)[0]]++;
25 GHist[(*its)[1]]++;
26 RHist[(*its)[2]]++;
27 }
28
29 //设置LowCut和HighCut
30 float LowCut = 0.5;
31 float HighCut = 0.5;
32
33 //根据LowCut和HighCut查找每个通道最大值最小值
34 int BMax = 0, BMin = 0;
35 int GMax = 0, GMin = 0;
36 int RMax = 0, RMin = 0;
37
38 int TotalPixels = src.cols * src.rows;
39 float LowTh = LowCut * 0.01 * TotalPixels;
40 float HighTh = HighCut * 0.01 * TotalPixels;
41
42 //B通道查找最小最大值
43 int sumTempB = 0;
44 for (int i = 0; i < 256; i++)
45 {
46 sumTempB += BHist[i];
47 if (sumTempB >= LowTh)
48 {
49 BMin = i;
50 break;
51 }
52 }
53 sumTempB = 0;
54 for (int i = 255; i >= 0; i--)
55 {
56 sumTempB += BHist[i];
57 if (sumTempB >= HighTh)
58 {
59 BMax = i;
60 break;
61 }
62 }
63
64 //G通道查找最小最大值
65 int sumTempG = 0;
66 for (int i = 0; i < 256; i++)
67 {
68 sumTempG += GHist[i];
69 if (sumTempG >= LowTh)
70 {
71 GMin = i;
72 break;
73 }
74 }
75 sumTempG = 0;
76 for (int i = 255; i >= 0; i--)
77 {
78 sumTempG += GHist[i];
79 if (sumTempG >= HighTh)
80 {
81 GMax = i;
82 break;
83 }
84 }
85
86 //R通道查找最小最大值
87 int sumTempR = 0;
88 for (int i = 0; i < 256; i++)
89 {
90 sumTempR += RHist[i];
91 if (sumTempR >= LowTh)
92 {
93 RMin = i;
94 break;
95 }
96 }
97 sumTempR = 0;
98 for (int i = 255; i >= 0; i--)
99 {
100 sumTempR += RHist[i];
101 if (sumTempR >= HighTh)
102 {
103 RMax = i;
104 break;
105 }
106 }
107
108 //对每个通道建立分段线性查找表
109 //B分量查找表
110 int BTable[256] = { 0 };
111 for (int i = 0; i < 256; i++)
112 {
113 if (i <= BMin)
114 BTable[i] = 0;
115 else if (i > BMin && i < BMax)
116 BTable[i] = cvRound((float)(i - BMin) / (BMax - BMin) * 255);
117 else
118 BTable[i] = 255;
119 }
120
121 //G分量查找表
122 int GTable[256] = { 0 };
123 for (int i = 0; i < 256; i++)
124 {
125 if (i <= GMin)
126 GTable[i] = 0;
127 else if (i > GMin && i < GMax)
128 GTable[i] = cvRound((float)(i - GMin) / (GMax - GMin) * 255);
129 else
130 GTable[i] = 255;
131 }
132
133 //R分量查找表
134 int RTable[256] = { 0 };
135 for (int i = 0; i < 256; i++)
136 {
137 if (i <= RMin)
138 RTable[i] = 0;
139 else if (i > RMin && i < RMax)
140 RTable[i] = cvRound((float)(i - RMin) / (RMax - RMin) * 255);
141 else
142 RTable[i] = 255;
143 }
144
145 //对每个通道用相应的查找表进行分段线性拉伸
146 cv::Mat dst_ = src.clone();
147 cv::MatIterator_<Vec3b> itd, endd;
148 for (itd = dst_.begin<Vec3b>(), endd = dst_.end<Vec3b>(); itd != endd; itd++)
149 {
150 (*itd)[0] = BTable[(*itd)[0]];
151 (*itd)[1] = GTable[(*itd)[1]];
152 (*itd)[2] = RTable[(*itd)[2]];
153 }
154 dst = dst_;
155 }
156
157 int main()
158 {
159 Mat image = imread("E:\picture\wutian.jpg");
160 Mat dst = Mat::zeros(image.rows, image.cols, CV_8UC1);
161 AutoLevelsAdjust(image, dst);
162 imshow("src", image);
163 imshow("dst", dst);
164 while (char(waitKey(1)) != 'q') {}
165 }
自动色阶用来做去雾,还是最为稳定的,虽然现在有很多种其他增强方法,例如暗通道去雾,优化对比度去雾,基于Color-Lines的去雾,但是这些算法还不是很稳定,当先验知识失效时,处理失真比较严重。但自动色阶相对就比较稳定。