Opencv c++对比度增强(1.多尺度Retinex;2.LICE-Retinex;3.自适应对数映射。)

2023-11-07 17:50

本文主要是介绍Opencv c++对比度增强(1.多尺度Retinex;2.LICE-Retinex;3.自适应对数映射。),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

常用对比度增强:直方图均衡化、gamma变换、对数变换,基本可以解决常见的工业问题。

 

本期主要讲述的是三种效果较好的对比度增强算法:1.多尺度Retinex;2. LICE -Retinex;2. 自适应对数映射。

    1. 多尺度Retinex

Retinex 理论的基本内容是物体的颜色是由物体对长波(红)、中波(绿)和短波(蓝)光线的反射能力决定的,而不是由反射光强度的绝对值决定的;物体的色彩不受光照非均性的影响,具有一致性,即Retinex理论是以色感一致性(颜色恒常性)为基础的。如下图所示,观察者所看到的物体的图像S是由物体表面对入射光L反射得到的,反射率R由物体本身决定,不受入射光L变化。 

由于SSR 单尺度需要在颜色保真度和细节保持度上追去一个完美的平衡,而这个平衡在 应对不同图像的时候一般都有差别,所以针对这个清况,Jobson 和 Rahman 等人再次提出了 多尺度的 Retinex 算法(MSR),即对一幅图像在不同的尺度上利用高斯进行滤波,然后在对不同尺度上的滤波结果进行平均加权,获得所估计的照度图像。MSR 简单来说就是相当于做 多次的 SSR。

1.2 LICE-Retinex(低亮度图像Retinex图像增强)

这个算法中有几个比较重要的点:

  1. 该算法分为两个部分,第一部分是全局的自适应,第二部分是局部的自适应;
  2. 该算法是基于Retinex算法的;
  3. 该算法的局部自适应部分使用了导向滤波器。

1.2.1全局自适应

本节并非基于Retinex,而是通过首先计算图像像素的对数平均亮度值和最大亮度值,并以对数函数的形式压缩图像的动态范围来提高图像的对比度。

1.2.2区域自适应

难点在于Retinex和导向滤波的理解。

2.1Guide Filter导向滤波

导向滤波(Guided Fliter)显式地利用 guidance image 计算输出图像,其中 guidance image 可以是输入图像本身或者其他图像。导向滤波比起双边滤波来说在边界附近效果较好;另外,它还具有 O(N) 的线性时间的速度优势。和双边滤波的仿真对比效果如下:

基本原理如下:

 

 其中,p为输入图像,I 为导向图,q 为输出图像。在这里我们认为输出图像可以看成导向图I 的一个局部线性变换,其中k是局部化的窗口的中点,因此属于窗口 ωkωk 的pixel,都可以用导向图对应的pixel通过(ak,bk)的系数进行变换计算出来。同时,我们认为输入图像 p 是由 q 加上我们不希望的噪声或纹理得到的,因此有p = q + n 。

接下来就是解出这样的系数,使得p和q的差别尽量小,而且还可以保持局部线性模型。这里利用了带有正则项的 linear ridge regression(岭回归).

求解以上方程得到a和b在局部的值,对于一个要求的pixel可能含在多个window中,因此平均后得到:

 

 

