这个算法是IPOL上一篇名为《Automatic Color Equalization(ACE) and its Fast Implementation》提出的,这实际上也是《快速ACE算法及其在图像拼接中的应用》这篇论文中使用的ACE算法,这个算法主要是基于Retinex成像理论做的自动彩色均衡,我用C++ OpenCV实现了,来分享一下。
在论文介绍中提到,高动态图像是指在一幅图像中,既有明亮的区域又有阴影区域,为了使细节清晰,需要满足以下几点:
Rizzi等人根据Retinex理论提出自动色彩均衡算法,该算法考虑了图像中颜色和亮度的空间位置关系,进行局部的自适应滤波,实现具有局部和非线性特征的图像亮度,色彩与对比度调整,同时满足灰度世界理论和白斑点假设。
对图像进行色彩/空域调整,完成图像的色差矫正,得到空域重构图像:
R c ( p ) = ∑ j ∈ S u b s e t , j ≠ p r ( I c ( p ) − I c ( j ) ) d ( p , j ) R_c(p)=\sum_{j\in Subset,j\neq p \frac{r(I_c(p)-I_c(j))}{d(p, j)}} Rc(p)=∑j∈Subset,j=pd(p,j)r(Ic(p)−Ic(j))
其中 R c R_c Rc是中间结果, I c ( p ) − I c ( j ) I_c(p)-I_c(j) Ic(p)−Ic(j)为2个点的亮度差, d ( p , j ) d(p, j) d(p,j)表示距离度量函数, r ( ∗ ) r(*) r(∗)为亮度表现函数,需要是奇函数,这一步可以适应局部图像对比度, r ( ∗ ) r(*) r(∗)可以放大较小的差异,并丰富大的差异,根据局部内容扩展或者压缩动态范围。一般的, r ( ∗ ) r(*) r(∗)为:
r ( x ) = { 1 , x < − T x / T , − T < = x < = T − 1 , x > T } r(x)=\begin{Bmatrix} 1, x<-T\\ x/T, -T<=x<=T\\-1, x>T \end{Bmatrix} r(x)=⎩⎨⎧1,x<−Tx/T,−T<=x<=T−1,x>T⎭⎬⎫
对矫正后的图像进行动态扩展,一种简单的线性扩展为:
O c ( p ) = r o u n d [ 127.5 + s c R c ( p ) ] O_c(p)=round[127.5+s_cR_c(p)] Oc(p)=round[127.5+scRc(p)],
其中 s c s_c sc是 [ ( m c , 0 ) , ( M c , 255 ) ] [(m_c,0),(M_c,255)] [(mc,0),(Mc,255)]的斜率,其中:
M c = m a x p [ R c ( p ) ] M_c=max_p[R_c(p)] Mc=maxp[Rc(p)], m c = m i n p [ R c ( p ) ] m_c=min_p[R_c(p)] mc=minp[Rc(p)]
我们还可以将其映射到 [ 0 , 255 ] [0, 255] [0,255]的空间中:
L ( x ) = R ( x ) − m i n R m a x R − m i n R L(x)=\frac{R(x)-minR}{maxR-minR} L(x)=maxR−minRR(x)−minR
我实现代码时选择了后者,效果会好一点。
在查阅资料[参考2]的时候看到一个非常有趣的改进方法,可以让ACE算法速度更快,更利于实际应用。
快速ACE算法基于两个基本假设:(1)对一副图像ACE增强后得到输出 Y Y Y,如果对 Y Y Y再进行一次ACE增强,输出仍然是 Y Y Y本身;(2)对一副图像的ACE增强结果进行尺寸缩放得到 Y Y Y,对 Y Y Y进行ACE增强,输出仍然是 Y Y Y本身。
如果上面假设成立,我们就可以对图像进行缩放得到 I 1 I1 I1,对 I 1 I1 I1的ACE增强结果进行尺度放大(与 I I I尺寸一样)得到 Y 1 Y1 Y1,那么 Y Y Y和 Y 1 Y1 Y1是非常接近的,我们只需要在 Y 1 Y1 Y1基础上进一步处理即可。这里就又引申了两个细节问题:
注意,这种方法不是论文中使用的改进方法,之所以要介绍这种方法是因为它操作起来很简单,同时原理也比较好懂。关于论文中使用的快速ACE算法加速技巧比较复杂,有兴趣可以去看原论文。
#include
#include
#include
#include
#include
#include
#include "opencv2/highgui/highgui.hpp"
using namespace cv;
using namespace cv::ml;
using namespace std;
namespace ACE {
//Gray
Mat stretchImage(Mat src) {
int row = src.rows;
int col = src.cols;
Mat dst(row, col, CV_64FC1);
double MaxValue = 0;
double MinValue = 256.0;
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
MaxValue = max(MaxValue, src.at(i, j));
MinValue = min(MinValue, src.at(i, j));
}
}
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
dst.at(i, j) = (1.0 * src.at(i, j) - MinValue) / (MaxValue - MinValue);
if (dst.at(i, j) > 1.0) {
dst.at(i, j) = 1.0;
}
else if (dst.at(i, j) < 0) {
dst.at(i, j) = 0;
}
}
}
return dst;
}
Mat getPara(int radius) {
int size = radius * 2 + 1;
Mat dst(size, size, CV_64FC1);
for (int i = -radius; i <= radius; i++) {
for (int j = -radius; j <= radius; j++) {
if (i == 0 && j == 0) {
dst.at(i + radius, j + radius) = 0;
}
else {
dst.at(i + radius, j + radius) = 1.0 / sqrt(i * i + j * j);
}
}
}
double sum = 0;
for (int i = 0; i < size; i++) {
for (int j = 0; j < size; j++) {
sum += dst.at(i, j);
}
}
for (int i = 0; i < size; i++) {
for (int j = 0; j < size; j++) {
dst.at(i, j) = dst.at(i, j) / sum;
}
}
return dst;
}
Mat NormalACE(Mat src, int ratio, int radius) {
Mat para = getPara(radius);
int row = src.rows;
int col = src.cols;
int size = 2 * radius + 1;
Mat Z(row + 2 * radius, col + 2 * radius, CV_64FC1);
for (int i = 0; i < Z.rows; i++) {
for (int j = 0; j < Z.cols; j++) {
if((i - radius >= 0) && (i - radius < row) && (j - radius >= 0) && (j - radius < col)) {
Z.at(i, j) = src.at(i - radius, j - radius);
}
else {
Z.at(i, j) = 0;
}
}
}
Mat dst(row, col, CV_64FC1);
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
dst.at(i, j) = 0.f;
}
}
for (int i = 0; i < size; i++) {
for (int j = 0; j < size; j++) {
if (para.at(i, j) == 0) continue;
for (int x = 0; x < row; x++) {
for (int y = 0; y < col; y++) {
double sub = src.at(x, y) - Z.at(x + i, y + j);
double tmp = sub * ratio;
if (tmp > 1.0) tmp = 1.0;
if (tmp < -1.0) tmp = -1.0;
dst.at(x, y) += tmp * para.at(i, j);
}
}
}
}
return dst;
}
Mat FastACE(Mat src, int ratio, int radius) {
int row = src.rows;
int col = src.cols;
if (min(row, col) <= 2) {
Mat dst(row, col, CV_64FC1);
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
dst.at(i, j) = 0.5;
}
}
return dst;
}
Mat Rs((row + 1) / 2, (col + 1) / 2, CV_64FC1);
resize(src, Rs, Size((col + 1) / 2, (row + 1) / 2));
Mat Rf= FastACE(Rs, ratio, radius);
resize(Rf, Rf, Size(col, row));
resize(Rs, Rs, Size(col, row));
Mat dst(row, col, CV_64FC1);
Mat dst1 = NormalACE(src, ratio, radius);
Mat dst2 = NormalACE(Rs, ratio, radius);
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
dst.at(i, j) = Rf.at(i, j) + dst1.at(i, j) - dst2.at(i, j);
}
}
return dst;
}
Mat getACE(Mat src, int ratio, int radius) {
int row = src.rows;
int col = src.cols;
vector v;
split(src, v);
v[0].convertTo(v[0], CV_64FC1);
v[1].convertTo(v[1], CV_64FC1);
v[2].convertTo(v[2], CV_64FC1);
Mat src1(row, col, CV_64FC1);
Mat src2(row, col, CV_64FC1);
Mat src3(row, col, CV_64FC1);
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
src1.at(i, j) = 1.0 * src.at(i, j)[0] / 255.0;
src2.at(i, j) = 1.0 * src.at(i, j)[1] / 255.0;
src3.at(i, j) = 1.0 * src.at(i, j)[2] / 255.0;
}
}
src1 = stretchImage(FastACE(src1, ratio, radius));
src2 = stretchImage(FastACE(src2, ratio, radius));
src3 = stretchImage(FastACE(src3, ratio, radius));
Mat dst1(row, col, CV_8UC1);
Mat dst2(row, col, CV_8UC1);
Mat dst3(row, col, CV_8UC1);
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
dst1.at(i, j) = (int)(src1.at(i, j) * 255);
if (dst1.at(i, j) > 255) dst1.at(i, j) = 255;
else if (dst1.at(i, j) < 0) dst1.at(i, j) = 0;
dst2.at(i, j) = (int)(src2.at(i, j) * 255);
if (dst2.at(i, j) > 255) dst2.at(i, j) = 255;
else if (dst2.at(i, j) < 0) dst2.at(i, j) = 0;
dst3.at(i, j) = (int)(src3.at(i, j) * 255);
if (dst3.at(i, j) > 255) dst3.at(i, j) = 255;
else if (dst3.at(i, j) < 0) dst3.at(i, j) = 0;
}
}
vector out;
out.push_back(dst1);
out.push_back(dst2);
out.push_back(dst3);
Mat dst;
merge(out, dst);
return dst;
}
}
using namespace ACE;
int main() {
Mat src = imread("F:\\sky.jpg");
Mat dst = getACE(src, 4, 7);
imshow("origin", src);
imshow("result", dst);
waitKey(0);
}
欢迎关注GiantPandaCV, 在这里你将看到独家的深度学习分享,坚持原创,每天分享我们学习到的新鲜知识。( • ̀ω•́ )✧
有对文章相关的问题,或者想要加入交流群,欢迎添加BBuf微信: