數字圖像處理:自適應局部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

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