cv::Mat GuidedFilter(cv::Mat& I, cv::Mat& p, int r, float eps)
{
#define _cv_type_	CV_32FC1cv::Mat _I;I.convertTo(_I, _cv_type_);I = _I;cv::Mat _p;p.convertTo(_p, _cv_type_);p = _p;//[hei, wid] = size(I);  int hei = I.rows;int wid = I.cols;r = 2 * r + 1;//因为opencv自带的boxFilter()中的Size,比如9x9,我们说半径为4 //mean_I = boxfilter(I, r) ./ N;  cv::Mat mean_I;cv::boxFilter(I, mean_I, _cv_type_, cv::Size(r, r));//mean_p = boxfilter(p, r) ./ N;  cv::Mat mean_p;cv::boxFilter(p, mean_p, _cv_type_, cv::Size(r, r));//mean_Ip = boxfilter(I.*p, r) ./ N;  cv::Mat mean_Ip;cv::boxFilter(I.mul(p), mean_Ip, _cv_type_, cv::Size(r, r));//cov_Ip = mean_Ip - mean_I .* mean_p; % this is the covariance of (I, p) in each local patch.  cv::Mat cov_Ip = mean_Ip - mean_I.mul(mean_p);//mean_II = boxfilter(I.*I, r) ./ N;  cv::Mat mean_II;cv::boxFilter(I.mul(I), mean_II, _cv_type_, cv::Size(r, r));//var_I = mean_II - mean_I .* mean_I;  cv::Mat var_I = mean_II - mean_I.mul(mean_I);//a = cov_Ip ./ (var_I + eps); % Eqn. (5) in the paper;     cv::Mat a = cov_Ip / (var_I + eps);//b = mean_p - a .* mean_I; % Eqn. (6) in the paper;  cv::Mat b = mean_p - a.mul(mean_I);//mean_a = boxfilter(a, r) ./ N;  cv::Mat mean_a;cv::boxFilter(a, mean_a, _cv_type_, cv::Size(r, r));//mean_b = boxfilter(b, r) ./ N;  cv::Mat mean_b;cv::boxFilter(b, mean_b, _cv_type_, cv::Size(r, r));//q = mean_a .* I + mean_b; % Eqn. (8) in the paper;  cv::Mat q = mean_a.mul(I) + mean_b;return q;
}cv::Mat LICE_retinex(const cv::Mat& img, bool LocalAdaptation = false, bool ContrastCorrect = true)
{const int cx = img.cols / 2;const int cy = img.rows / 2;Mat temp, Lw;img.convertTo(temp, CV_32FC3);cvtColor(temp, Lw, COLOR_BGR2GRAY);// global adaptation double LwMax;cv::minMaxLoc(Lw, NULL, &LwMax);Mat Lw_;const int num = img.rows * img.cols;cv::log(Lw + 1e-3f, Lw_);float LwAver = exp(cv::sum(Lw_)[0] / num);// globally compress the dynamic range of a HDR scene we use the following function in(4) presented in[5].Mat Lg;cv::log(Lw / LwAver + 1.f, Lg);cv::divide(Lg, log(LwMax / LwAver + 1.f), Lg);// local adaptation Mat Lout;if (LocalAdaptation) {int kernelSize = floor(std::max(3, std::max(img.rows / 100, img.cols / 100)));Mat Lp, kernel = cv::getStructuringElement(MORPH_RECT, Size(kernelSize, kernelSize));cv::dilate(Lg, Lp, kernel);Mat Hg = GuidedFilter(Lg, Lp, 10, 0.01f);double eta = 36;double LgMax;cv::minMaxLoc(Lg, NULL, &LgMax);Mat alpha = 1.0f + Lg * (eta / LgMax);Mat Lg_;cv::log(Lg + 1e-3f, Lg_);float LgAver = exp(cv::sum(Lg_)[0] / num);float lambda = 10;float beta = lambda * LgAver;cv::log(Lg / Hg + beta, Lout);cv::multiply(alpha, Lout, Lout);cv::normalize(Lout, Lout, 0, 255, NORM_MINMAX);}else {cv::normalize(Lg, Lout, 0, 255, NORM_MINMAX);}Mat gain(img.rows, img.cols, CV_32F);for (int i = 0; i < img.rows; i++) {for (int j = 0; j < img.cols; j++) {float x = Lw.at<float>(i, j);float y = Lout.at<float>(i, j);if (0 == x) gain.at<float>(i, j) = y;else gain.at<float>(i, j) = y / x;}}Mat out;Mat bgr[3];cv::split(temp, bgr);if (ContrastCorrect) {bgr[0] = (gain.mul(bgr[0] + Lw) + bgr[0] - Lw) * 0.5f;bgr[1] = (gain.mul(bgr[1] + Lw) + bgr[1] - Lw) * 0.5f;bgr[2] = (gain.mul(bgr[2] + Lw) + bgr[2] - Lw) * 0.5f;}else {cv::multiply(bgr[0], gain, bgr[0]);cv::multiply(bgr[1], gain, bgr[1]);cv::multiply(bgr[2], gain, bgr[2]);}cv::merge(bgr, 3, out);out.convertTo(out, CV_8UC3);return out;
}

