数字图像处理:自适应局部gamma校正

本文主要参考 博客 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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章