前言

这篇论文实际上也是《快速 ACE 算法 及其在 图 像拼接 中 的 应 用》这篇论文中的快速ACE算法,我用C++实现了,现在放出来。

算法原理

在介绍中,提到了,高动态图像是指在一幅图像中,既有明亮的区域又有阴影区域,为了使细节清晰,需要满足以下几点:
(1)对动态范围具有一定的压缩能力
(2)对亮暗区域的细节有一定的显示能力
(3)满足(1),(2)的条件下不破坏图像的清晰度
Rizzi等根据Retinex理论提出自动颜色均衡算法,该算法考虑了图像中颜色和亮度的空间位置关系,进行局部的自适应滤波,实现具有局部和非线性特征的图像亮度,色彩与对比度调整,同时满足灰度世界理论和白斑点假设。

算法步骤

  • 对图像进行色彩/空域调整,完成图像的色差矫正,得到空域重构图像
    R c ( p ) = j S u b s e t , j p r ( I c ( p ) I c ( j ) ) d ( p , j ) , ( 1 ) R_c(p)=\sum_{j\in Subset,j\neq p \frac{r(I_c(p)-I_c(j))}{d(p, j)}},(1) Rc(p)=jSubset,j̸=pd(p,j)r(Ic(p)Ic(j)),(1),其中 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 ) = { <mstyle displaystyle="false" scriptlevel="0"> 1 , x &lt; T </mstyle> <mstyle displaystyle="false" scriptlevel="0"> x / T , T &lt; = x &lt; = T </mstyle> <mstyle displaystyle="false" scriptlevel="0"> 1 , x &gt; T </mstyle> } r(x)=\begin{Bmatrix} 1, x&lt;-T\\ x/T, -T&lt;=x&lt;=T\\-1, x&gt;T \end{Bmatrix} r(x)=1,x<Tx/T,T<=x<=T1,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 ) ] 3 M_c=max_p[R_c(p)],(3) Mc=maxp[Rc(p)]3 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)=maxRminRR(x)minR

算法改进

我没看懂这部分,暂时就不写了。

C++源码实现

#include <stdio.h>
#include <iostream>
#include <immintrin.h>
#include <opencv2/opencv.hpp>
#include <opencv2/core/core.hpp>
#include <opencv2/ml/ml.hpp>
#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<double>(i, j));
				MinValue = min(MinValue, src.at<double>(i, j));
			}
		}
		for (int i = 0; i < row; i++) {
			for (int j = 0; j < col; j++) {
				dst.at<double>(i, j) = (1.0 * src.at<double>(i, j) - MinValue) / (MaxValue - MinValue);
				if (dst.at<double>(i, j) > 1.0) {
					dst.at<double>(i, j) = 1.0;
				}
				else if (dst.at<double>(i, j) < 0) {
					dst.at<double>(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<double>(i + radius, j + radius) = 0;
				}
				else {
					dst.at<double>(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<double>(i, j);
			}
		}
		for (int i = 0; i < size; i++) {
			for (int j = 0; j < size; j++) {
				dst.at<double>(i, j) = dst.at<double>(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<double>(i, j) = src.at<double>(i - radius, j - radius);
				}
				else {
					Z.at<double>(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<double>(i, j) = 0.f;
			}
		}
		for (int i = 0; i < size; i++) {
			for (int j = 0; j < size; j++) {
				if (para.at<double>(i, j) == 0) continue;
				for (int x = 0; x < row; x++) {
					for (int y = 0; y < col; y++) {
						double sub = src.at<double>(x, y) - Z.at<double>(x + i, y + j);
						double tmp = sub * ratio;
						if (tmp > 1.0) tmp = 1.0;
						if (tmp < -1.0) tmp = -1.0;
						dst.at<double>(x, y) += tmp * para.at<double>(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<double>(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<double>(i, j) = Rf.at<double>(i, j) + dst1.at<double>(i, j) - dst2.at<double>(i, j);
			}
		}
		return dst;
	}

	Mat getACE(Mat src, int ratio, int radius) {
		int row = src.rows;
		int col = src.cols;
		vector <Mat> 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<double>(i, j) = 1.0 * src.at<Vec3b>(i, j)[0] / 255.0;
				src2.at<double>(i, j) = 1.0 * src.at<Vec3b>(i, j)[1] / 255.0;
				src3.at<double>(i, j) = 1.0 * src.at<Vec3b>(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<uchar>(i, j) = (int)(src1.at<double>(i, j) * 255);
				if (dst1.at<uchar>(i, j) > 255) dst1.at<uchar>(i, j) = 255;
				else if (dst1.at<uchar>(i, j) < 0) dst1.at<uchar>(i, j) = 0;
				dst2.at<uchar>(i, j) = (int)(src2.at<double>(i, j) * 255);
				if (dst2.at<uchar>(i, j) > 255) dst2.at<uchar>(i, j) = 255;
				else if (dst2.at<uchar>(i, j) < 0) dst2.at<uchar>(i, j) = 0;
				dst3.at<uchar>(i, j) = (int)(src3.at<double>(i, j) * 255);
				if (dst3.at<uchar>(i, j) > 255) dst3.at<uchar>(i, j) = 255;
				else if (dst3.at<uchar>(i, j) < 0) dst3.at<uchar>(i, j) = 0;
			}
		}
		vector <Mat> 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);
}

效果图

\quad 实现的效果和论文有所偏差,这只是拿来参考一下,对这个感兴趣可以研究作者给出的C++源码。

参考博客

https://blog.csdn.net/piaoxuezhong/article/details/78357815
https://www.cnblogs.com/whw19818/p/5765995.html