算法具体过程如下:

 

接下来上原图及仿真图:

 原图

仿真图

1.2 自适应对数映射

本文描述了一种快速、高质量的色调映射技术,用于在亮度值动态范围有限的设备上显示高对比度图像。该方法基于亮度值的对数压缩,模仿人类对光的反应。引入了一个偏置功率函数来自适应地改变对数基底,从而保持了良好的细节和对比度。为了提高暗区域的对比度,建议对伽马校正程序进行更改。我们的自适应对数映射技术能够产生具有高动态内容的感知调谐图像,并以交互速度工作。我们展示了我们的色调映射技术在高动态范围视频播放器中的成功应用,该播放器能够调整任何类型显示器的最佳观看条件,同时考虑用户对亮度、对比度压缩和细节再现的偏好。

废话不多说,上图。

颜色饱和程度比LICE-Retinex要高。

cv::Mat adaptive_logarithmic_mapping(const cv::Mat& img)
{Mat ldrDrago, result;img.convertTo(ldrDrago, CV_32FC3, 1.0f / 255);cvtColor(ldrDrago, ldrDrago, cv::COLOR_BGR2XYZ);Ptr<TonemapDrago> tonemapDrago = createTonemapDrago(1.f, 1.f, 0.85f);tonemapDrago->process(ldrDrago, result);cvtColor(result, result, cv::COLOR_XYZ2BGR);result.convertTo(result, CV_8UC3, 255);return result;
}

最后,总的仿真代码,复制后就能运行,博主用的是vs2019+opencv4.2.0

