低照图/低亮度视频的修复与优化(ALTM_Retinex实现高HDR)——Java及C实现
项目中遇到问题:低亮度视频与低亮度图片,无法进行后续的处理。
分别测试了传统的变换方法,如:Gamma变换、Log变换、拉普拉斯提升等,效果均不好,
参照大佬的Github:https://github.com/IsaacChanghau/OptimizedImageEnhance
其中选取了ALTMRetinex
算法,发现Github主的Java方法省略了源Matlab代码的局部提升
及部分公式
。
算法原理
源PDF:http://koasas.kaist.ac.kr/bitstream/10203/172985/1/73275.pdf
参照github主:https://github.com/IsaacChanghau/OptimizedImageEnhance/tree/master/matlab/ALTMRetinex
全局提升:取亮度,经过一系列变换,与原亮度相除,有个亮度提升比矩阵。
局部提升:没看,应用为主,局部提升会使一些图片过曝,实际使用中去掉了。
Matlab源码
原版带有局部提升算法
Matlab源码如下:
function outval = ALTM_Retinex(I)
II = im2double(I);
Ir=double(II(:,:,1)); Ig=double(II(:,:,2)); Ib=double(II(:,:,3));
% Global Adaptation
Lw = 0.299 * Ir + 0.587 * Ig + 0.114 * Ib;% input world luminance values
Lwmax = max(max(Lw));% the maximum luminance value
[m, n] = size(Lw);
Lwaver = exp(sum(sum(log(0.001 + Lw))) / (m * n));% log-average luminance
Lg = log(Lw / Lwaver + 1) / log(Lwmax / Lwaver + 1);
% Local Adaptation
kenlRatio = 0.01;
krnlsz = floor(max([3, m * kenlRatio, n * kenlRatio]));
Lg1 = maxfilt2(Lg, [krnlsz, krnlsz]);
Lg1 = imresize(Lg1, [m, n]);
Hg = guidedfilter(Lg, Lg1, 10, 0.01);
eta = 36;
alpha = 1 + eta * Lg / max(max(Lg));
alpha = alpha .* (alpha .^ (1 ./ alpha));
b = max(max(alpha));
a = 1.35;
alpha = 2 * atan(a * alpha / b) / pi * b;
Lgaver = exp(sum(sum(log(0.001 + Lg))) / (m * n));
lambda = 10;
beta = lambda * Lgaver;
Lout = alpha .* log(Lg ./ Hg + beta);
%Lout = normfun(Lout, 1);
Lout = SimplestColorBalance(Lout, 0.005, 0.001, 1);
gain = Lout ./ Lw;
gain(find(Lw == 0)) = 0;
outval = cat(3, gain .* Ir, gain .* Ig, gain .* Ib);
不带有局部提升算法
function outval = ALTM_Retinex2(I)
II = im2double(I);
Ir=double(II(:,:,1)); Ig=double(II(:,:,2)); Ib=double(II(:,:,3));
% Global Adaptation
Lw = 0.299 * Ir + 0.587 * Ig + 0.114 * Ib;% input world luminance values
Lwmax = max(max(Lw));% the maximum luminance value
[m, n] = size(Lw);
Lwaver = exp(sum(sum(log(0.001 + Lw))) / (m * n));% log-average luminance
Lg = log(Lw / Lwaver + 1) / log(Lwmax / Lwaver + 1);
% Global Adaptation
Lw = double(I(:,:,1));
Lw = Lw ./ 255.0;
%Lw = 0.299 * Ir + 0.587 * Ig + 0.114 * Ib;% input world luminance values
Lwmax = max(max(Lw));% the maximum luminance value
[m, n] = size(Lw);
Lwaver = exp(sum(sum(log(0.001 + Lw))) / (m * n));% log-average luminance
Lg = log(Lw / Lwaver + 1) / log(Lwmax / Lwaver + 1);
Lout = SimplestColorBalance(Lg, 0.005, 0.001, 1);
gain = Lout ./ Lw;
gain(find(Lw == 0)) = 0;
outval = cat(3, gain .* Ir, gain .* Ig, gain .* Ib);
RGB颜色提升优化算法
原版代码直接对rgb进行亮度的比例提升,会有色彩溢出的异常,采用公式:
R = (1/2) * (Lg / Lw * (R + Lw) + R - Lw)
都是元素对应位置相乘,GB两通道同理,方法也是参考某博主
function outval = ALTM_Retinex(I)
II = im2double(I);
Ir=double(II(:,:,1)); Ig=double(II(:,:,2)); Ib=double(II(:,:,3));
% Global Adaptation
Lw = 0.299 * Ir + 0.587 * Ig + 0.114 * Ib;% input world luminance values
Lwmax = max(max(Lw));% the maximum luminance value
[m, n] = size(Lw);
Lwaver = exp(sum(sum(log(0.001 + Lw))) / (m * n));% log-average luminance
Lg = log(Lw / Lwaver + 1) / log(Lwmax / Lwaver + 1);
% Local Adaptation
kenlRatio = 0.01;
krnlsz = floor(max([3, m * kenlRatio, n * kenlRatio]));
Lg1 = maxfilt2(Lg, [krnlsz, krnlsz]);
Lg1 = imresize(Lg1, [m, n]);
Hg = guidedfilter(Lg, Lg1, 10, 0.01);
eta = 36;
alpha = 1 + eta * Lg / max(max(Lg));
alpha = alpha .* (alpha .^ (1 ./ alpha));
b = max(max(alpha));
a = 1.35;
alpha = 2 * atan(a * alpha / b) / pi * b;
Lgaver = exp(sum(sum(log(0.001 + Lg))) / (m * n));
lambda = 10;
beta = lambda * Lgaver;
Lout = alpha .* log(Lg ./ Hg + beta);
%Lout = normfun(Lout, 1);
Lout = SimplestColorBalance(Lout, 0.005, 0.001, 1);
gain = Lout ./ Lw;
gain(find(Lw == 0)) = 0;
Ir = (1/2.0) .* (gain .* (Ir+Lw) + Ir - Lw);
Ig = (1/2.0) .* (gain .* (Ig+Lw) + Ig - Lw);
Ib = (1/2.0) .* (gain .* (Ib+Lw) + Ib - Lw);
outval = cat(3, Ir,Ig,Ib);
PS:以上代码.* ./
都是对应位置乘除法。
依赖的代码请去Github自行下载,在matlab中的压缩包中。
Java代码
用Opencv4Java实现了Java版本的Matlab版本算法,代码如下:
ALTMRetinex 主类
package cn.uestc.image.function;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.Point;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.imgproc.Imgproc;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
import cn.uestc.image.util.Filters;
/**
* 基于YUV的Y通道进行的图像提升
*/
public class ALTMRetinex {
private static final boolean isDebug = false;
private static long time;
// Global Adaptation Parameters -- fixed
private static final double rParam = 0.299;
private static final double gParam = 0.587;
private static final double bParam = 0.114;
public static Mat enhance(Mat src, int r, double eps, double eta, double lambda, double krnlRatio) {
Mat srcClone = src.clone();
srcClone.convertTo(srcClone, CvType.CV_32F);
// 分离通道为bgr
List<Mat> bgr = new ArrayList<>();
// 先归一化到(0-1)
Core.divide(srcClone, new Scalar(255, 255, 255), srcClone);
Core.split(srcClone, bgr);
Mat bChannel = bgr.get(0);
Mat gChannel = bgr.get(1);
Mat rChannel = bgr.get(2);
int m = rChannel.rows();
int n = rChannel.cols();
// Global Adaptation
if (isDebug) {
time = System.currentTimeMillis();
}
List<Mat> list = globalAdaptation(rChannel.clone(), gChannel.clone(), bChannel.clone(), m, n);
if (isDebug) {
System.out.println(String.format("globalAdaptation-耗时:%d", (System.currentTimeMillis() - time)));
time = System.currentTimeMillis();
}
Mat Lw = list.get(0);
Mat Lg = list.get(1);
// Local Adaptation
Mat Hg = localAdaptation(Lg, m, n, r, eps, krnlRatio);
if (isDebug) {
System.out.println(String.format("localAdaptation-耗时:%d", (System.currentTimeMillis() - time)));
time = System.currentTimeMillis();
}
Lg.convertTo(Lg, CvType.CV_32F);
// process
// alpha = 1 + eta * Lg / max(max(Lg));
Mat alpha = new Mat(m, n, rChannel.type());
Core.divide(Lg, new Scalar(Core.minMaxLoc(Lg).maxVal / eta), alpha);
Core.add(alpha, new Scalar(1.0), alpha);
if (isDebug) {
System.out.println(String.format("alpha = 1 + eta * Lg / max(max(Lg));-耗时:%d", (System.currentTimeMillis() - time)));
time = System.currentTimeMillis();
}
// alpha = alpha .* (alpha .^ (1 ./ alpha));
float[] data = new float[alpha.width() * alpha.height()];
alpha.get(0, 0, data);
for (int i = 0; i < data.length; i++) {
// data[i] = (float) (data[i] * (Math.pow(data[i], 1 / data[i])));
data[i] = (float) ((Math.pow(data[i], 1 + 1 / data[i])));
}
if (isDebug) {
System.out.println(String.format("alpha = alpha .* (alpha .^ (1 ./ alpha));-耗时:%d", (System.currentTimeMillis() - time)));
time = System.currentTimeMillis();
}
alpha.put(0, 0, data);
double b = Core.minMaxLoc(alpha).maxVal;
// alpha = 2 * atan(alpha * a / b) / pi * b;
Core.multiply(alpha, new Scalar(1.35 / b), alpha);
alpha.get(0, 0, data);
for (int i = 0; i < data.length; i++) {
// data[i] = (float) (2 * Math.atan(data[i] * 1.35 / b) / Math.PI * b);
data[i] = (float) (Math.atan(data[i]));
}
alpha.put(0, 0, data);
Core.multiply(alpha, new Scalar(2 / Math.PI * b), alpha);
if (isDebug) {
System.out.println(String.format("alpha = 2 * atan(alpha * a / b) / pi * b;-耗时:%d", (System.currentTimeMillis() - time)));
time = System.currentTimeMillis();
}
// beta = exp(sum(sum(log(0.001 + Lg))) / (m * n)) * lambda;
Mat Lg_ = new Mat(m, n, rChannel.type());
Core.add(Lg, new Scalar(0.001), Lg_);
Core.log(Lg_, Lg_);
double beta = Math.exp(Core.sumElems(Lg_).val[0] / (m * n)) * lambda;
if (isDebug) {
System.out.println(String.format("beta = exp(sum(sum(log(0.001 + Lg))) / (m * n)) * lambda;-耗时:%d", (System.currentTimeMillis() - time)));
time = System.currentTimeMillis();
}
// Lout = alpha .* log(Lg ./ Hg + beta);
Mat Lout = new Mat(m, n, rChannel.type());
Core.divide(Lg, Hg, Lout);
Core.add(Lout, new Scalar(beta), Lout);
Core.log(Lout, Lout);
Lout = alpha.mul(Lout);
if (isDebug) {
System.out.println(String.format("Lout = alpha .* log(Lg ./ Hg + beta);-耗时:%d", (System.currentTimeMillis() - time)));
time = System.currentTimeMillis();
}
// Lout = Filters.SimplestColorBalance(Lout, 1);
Lout = Filters.SimplestColorBalance(Lout, 0.005, 0.001);
if (isDebug) {
System.out.println(String.format("SimplestColorBalance-耗时:%d", (System.currentTimeMillis() - time)));
time = System.currentTimeMillis();
}
// gain = Lout ./ Lw;
Mat gain = new Mat();
Core.divide(Lout, Lw, gain);
Core.patchNaNs(gain, 0);
// Mat gain = obtainGain(Lout, Lw, m, n);
if (isDebug) {
System.out.println(String.format("gain = Lout ./ Lw;-耗时:%d", (System.currentTimeMillis() - time)));
time = System.currentTimeMillis();
}
// output
// R'=1/2[(Y'/Y)(R+Y)+R-Y]
Mat temp2 = new Mat(m, n, CvType.CV_32FC1);
Core.add(rChannel, Lw, temp2);
Core.multiply(gain, temp2, temp2);
Core.add(temp2, rChannel, temp2);
Core.subtract(temp2, Lw, temp2);
Core.multiply(temp2, new Scalar(0.5), rChannel);
Core.multiply(rChannel, new Scalar(255), rChannel);
if (isDebug) {
System.out.println(String.format("R'=1/2[(Y'/Y)(R+Y)+R-Y]-耗时:%d", (System.currentTimeMillis() - time)));
time = System.currentTimeMillis();
}
// G'=1/2[(Y'/Y)(G+Y)+G-Y]
Mat temp3 = new Mat(m, n, CvType.CV_32FC1);
Core.add(gChannel, Lw, temp3);
Core.multiply(gain, temp3, temp3);
Core.add(temp3, gChannel, temp3);
Core.subtract(temp3, Lw, temp3);
Core.multiply(temp3, new Scalar(0.5), gChannel);
Core.multiply(gChannel, new Scalar(255), gChannel);
if (isDebug) {
System.out.println(String.format("G'=1/2[(Y'/Y)(G+Y)+G-Y]-耗时:%d", (System.currentTimeMillis() - time)));
time = System.currentTimeMillis();
}
// B'=1/2[(Y'/Y)(B+Y)+B-Y]
Mat temp4 = new Mat(m, n, CvType.CV_32FC1);
Core.add(bChannel, Lw, temp4);
Core.multiply(gain, temp4, temp4);
Core.add(temp4, bChannel, temp4);
Core.subtract(temp4, Lw, temp4);
Core.multiply(temp4, new Scalar(0.5), temp4);
Core.multiply(temp4, new Scalar(255), bChannel);
if (isDebug) {
System.out.println(String.format("B'=1/2[(Y'/Y)(B+Y)+B-Y]-耗时:%d", (System.currentTimeMillis() - time)));
time = System.currentTimeMillis();
}
// merge three color channels to a image
Mat outval = new Mat();
Core.merge(new ArrayList<>(Arrays.asList(bChannel, gChannel, rChannel)), outval);
outval.convertTo(outval, CvType.CV_8U);
if (isDebug) {
System.out.println(String.format("merge-耗时:%d", (System.currentTimeMillis() - time)));
time = System.currentTimeMillis();
}
// 及时释放
temp2.release();
temp3.release();
temp4.release();
rChannel.release();
gChannel.release();
bChannel.release();
Lw.release();
Lg.release();
Hg.release();
alpha.release();
Lg_.release();
Lout.release();
gain.release();
return outval;
}
public static Mat enhanceWithoutLocal(Mat src) {
if (isDebug) {
time = System.currentTimeMillis();
}
Mat srcClone = src.clone();
srcClone.convertTo(srcClone, CvType.CV_32F);
// 分离通道为bgr
List<Mat> bgr = new ArrayList<>();
// 先归一化到(0-1)
Core.divide(srcClone, new Scalar(255, 255, 255), srcClone);
if (isDebug) {
System.out.println(String.format("归一化-耗时:%d", (System.currentTimeMillis() - time)));
time = System.currentTimeMillis();
}
Core.split(srcClone, bgr);
Mat bChannel = bgr.get(0);
Mat gChannel = bgr.get(1);
Mat rChannel = bgr.get(2);
int m = rChannel.rows();
int n = rChannel.cols();
if (isDebug) {
System.out.println(String.format("分解通道-耗时:%d", (System.currentTimeMillis() - time)));
time = System.currentTimeMillis();
}
// Global Adaptation
if (isDebug) {
time = System.currentTimeMillis();
}
List<Mat> list = globalAdaptation(rChannel.clone(), gChannel.clone(), bChannel.clone(), m, n);
if (isDebug) {
System.out.println(String.format("globalAdaptation-耗时:%d", (System.currentTimeMillis() - time)));
time = System.currentTimeMillis();
}
Mat Lw = list.get(0);
Mat Lg = list.get(1);
// gain = Lout ./ Lw;
Mat gain = new Mat();
Core.divide(Lg, Lw, gain);
Core.patchNaNs(gain, 0);
if (isDebug) {
System.out.println(String.format("gain = Lg ./ Lw;-耗时:%d", (System.currentTimeMillis() - time)));
time = System.currentTimeMillis();
}
// output
// R'=1/2[(Y'/Y)(R+Y)+R-Y]
Mat temp2 = new Mat(m, n, CvType.CV_32FC1);
Core.add(rChannel, Lw, temp2);
Core.multiply(gain, temp2, temp2);
Core.add(temp2, rChannel, temp2);
Core.subtract(temp2, Lw, temp2);
Core.multiply(temp2, new Scalar(0.5), rChannel);
Core.multiply(rChannel, new Scalar(255), rChannel);
if (isDebug) {
System.out.println(String.format("R'=1/2[(Y'/Y)(R+Y)+R-Y]-耗时:%d", (System.currentTimeMillis() - time)));
time = System.currentTimeMillis();
}
// G'=1/2[(Y'/Y)(G+Y)+G-Y]
Mat temp3 = new Mat(m, n, CvType.CV_32FC1);
Core.add(gChannel, Lw, temp3);
Core.multiply(gain, temp3, temp3);
Core.add(temp3, gChannel, temp3);
Core.subtract(temp3, Lw, temp3);
Core.multiply(temp3, new Scalar(0.5), gChannel);
Core.multiply(gChannel, new Scalar(255), gChannel);
if (isDebug) {
System.out.println(String.format("G'=1/2[(Y'/Y)(G+Y)+G-Y]-耗时:%d", (System.currentTimeMillis() - time)));
time = System.currentTimeMillis();
}
// B'=1/2[(Y'/Y)(B+Y)+B-Y]
Mat temp4 = new Mat(m, n, CvType.CV_32FC1);
Core.add(bChannel, Lw, temp4);
Core.multiply(gain, temp4, temp4);
Core.add(temp4, bChannel, temp4);
Core.subtract(temp4, Lw, temp4);
Core.multiply(temp4, new Scalar(0.5), temp4);
Core.multiply(temp4, new Scalar(255), bChannel);
if (isDebug) {
System.out.println(String.format("B'=1/2[(Y'/Y)(B+Y)+B-Y]-耗时:%d", (System.currentTimeMillis() - time)));
time = System.currentTimeMillis();
}
// merge three color channels to a image
Mat outval = new Mat();
Core.merge(new ArrayList<>(Arrays.asList(bChannel, gChannel, rChannel)), outval);
outval.convertTo(outval, CvType.CV_8U);
if (isDebug) {
System.out.println(String.format("merge-耗时:%d", (System.currentTimeMillis() - time)));
time = System.currentTimeMillis();
}
// 及时释放
temp2.release();
temp3.release();
temp4.release();
rChannel.release();
gChannel.release();
bChannel.release();
Lw.release();
Lg.release();
gain.release();
srcClone.release();
if (isDebug) {
System.out.println(String.format("释放资源-耗时:%d", (System.currentTimeMillis() - time)));
}
return outval;
}
private static Mat localAdaptation(Mat Lg, int rows, int cols, int r, double eps, double krnlRatio) {
int krnlSz = Stream.of(3.0, rows * krnlRatio, cols * krnlRatio).max(Double::compare).orElse(3.0).intValue();
// maximum filter: using dilate to extract the local maximum of a image block, which acts as the maximum filter
// Meanwhile, minimum filter can be achieved by using erode function
Mat Lg_ = new Mat();
Mat kernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(krnlSz, krnlSz), new Point(-1, -1));
Imgproc.dilate(Lg, Lg_, kernel);
// guided image filter
Mat guidedImage = Filters.GuidedImageFilter(Lg, Lg_, r, eps);
// 及时释放
Lg_.release();
kernel.release();
return guidedImage;
}
private static List<Mat> globalAdaptation(Mat r, Mat g, Mat b, int rows, int cols) {
// Calculate Lw(初始亮度矩阵) & maximum of Lw(亮度的最大值)
// 下面的LW是通过参数计算的灰度图,本意是YUV的Y通道
// Lw = 0.299 * Ir + 0.587 * Ig + 0.114 * Ib
Mat Lw = new Mat(rows, cols, r.type());
Core.multiply(r, new Scalar(rParam), r);
Core.multiply(g, new Scalar(gParam), g);
Core.multiply(b, new Scalar(bParam), b);
Core.add(r, g, Lw);
Core.add(Lw, b, Lw);
// the maximum luminance value
// 亮度的最大值
double LwMax = Core.minMaxLoc(Lw).maxVal;
// Calculate log-average luminance and get global adaptation result
// 计算log平均亮度
// Lw_average(log平均亮度) = exp(sum(sum(log(0.001 + Lw))) / (m * n));
Mat Lw_ = Lw.clone();
Core.add(Lw_, new Scalar(0.001), Lw_);
Core.log(Lw_, Lw_);
double LwAver = Math.exp(Core.sumElems(Lw_).val[0] / (rows * cols));
// 并获取新的亮度矩阵(Lg)
// Lg = log(Lw / Lw_aver + 1) / log(Lw_max / Lw_aver + 1);
Mat Lg = Lw.clone();
Core.divide(Lg, new Scalar(LwAver), Lg);
Core.add(Lg, new Scalar(1.0), Lg);
Core.log(Lg, Lg);
Core.divide(Lg, new Scalar(Math.log(LwMax / LwAver + 1.0)), Lg); // Lg is the global adaptation
List<Mat> list = new ArrayList<>();
list.add(Lw);
list.add(Lg);
// 及时释放
b.release();
g.release();
r.release();
Lw_.release();
return list;
}
private static Mat obtainGain(Mat Lout, Mat Lw, int rows, int cols) {
Mat gain = new Mat(rows, cols, Lout.type());
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
if (Lw.get(i, j)[0] == 0) gain.put(i, j, Lout.get(i, j)[0]);
else gain.put(i, j, Lout.get(i, j)[0] / Lw.get(i, j)[0]);
}
}
return gain;
}
@SuppressWarnings("unused")
private static Mat adjustment(Mat alpha, double a) {
double b = Core.minMaxLoc(alpha).maxVal;
int rows = alpha.rows();
int cols = alpha.cols();
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
//double val = alpha.get(i, j)[0];
alpha.put(i, j, (2 * Math.atan(a * alpha.get(i, j)[0] / b) / Math.PI * b));
}
}
return alpha;
}
}
滤波类Filters
package cn.uestc.image.util;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.Point;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.imgproc.Imgproc;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Filters {
/**
* Simplest Color Balance. Performs color balancing via histogram
* normalization.
*
* @param img input color or gray scale image
* @param percent controls the percentage of pixels to clip to white and black. (normally, choose 1~10)
* @return Balanced image in CvType.CV_32F
*/
public static Mat SimplestColorBalance(Mat img, int percent) {
if (percent <= 0)
percent = 5;
img.convertTo(img, CvType.CV_32F);
List<Mat> channels = new ArrayList<>();
int rows = img.rows(); // number of rows of image
int cols = img.cols(); // number of columns of image
int chnls = img.channels(); // number of channels of image
double halfPercent = percent / 200.0;
if (chnls == 3) Core.split(img, channels);
else channels.add(img);
List<Mat> results = new ArrayList<>();
for (int i = 0; i < chnls; i++) {
// find the low and high precentile values (based on the input percentile)
Mat flat = new Mat();
channels.get(i).reshape(1, 1).copyTo(flat);
Core.sort(flat, flat, Core.SORT_ASCENDING);
double lowVal = flat.get(0, (int) Math.floor(flat.cols() * halfPercent))[0];
double topVal = flat.get(0, (int) Math.ceil(flat.cols() * (1.0 - halfPercent)))[0];
// saturate below the low percentile and above the high percentile
// 优化成数组操作
Mat channel = channels.get(i);
float[] data = new float[rows * cols];
channel.get(0, 0, data);
for (int j = 0; j < data.length; j++) {
if (data[j] < lowVal) data[j] = (float) lowVal;
if (data[j] > topVal) data[j] = (float) topVal;
}
channel.put(0, 0, data);
// for (int m = 0; m < rows; m++) {
// for (int n = 0; n < cols; n++) {
// if (channel.get(m, n)[0] < lowVal) channel.put(m, n, lowVal);
// if (channel.get(m, n)[0] > topVal) channel.put(m, n, topVal);
// }
// }
Core.normalize(channel, channel, 0.0, 255.0, Core.NORM_MINMAX);
channel.convertTo(channel, CvType.CV_32F);
results.add(channel);
}
Mat outval = new Mat();
Core.merge(results, outval);
return outval;
}
public static Mat SimplestColorBalance(Mat img, double bottom, double top) {
img.convertTo(img, CvType.CV_32F);
List<Mat> channels = new ArrayList<>();
int rows = img.rows(); // number of rows of image
int cols = img.cols(); // number of columns of image
int chnls = img.channels(); // number of channels of image
if (chnls == 3) Core.split(img, channels);
else channels.add(img);
List<Mat> results = new ArrayList<>();
for (int i = 0; i < chnls; i++) {
// find the low and high precentile values (based on the input percentile)
Mat flat = new Mat();
channels.get(i).reshape(1, 1).copyTo(flat);
Core.sort(flat, flat, Core.SORT_ASCENDING);
int lowIndex = (int) Math.floor(flat.cols() * bottom);
double lowVal = flat.get(0, lowIndex)[0];
int topIndex = (int) Math.ceil(flat.cols() * (1.0 - top));
double topVal = flat.get(0, topIndex)[0];
// saturate below the low percentile and above the high percentile
// 优化成数组操作
Mat channel = channels.get(i);
float[] data = new float[rows * cols];
channel.get(0, 0, data);
for (int j = 0; j < data.length; j++) {
if (data[j] < lowVal) {
data[j] = (float) lowVal;
}
if (data[j] > topVal) {
data[j] = (float) topVal;
}
}
channel.put(0, 0, data);
data = null;
Core.normalize(channel, channel, 1, 0, Core.NORM_MINMAX);
channel.convertTo(channel, CvType.CV_32F);
results.add(channel);
flat.release();
}
Mat outval = new Mat();
Core.merge(results, outval);
for (Mat m : channels) {
m.release();
}
for(Mat m : results){
m.release();
}
return outval;
}
/**
* Guided Image Filter for grayscale image, O(1) time implementation of guided filter
*
* @param I guidance image (should be a gray-scale/single channel image)
* @param p filtering input image (should be a gray-scale/single channel image)
* @param r local window radius
* @param eps regularization parameter
* @return filtered image
*/
public static Mat GuidedImageFilter(Mat I, Mat p, int r, double eps) {
I.convertTo(I, CvType.CV_64FC1);
p.convertTo(p, CvType.CV_64FC1);
//[hei, wid] = size(I);
int rows = I.rows();
int cols = I.cols();
// N = boxfilter(ones(hei, wid), r); % the size of each local patch; N=(2r+1)^2 except for boundary pixels.
Mat N = new Mat();
Mat temp1 = Mat.ones(rows, cols, I.type());
Imgproc.boxFilter(temp1, N, -1, new Size(r * 2 + 1, r * 2 + 1), new Point(-1, -1), false, Core.BORDER_CONSTANT);
temp1.release();
// mean_I = boxfilter(I, r) ./ N;
Mat mean_I = new Mat();
Imgproc.boxFilter(I, mean_I, -1, new Size(r * 2 + 1, r * 2 + 1), new Point(-1, -1), false, Core.BORDER_CONSTANT);
Core.divide(mean_I, N, mean_I);
// mean_p = boxfilter(p, r) ./ N
Mat mean_p = new Mat();
Imgproc.boxFilter(p, mean_p, -1, new Size(r * 2 + 1, r * 2 + 1), new Point(-1, -1), false, Core.BORDER_CONSTANT);
Core.divide(mean_p, N, mean_p);
// mean_Ip = boxfilter(I.*p, r) ./ N;
Mat mean_Ip = new Mat();
Mat temp2 = I.mul(p);
Imgproc.boxFilter(temp2, mean_Ip, -1, new Size(r * 2 + 1, r * 2 + 1), new Point(-1, -1), false, Core.BORDER_CONSTANT);
temp2.release();
Core.divide(mean_Ip, N, mean_Ip);
// cov_Ip = mean_Ip - mean_I .* mean_p; % this is the covariance of (I, p) in each local patch.
Mat cov_Ip = new Mat();
Mat temp3 = mean_I.mul(mean_p);
Core.subtract(mean_Ip, temp3, cov_Ip);
temp3.release();
// mean_II = boxfilter(I.*I, r) ./ N;
Mat mean_II = new Mat();
Mat temp4 = I.mul(I);
Imgproc.boxFilter(temp4, mean_II, -1, new Size(r * 2 + 1, r * 2 + 1), new Point(-1, -1), false, Core.BORDER_CONSTANT);
temp4.release();
Core.divide(mean_II, N, mean_II);
// var_I = mean_II - mean_I .* mean_I;
Mat var_I = new Mat();
Mat temp5 = mean_I.mul(mean_I);
Core.subtract(mean_II, temp5, var_I);
temp5.release();
// a = cov_Ip ./ (var_I + eps); % Eqn. (5) in the paper;
Mat a = new Mat();
Core.add(var_I, new Scalar(eps), a);
Core.divide(cov_Ip, a, a);
//b = mean_p - a .* mean_I; % Eqn. (6) in the paper;
Mat b = new Mat();
Mat temp6 = a.mul(mean_I);
Core.subtract(mean_p, temp6, b);
temp6.release();
// mean_a = boxfilter(a, r) ./ N;
Mat mean_a = new Mat();
Imgproc.boxFilter(a, mean_a, -1, new Size(r * 2 + 1, r * 2 + 1), new Point(-1, -1), false, Core.BORDER_CONSTANT);
Core.divide(mean_a, N, mean_a);
// mean_b = boxfilter(b, r) ./ N;
Mat mean_b = new Mat();
Imgproc.boxFilter(b, mean_b, -1, new Size(r * 2 + 1, r * 2 + 1), new Point(-1, -1), false, Core.BORDER_CONSTANT);
Core.divide(mean_b, N, mean_b);
// q = mean_a .* I + mean_b; % Eqn. (8) in the paper;
Mat q = new Mat();
Mat temp7 = mean_a.mul(I);
Core.add(temp7, mean_b, q);
temp7.release();
q.convertTo(q, CvType.CV_32F);
// 及时释放
N.release();
mean_I.release();
mean_p.release();
mean_Ip.release();
cov_Ip.release();
mean_II.release();
var_I.release();
a.release();
b.release();
mean_a.release();
mean_b.release();
return q;
}
/**
* Guided Image Filter for Color Image
*
* @param origI guidance image (should be a gray-scale/single channel image)
* @param p filtering input image (should be a gray-scale/single channel image)
* @param r local window radius
* @param eps regularization parameter
* @param s blocks number, s * s
* @param depth image depth
* @return filtered image
*/
@SuppressWarnings("unused")
public static Mat GuidedImageFilter_Color(Mat origI, Mat p, int r, double eps, double s, int depth) {
//Pre-defined parameters
ArrayList<Mat> Ichannels = new ArrayList<>();
ArrayList<Mat> Isubchannels = new ArrayList<>();
int Idepth;
double r_sub;
Mat mean_I_r;
Mat mean_I_g;
Mat mean_I_b;
Mat invrr = new Mat();
Mat invrg = new Mat();
Mat invrb = new Mat();
Mat invgg = new Mat();
Mat invgb = new Mat();
Mat invbb = new Mat();
// Process
Mat I;
if (origI.depth() == CvType.CV_32F || origI.depth() == CvType.CV_64F) {
I = origI.clone();
} else {
I = convertTo(origI, CvType.CV_32F);
}
Idepth = I.depth();
Core.split(I, Ichannels);
Mat I_sub = new Mat();
Imgproc.resize(I, I_sub, new Size(I.cols() / s, I.rows() / s), 0.0, 0.0, Imgproc.INTER_NEAREST);
Core.split(I_sub, Isubchannels);
r_sub = r / s;
mean_I_r = boxfilter(Isubchannels.get(0), (int) r_sub);
mean_I_g = boxfilter(Isubchannels.get(1), (int) r_sub);
mean_I_b = boxfilter(Isubchannels.get(2), (int) r_sub);
// variance of I in each local patch: the matrix Sigma in Eqn (14).
// Note the variance in each local patch is a 3x3 symmetric matrix:
// rr, rg, rb
// Sigma = rg, gg, gb
// rb, gb, bb
Mat var_I_rr = new Mat();
Mat var_I_rg = new Mat();
Mat var_I_rb = new Mat();
Mat var_I_gg = new Mat();
Mat var_I_gb = new Mat();
Mat var_I_bb = new Mat();
Mat temp1 = new Mat();
Core.subtract(boxfilter(Isubchannels.get(0).mul(Isubchannels.get(0)), (int) r_sub),
mean_I_r.mul(mean_I_r), temp1);
Core.add(temp1, new Scalar(eps), var_I_rr);
Core.subtract(boxfilter(Isubchannels.get(0).mul(Isubchannels.get(1)), (int) r_sub),
mean_I_r.mul(mean_I_g), var_I_rg);
Core.subtract(boxfilter(Isubchannels.get(0).mul(Isubchannels.get(2)), (int) r_sub),
mean_I_r.mul(mean_I_b), var_I_rb);
Core.subtract(boxfilter(Isubchannels.get(1).mul(Isubchannels.get(1)), (int) r_sub),
mean_I_g.mul(mean_I_g), temp1);
Core.add(temp1, new Scalar(eps), var_I_gg);
Core.subtract(boxfilter(Isubchannels.get(1).mul(Isubchannels.get(2)), (int) r_sub),
mean_I_g.mul(mean_I_b), var_I_gb);
Core.subtract(boxfilter(Isubchannels.get(2).mul(Isubchannels.get(2)), (int) r_sub),
mean_I_b.mul(mean_I_b), temp1);
Core.add(temp1, new Scalar(eps), var_I_bb);
// Inverse of Sigma + eps * I
Core.subtract(var_I_gg.mul(var_I_bb), var_I_gb.mul(var_I_gb), invrr);
Core.subtract(var_I_gb.mul(var_I_rb), var_I_rg.mul(var_I_bb), invrg);
Core.subtract(var_I_rg.mul(var_I_gb), var_I_gg.mul(var_I_rb), invrb);
Core.subtract(var_I_rr.mul(var_I_bb), var_I_rb.mul(var_I_rb), invgg);
Core.subtract(var_I_rb.mul(var_I_rg), var_I_rr.mul(var_I_gb), invgb);
Core.subtract(var_I_rr.mul(var_I_gg), var_I_rg.mul(var_I_rg), invbb);
Mat covDet = new Mat();
Core.add(invrr.mul(var_I_rr), invrg.mul(var_I_rg), temp1);
Core.add(temp1, invrb.mul(var_I_rb), covDet);
Core.divide(invrr, covDet, invrr);
Core.divide(invrg, covDet, invrg);
Core.divide(invrb, covDet, invrb);
Core.divide(invgg, covDet, invgg);
Core.divide(invgb, covDet, invgb);
Core.divide(invbb, covDet, invbb);
Mat p2 = convertTo(p, Idepth);
Mat result = new Mat();
if (p.channels() == 1) {
result = filterSingleChannel(p2, s, Isubchannels, Ichannels, mean_I_r, mean_I_g, mean_I_b, invrr, invrg,
invrb, invgg, invgb, invbb, r_sub);
} else {
ArrayList<Mat> pc = new ArrayList<>();
Core.split(p2, pc);
for (int i = 0; i < pc.size(); i++) {
pc.set(i, filterSingleChannel(pc.get(i), s, Isubchannels, Ichannels, mean_I_r, mean_I_g, mean_I_b, invrr,
invrg, invrb, invgg, invgb, invbb, r_sub));
}
Core.merge(pc, result);
}
return convertTo(result, depth == -1 ? p.depth() : depth);
}
private static Mat boxfilter(Mat I, int r) {
Mat result = new Mat();
Imgproc.blur(I, result, new Size(r, r));
return result;
}
private static Mat convertTo(Mat mat, int depth) {
if (mat.depth() == depth) {
return mat;
}
Mat result = new Mat();
mat.convertTo(result, depth);
return result;
}
private static Mat filterSingleChannel(Mat p, double s, ArrayList<Mat> Isubchannels, ArrayList<Mat> Ichannels,
Mat mean_I_r, Mat mean_I_g, Mat mean_I_b, Mat invrr, Mat invrg, Mat invrb, Mat invgg, Mat invgb,
Mat invbb, double r_sub) {
Mat p_sub = new Mat();
Imgproc.resize(p, p_sub, new Size(p.cols() / s, p.rows() / s), 0.0, 0.0, Imgproc.INTER_NEAREST);
Mat mean_p = boxfilter(p_sub, (int) r_sub);
Mat mean_Ip_r = boxfilter(Isubchannels.get(0).mul(p_sub), (int) r_sub);
Mat mean_Ip_g = boxfilter(Isubchannels.get(1).mul(p_sub), (int) r_sub);
Mat mean_Ip_b = boxfilter(Isubchannels.get(2).mul(p_sub), (int) r_sub);
// convariance of (I, p) in each local patch
Mat cov_Ip_r = new Mat();
Mat cov_Ip_g = new Mat();
Mat cov_Ip_b = new Mat();
Core.subtract(mean_Ip_r, mean_I_r.mul(mean_p), cov_Ip_r);
Core.subtract(mean_Ip_g, mean_I_g.mul(mean_p), cov_Ip_g);
Core.subtract(mean_Ip_b, mean_I_b.mul(mean_p), cov_Ip_b);
Mat temp1 = new Mat();
Mat a_r = new Mat();
Mat a_g = new Mat();
Mat a_b = new Mat();
Core.add(invrr.mul(cov_Ip_r), invrg.mul(cov_Ip_g), temp1);
Core.add(temp1, invrb.mul(cov_Ip_b), a_r);
Core.add(invrg.mul(cov_Ip_r), invgg.mul(cov_Ip_g), temp1);
Core.add(temp1, invgb.mul(cov_Ip_b), a_g);
Core.add(invrb.mul(cov_Ip_r), invgb.mul(cov_Ip_g), temp1);
Core.add(temp1, invbb.mul(cov_Ip_b), a_b);
Mat b = new Mat();
Core.subtract(mean_p, a_r.mul(mean_I_r), b);
Core.subtract(b, a_g.mul(mean_I_g), b);
Core.subtract(b, a_b.mul(mean_I_b), b);
Mat mean_a_r = boxfilter(a_r, (int) r_sub);
Mat mean_a_g = boxfilter(a_g, (int) r_sub);
Mat mean_a_b = boxfilter(a_b, (int) r_sub);
Mat mean_b = boxfilter(b, (int) r_sub);
Imgproc.resize(mean_a_r, mean_a_r,
new Size(Ichannels.get(0).cols(), Ichannels.get(0).rows()), 0.0, 0.0, Imgproc.INTER_LINEAR);
Imgproc.resize(mean_a_g, mean_a_g,
new Size(Ichannels.get(0).cols(), Ichannels.get(0).rows()), 0.0, 0.0, Imgproc.INTER_LINEAR);
Imgproc.resize(mean_a_b, mean_a_b,
new Size(Ichannels.get(0).cols(), Ichannels.get(0).rows()), 0.0, 0.0, Imgproc.INTER_LINEAR);
Imgproc.resize(mean_b, mean_b,
new Size(Ichannels.get(0).cols(), Ichannels.get(0).rows()), 0.0, 0.0, Imgproc.INTER_LINEAR);
Mat result = new Mat();
Core.add(mean_a_r.mul(Ichannels.get(0)), mean_a_g.mul(Ichannels.get(1)), temp1);
Core.add(temp1, mean_a_b.mul(Ichannels.get(2)), temp1);
Core.add(temp1, mean_b, result);
return result;
}
}
以上代码均可以在Opencv4Java下运行,速度是一方面,大概一帧需要0.5秒,处理视频可以采用多线程并发,相互帧不影响。
相对原算法,去掉了局部提升、优化了RGB的提升方式,实现了完整的Java版本的代码。
C版本
可移植DLL或SO,供Windows或Linux或Android使用,只实现了全局提升。
//
// Created by ShenHongBin on 2019/3/14.
//
#ifndef SMILETEETH_IMAGEENHANCE_H
#define SMILETEETH_IMAGEENHANCE_H
#include <stdio.h>
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
class ImageEnhanceHelper {
public:
ImageEnhanceHelper(int rows, int cols) {
this->cols = cols;
this->rows = rows;
}
Mat enhance(Mat src, int r, double eps, double eta, double lambda, double krnlRatio);
Mat enhanceWithoutLocal(Mat src);
vector<Mat> globalAdaptation(Mat r, Mat g, Mat b);
Mat localAdaptation(Mat Lg, int rows, int cols, int r, double eps, double krnlRatio);
void close() {
}
private:
int rows;
int cols;
const int r = 10;
const double eps = 0.01;
const double eta = 36.0;
const double lambda = 10.0;
const double krnlRatio = 0.01;
};
Mat ImageEnhanceHelper::enhance(Mat src, int r, double eps, double eta, double lambda,
double krnlRatio) {
return Mat();
}
Mat ImageEnhanceHelper::enhanceWithoutLocal(Mat src) {
Mat srcClone, gain, outMat, lw, lg;
vector<Mat> bgr;
srcClone = src.clone();
srcClone.convertTo(srcClone, CV_32F);
srcClone = srcClone / 255.0f;
split(srcClone, bgr);
float a = bgr[0].at<float>(0,0);
a = bgr[1].at<float>(0,0);
a = bgr[2].at<float>(0,0);
vector<Mat> vectorMat = globalAdaptation(bgr[0], bgr[1], bgr[2]);
lw = vectorMat[0];
lg = vectorMat[1];
gain = vectorMat[1] / vectorMat[0];
patchNaNs(gain, 0);
a = gain.at<float>(0,0);
a = lw.at<float>(0,0);
a = lg.at<float>(0,0);
// R'=1/2[(Y'/Y)(R+Y)+R-Y]
Mat temp;
// cvtColor(lw, lw, COLOR_GRAY2BGR);
// cvtColor(gain, gain, COLOR_GRAY2BGR);
//
//
// add(outMat, lw, temp);
// multiply(temp, gain, temp);
// add(temp, outMat, temp);
// subtract(temp, lw, temp);
// outMat = temp.mul(Scalar(0.5));
temp = bgr[0] + lw;
multiply(temp, gain, temp);
bgr[0] = (0.5f) * (temp + bgr[0] - lw);
temp = bgr[1] + lw;
multiply(temp, gain, temp);
bgr[1] = (0.5f) * (temp + bgr[1] - lw);
temp = bgr[2] + lw;
multiply(temp, gain, temp);
bgr[2] = (0.5f) * (temp + bgr[2] - lw);
merge(bgr, outMat);
multiply(outMat, Scalar(255, 255, 255), outMat);
outMat.convertTo(outMat, CV_8U);
// 释放资源
lw.release();
lg.release();
gain.release();
temp.release();
bgr[0].release();
bgr[1].release();
bgr[2].release();
srcClone.release();
return outMat;
}
vector<Mat> ImageEnhanceHelper::globalAdaptation(Mat r, Mat g, Mat b) {
vector<Mat> vectorMat;
Mat lw, lwClone, lg;
lw = 0.299 * r + 0.587 * g + 0.114 * b;
double min, max;
Point2i minLoc, maxLoc;
minMaxLoc(lw, &min, &max, &minLoc, &maxLoc);
// Lw_average(log平均亮度) = exp(sum(sum(log(0.001 + Lw))) / (m * n));
double lwAver;
lwClone = lw + 0.001;
log(lwClone, lwClone);
lwAver = exp(sum(sum(lwClone))[0] / (rows * cols));
// Lg = log(Lw / Lw_aver + 1) / log(Lw_max / Lw_aver + 1);
lg = lw / lwAver + 1;
log(lg, lg);
lg = lg / log(max / lwAver + 1);
vectorMat.push_back(lw);
vectorMat.push_back(lg);
// 释放资源
lwClone.release();
return vectorMat;
}
Mat ImageEnhanceHelper::localAdaptation(Mat Lg, int rows, int cols, int r, double eps,
double krnlRatio) {
return Mat();
}
#endif //SMILETEETH_IMAGEENHANCE_H