本文主要参考 博客 OpenCV图像处理专栏十二 |《基于二维伽马函数的光照不均匀图像自适应校正算法》 中的代码
同时也参考了原论文: 刘志成,王殿伟,刘颖,刘学杰.基于二维伽马函数的光照不均匀图像自适应校正算法[J].北京理工大学学报,2016,36(02):191-196+214.
做了以下定制化内容:
- 抑制高光(如日光,过曝)
- 全局过暗全局使用BGR颜色(太暗了,再使用HSV颜色,极易出现彩色噪音,虽然色彩保留较好,但是效果和直接使用HSV颜色模式下V直方图均衡差不多)
- 局部过暗使用HSV混合BGR(实验发现只使用HSV颜色,在过暗时容易出现彩色噪音,因为第亮度时,只改变V,会对HS产生较大变化,我猜的)
- 不再使用平均亮度作为gamma校正标准,而是使用给定亮度
- 不再使用3尺度加权高斯滤波求光照分量,而是使用一次均值滤波(追求速度,虽然不是基于Retinex 理论,但是发现效果差别不是很大)
优化部分:
- 使用并行多线程,速度提升一倍
- 更多基于opencv操作,在i5 mac 上8k图平均1.5s处理完
- 对过暗和过亮部分做了优化
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc/imgproc.hpp>
class ParallelMix : public cv::ParallelLoopBody{
public:
ParallelMix(cv::Mat& _v, cv::Mat& _v_i, cv::Mat& _src, const float _average): v(_v), v_i(_v_i), src(_src), average(_average){}
void operator()(const cv::Range& range) const{
for (int i = range.start; i < range.end; ++i){
float *vp = v.ptr<float>(i);
float *ip = v_i.ptr<float>(i);
cv::Vec3f *srcp = src.ptr<cv::Vec3f>(i);
for (int j = 0; j < src.cols; j++) {
float dealta = ip[j] - average;
float r = dealta / average;
if(dealta > 0){
// inhibition of highlights
r *= powf((1 + dealta), 3);
}
float gamma = powf(2 , r );
if(dealta > -0.4){
float x = powf(vp[j] , gamma);
vp[j] = x;
srcp[j] = cv::Vec3f(0, 0, 0);
}else{
// to reduce noise
float dd = -0.4 - r;
float bx = powf(srcp[j][0], gamma+dd/5);
float gx = powf(srcp[j][1], gamma+dd/5);
float rx = powf(srcp[j][2], gamma+dd/5);
srcp[j][0] = bx * dd;
srcp[j][1] = gx * dd;
srcp[j][2] = rx * dd;
vp[j] = powf(vp[j] , gamma) * (1 - dd);
}
}
}
}
private:
cv::Mat& v;
cv::Mat& v_i;
cv::Mat& src;
float average;
};
class ParallelBGR : public cv::ParallelLoopBody{
public:
ParallelBGR(cv::Mat& _v_i, cv::Mat& _src, const float _average, const float _e): v_i(_v_i), src(_src), average(_average), e(_e){}
void operator()(const cv::Range& range) const{
for (int i = range.start; i < range.end; ++i){
float *ip = v_i.ptr<float>(i);
cv::Vec3f *srcp = src.ptr<cv::Vec3f>(i);
for (int j = 0; j < src.cols; j++) {
float dealta = ip[j] - average;
float r = dealta / average;
float gamma = powf(2 , r + e);
float bx = powf(srcp[j][0], gamma);
float gx = powf(srcp[j][1], gamma);
float rx = powf(srcp[j][2], gamma);
srcp[j][0] = bx;
srcp[j][1] = gx;
srcp[j][2] = rx;
}
}
}
private:
cv::Mat& v_i;
cv::Mat& src;
float average;
float e;
};
//Adaptive Local Gamma Correction based on mean value adjustment
cv::Mat agc_mean_mix(cv::Mat& src, int kernel_size = -1, float average = 0.68) {
cv::Mat src_hsv;
std::vector<cv::Mat> hsv_channels(3);
src.convertTo(src, CV_32FC3, 1 / 255.0);
cv::cvtColor(src, src_hsv, cv::COLOR_BGR2HSV);
cv::split(src_hsv, hsv_channels);
cv::Mat& v = hsv_channels[2];
// light component of V
cv::Mat v_i;
kernel_size = kernel_size == -1 ? std::min(src.rows, src.cols) / 30 : kernel_size;
kernel_size = kernel_size % 2 ? kernel_size : kernel_size -1 ;
// mean filtering replaces time-consuming gaussian filtering
cv::blur(v, v_i, cv::Size(kernel_size, kernel_size));
float mean = cv::mean(v_i)[0];
float e = mean - average;
if(e > -0.4){
cv::parallel_for_(cv::Range(0, src.rows), ParallelMix(v, v_i, src, average));
cv::Mat dst_hsv;
cv::merge(hsv_channels, dst_hsv);
cv::cvtColor(dst_hsv, dst_hsv, cv::COLOR_HSV2BGR);
dst_hsv.convertTo(dst_hsv, CV_8UC3, 255);
cv::Mat dst_rgb;
src.convertTo(dst_rgb, CV_8UC3, 255);
cv::Mat dst;
cv::add(dst_hsv, dst_rgb, dst);
return dst;
}else{
// brightness too low global use bgr color
cv::parallel_for_(cv::Range(0, src.rows), ParallelBGR(v_i, src, average, e));
cv::Mat dst;
src.convertTo(dst, CV_8UC3, 255);
return dst;
}
}
结语
实验对比 查看
本文代码 链接 ,如果喜欢请给个star