#include<opencv2/opencv.hpp>
#include<iostream>using namespace std;
using namespace cv;static void gaussian_filter(cv::Mat& img, double sigma)
{int filter_size;// Reject unreasonable demandsif (sigma > 300) sigma = 300;// get needed filter size (enforce oddness)filter_size = (int)floor(sigma * 6) / 2;filter_size = filter_size * 2 + 1;// If 3 sigma is less than a pixel, why bother (ie sigma < 2/3)if (filter_size < 3) return;// FilterGaussianBlur(img, img, cv::Size(filter_size, filter_size), 0);
}cv::Mat multi_scale_retinex(const cv::Mat& img, const std::vector<double>& weights, const std::vector<double>& sigmas, int gain, int offset)
{cv::Mat fA, fB, fC;img.convertTo(fB, CV_32FC3);cv::log(fB, fA);// Normalize according to given weightsdouble weight = 0;size_t num = weights.size();for (int i = 0; i < num; i++)weight += weights[i];if (weight != 1.0f)fA *= weight;// Filter at each scalefor (int i = 0; i < num; i++){cv::Mat blur = fB.clone();gaussian_filter(blur, sigmas[i]);cv::log(blur, fC);// Compute weighted differencefC *= weights[i];fA -= fC;}
#if 0// Color restorationfloat restoration_factor = 6;float color_gain = 2;cv::normalize(fB, fC, restoration_factor, cv::NORM_L1);cv::log(fC + 1, fC);cv::multiply(fA, fC, fA, color_gain);
#endif// RestoreMat result = (fA * gain) + offset;result.convertTo(result, CV_8UC3);return result;
}cv::Mat GuidedFilter(cv::Mat& I, cv::Mat& p, int r, float eps)
{
#define _cv_type_	CV_32FC1cv::Mat _I;I.convertTo(_I, _cv_type_);I = _I;cv::Mat _p;p.convertTo(_p, _cv_type_);p = _p;//[hei, wid] = size(I);  int hei = I.rows;int wid = I.cols;r = 2 * r + 1;//因为opencv自带的boxFilter()中的Size,比如9x9,我们说半径为4 //mean_I = boxfilter(I, r) ./ N;  cv::Mat mean_I;cv::boxFilter(I, mean_I, _cv_type_, cv::Size(r, r));//mean_p = boxfilter(p, r) ./ N;  cv::Mat mean_p;cv::boxFilter(p, mean_p, _cv_type_, cv::Size(r, r));//mean_Ip = boxfilter(I.*p, r) ./ N;  cv::Mat mean_Ip;cv::boxFilter(I.mul(p), mean_Ip, _cv_type_, cv::Size(r, r));//cov_Ip = mean_Ip - mean_I .* mean_p; % this is the covariance of (I, p) in each local patch.  cv::Mat cov_Ip = mean_Ip - mean_I.mul(mean_p);//mean_II = boxfilter(I.*I, r) ./ N;  cv::Mat mean_II;cv::boxFilter(I.mul(I), mean_II, _cv_type_, cv::Size(r, r));//var_I = mean_II - mean_I .* mean_I;  cv::Mat var_I = mean_II - mean_I.mul(mean_I);//a = cov_Ip ./ (var_I + eps); % Eqn. (5) in the paper;     cv::Mat a = cov_Ip / (var_I + eps);//b = mean_p - a .* mean_I; % Eqn. (6) in the paper;  cv::Mat b = mean_p - a.mul(mean_I);//mean_a = boxfilter(a, r) ./ N;  cv::Mat mean_a;cv::boxFilter(a, mean_a, _cv_type_, cv::Size(r, r));//mean_b = boxfilter(b, r) ./ N;  cv::Mat mean_b;cv::boxFilter(b, mean_b, _cv_type_, cv::Size(r, r));//q = mean_a .* I + mean_b; % Eqn. (8) in the paper;  cv::Mat q = mean_a.mul(I) + mean_b;return q;
}cv::Mat LICE_retinex(const cv::Mat& img, bool LocalAdaptation = false, bool ContrastCorrect = true)
{const int cx = img.cols / 2;const int cy = img.rows / 2;Mat temp, Lw;img.convertTo(temp, CV_32FC3);cvtColor(temp, Lw, COLOR_BGR2GRAY);// global adaptation double LwMax;cv::minMaxLoc(Lw, NULL, &LwMax);Mat Lw_;const int num = img.rows * img.cols;cv::log(Lw + 1e-3f, Lw_);float LwAver = exp(cv::sum(Lw_)[0] / num);// globally compress the dynamic range of a HDR scene we use the following function in(4) presented in[5].Mat Lg;cv::log(Lw / LwAver + 1.f, Lg);cv::divide(Lg, log(LwMax / LwAver + 1.f), Lg);// local adaptation Mat Lout;if (LocalAdaptation) {int kernelSize = floor(std::max(3, std::max(img.rows / 100, img.cols / 100)));Mat Lp, kernel = cv::getStructuringElement(MORPH_RECT, Size(kernelSize, kernelSize));cv::dilate(Lg, Lp, kernel);Mat Hg = GuidedFilter(Lg, Lp, 10, 0.01f);double eta = 36;double LgMax;cv::minMaxLoc(Lg, NULL, &LgMax);Mat alpha = 1.0f + Lg * (eta / LgMax);Mat Lg_;cv::log(Lg + 1e-3f, Lg_);float LgAver = exp(cv::sum(Lg_)[0] / num);float lambda = 10;float beta = lambda * LgAver;cv::log(Lg / Hg + beta, Lout);cv::multiply(alpha, Lout, Lout);cv::normalize(Lout, Lout, 0, 255, NORM_MINMAX);}else {cv::normalize(Lg, Lout, 0, 255, NORM_MINMAX);}Mat gain(img.rows, img.cols, CV_32F);for (int i = 0; i < img.rows; i++) {for (int j = 0; j < img.cols; j++) {float x = Lw.at<float>(i, j);float y = Lout.at<float>(i, j);if (0 == x) gain.at<float>(i, j) = y;else gain.at<float>(i, j) = y / x;}}Mat out;Mat bgr[3];cv::split(temp, bgr);if (ContrastCorrect) {bgr[0] = (gain.mul(bgr[0] + Lw) + bgr[0] - Lw) * 0.5f;bgr[1] = (gain.mul(bgr[1] + Lw) + bgr[1] - Lw) * 0.5f;bgr[2] = (gain.mul(bgr[2] + Lw) + bgr[2] - Lw) * 0.5f;}else {cv::multiply(bgr[0], gain, bgr[0]);cv::multiply(bgr[1], gain, bgr[1]);cv::multiply(bgr[2], gain, bgr[2]);}cv::merge(bgr, 3, out);out.convertTo(out, CV_8UC3);return out;
}cv::Mat adaptive_logarithmic_mapping(const cv::Mat& img)
{Mat ldrDrago, result;img.convertTo(ldrDrago, CV_32FC3, 1.0f / 255);cvtColor(ldrDrago, ldrDrago, cv::COLOR_BGR2XYZ);Ptr<TonemapDrago> tonemapDrago = createTonemapDrago(1.f, 1.f, 0.85f);tonemapDrago->process(ldrDrago, result);cvtColor(result, result, cv::COLOR_XYZ2BGR);result.convertTo(result, CV_8UC3, 255);return result;
}int main(int argc, char** argv)
{vector<double> sigemas;vector<double> weights;for (int i = 0; i < 3; i++){weights.push_back(1.f / 3);}sigemas.push_back(30);sigemas.push_back(150);sigemas.push_back(300);Mat rgb = imread("C:\\Users\\tangzy\\Desktop\\2.png");//Mat out3 = multi_scale_retinex(rgb, weights, sigemas,1,1);Mat out2 = adaptive_logarithmic_mapping(rgb);Mat out1 = LICE_retinex(rgb);waitKey(0);return 0;
}

这篇关于Opencv c++对比度增强(1.多尺度Retinex;2.LICE-Retinex;3.自适应对数映射。)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/365282

相关文章

深入理解C++ 空类大小

《深入理解C++空类大小》本文主要介绍了C++空类大小,规定空类大小为1字节,主要是为了保证对象的唯一性和可区分性,满足数组元素地址连续的要求,下面就来了解一下... 目录1. 保证对象的唯一性和可区分性2. 满足数组元素地址连续的要求3. 与C++的对象模型和内存管理机制相适配查看类对象内存在C++中,规

opencv实现像素统计的示例代码

《opencv实现像素统计的示例代码》本文介绍了OpenCV中统计图像像素信息的常用方法和函数,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录1. 统计像素值的基本信息2. 统计像素值的直方图3. 统计像素值的总和4. 统计非零像素的数量

在 VSCode 中配置 C++ 开发环境的详细教程

《在VSCode中配置C++开发环境的详细教程》本文详细介绍了如何在VisualStudioCode(VSCode)中配置C++开发环境,包括安装必要的工具、配置编译器、设置调试环境等步骤,通... 目录如何在 VSCode 中配置 C++ 开发环境:详细教程1. 什么是 VSCode?2. 安装 VSCo

C++11的函数包装器std::function使用示例

《C++11的函数包装器std::function使用示例》C++11引入的std::function是最常用的函数包装器,它可以存储任何可调用对象并提供统一的调用接口,以下是关于函数包装器的详细讲解... 目录一、std::function 的基本用法1. 基本语法二、如何使用 std::function

【C++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<

使用opencv优化图片(画面变清晰)

文章目录 需求影响照片清晰度的因素 实现降噪测试代码 锐化空间锐化Unsharp Masking频率域锐化对比测试 对比度增强常用算法对比测试 需求 对图像进行优化,使其看起来更清晰,同时保持尺寸不变,通常涉及到图像处理技术如锐化、降噪、对比度增强等 影响照片清晰度的因素 影响照片清晰度的因素有很多,主要可以从以下几个方面来分析 1. 拍摄设备 相机传感器:相机传

C++包装器

包装器 在 C++ 中,“包装器”通常指的是一种设计模式或编程技巧,用于封装其他代码或对象,使其更易于使用、管理或扩展。包装器的概念在编程中非常普遍,可以用于函数、类、库等多个方面。下面是几个常见的 “包装器” 类型: 1. 函数包装器 函数包装器用于封装一个或多个函数,使其接口更统一或更便于调用。例如,std::function 是一个通用的函数包装器,它可以存储任意可调用对象(函数、函数

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

06 C++Lambda表达式

lambda表达式的定义 没有显式模版形参的lambda表达式 [捕获] 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 有显式模版形参的lambda表达式 [捕获] <模版形参> 模版约束 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 含义 捕获:包含零个或者多个捕获符的逗号分隔列表 模板形参:用于泛型lambda提供个模板形参的名