图像增强-单尺度Retinex算法
在前面我们讲述了最基本的图像增强方法-直方图均衡化,今天我们来讲解一下新的图像增强的方法,Retinex算法,这一篇为简单的单尺度的Retinex算法,首先Retinex算法认为一副图像是由反射光与照射光强度构成的,其中发射光代表了图像的纹理、材质等主要的,而照射光强度则认为是像素所能到达的动态范围。
一、概述
在前面我们讲述了最基本的图像增强方法-直方图均衡化,今天我们来讲解一下新的图像增强的方法,Retinex算法,这一篇为简单的单尺度的Retinex算法,首先Retinex算法认为一副图像是由反射光与照射光强度构成的,其中发射光代表了图像的纹理、材质等主要的,而照射光强度则认为是像素所能到达的动态范围。下面即为Retinex算法的原理图,其中R(x, y)为反射分量,而S(x, y)为照射分量,我们可以简单的认为一副图像其实本质是由R(x, y)构成的,我们举一个简单的例子,为什么人能同时在不同环境下都能分辨出物体颜色(例如红色的外套),但相机却不行呢?在较暗的环境下,相机拍摄出来的图像颜色会有很大差异,这是因为相机拍摄的图像收到了关照强度的影响,而人的视觉符合颜色恒常性,所以Retinex算法也是利用颜色恒常性而创造出来的,Retinex算法的本质就是尽可能的保持仿射分量的存在,尽可能的减少关照强度对图像的影响。后面我们会具体讲解各个公式的来由。

二、手动高斯核生成
在Retinex算法中,我们要假设出S(x, y)的像素值,所以我们需要通过高斯模糊去估计它,为什么选择高斯模糊呢?首先我们先了解一下高斯函数,首先我们可以知道高斯函数它的特点就是连续,并且无限可导,所以相对来说比较平滑,另外高斯函数是严格单调递减的函数,不会出现断点,即不产生伪影,其次它关于旋转对称,不偏向某方向。最后也是最重要的是关照图是低频信号,因为关照图的相邻像素不会突变,举一个简单的例子,我们周围的关照不会因为太阳或者灯光而图像变亮或者变暗,所以关照图是低频型号,而高斯模糊也是低频信号。所以高斯是相对来说比较优的方法。

手动首先高斯滤波代码如下:
std::vector<std::vector<double>> GerenateGaussianKernel(int kSize, double sigma)
{
std::vector<std::vector<double>>kernel(kSize, std::vector<double>(kSize));
int k = kSize / 2;
double sum = 0.0;
const double PI = 3.14159265358979323846;
for (int i = -k; i <= k; ++i)
{
for (int j = -k; j <= k; ++j)
{
double value = static_cast<double>((1 / (2 * PI * sigma * sigma)) * exp(-(i * i + j * j) / (2 * sigma * sigma)));
kernel[i + k][j + k] = value;
sum += value;
}
}
// 归一化
for (int i = 0; i < kSize; ++i)
{
for (int j = 0; j < kSize; ++j)
{
kernel[i][j] /= sum;
}
}
return kernel;
}
三、手动卷积生成
高斯模糊需要用 高斯函数生成的卷积核 对图像做卷积,也就是利用周围像素按高斯权重加权平均。卷积公式如下,其中I(x, y)为图像像素,K(i, j)对应高斯卷积核的权重。

手动实现卷积代码如下:
cv::Mat ConvolutionFilter2DFunc(const cv::Mat& image, std::vector<std::vector<double>>& kernel)
{
int kSize = kernel.size();
int k = kSize / 2;
cv::Mat dst = cv::Mat::zeros(image.size(), CV_64F);
for (int y = 0; y < image.rows; ++y)
{
for (int x = 0; x < image.cols; ++x)
{
double sum = 0.0; // 每次更新像素时重置sum
for (int i = -k; i <= k; ++i)
{
for (int j = -k; j <= k; ++j)
{
// 选取有效像素进行加权求和
int yy = std::min(std::max((y + i), 0), (image.rows - 1));
int xx = std::min(std::max((x + j), 0), (image.cols - 1));
sum += image.at<uchar>(yy, xx) * kernel[i + k][j + k];
}
}
dst.at<double>(y, x) = sum;
}
}
return dst;
}
四、手动Retinex算法实现
Retinex算法原理认为图像像素由反射分量与照明分量共同组成,即,我们需要求解出R(x, y),这个时候我们将源图像像素与关照图的像素都取对数化,即为
,由对数函数的性质我们可以知道
,通过以上步骤,就可以正确实现单通道的Retinex算法了,代码如下:
// 单尺度Retinex算法
cv::Mat singleScaleRetinex(const cv::Mat& image, double sigma)
{
CV_Assert(image.channels() == 1);
// 求高斯卷积核
std::vector<std::vector<double>> kernel = GerenateGaussianKernel(7, sigma);
// 对关照图做高斯模糊(卷积)
cv::Mat L = ConvolutionFilter2DFunc(image, kernel);
// 定义仿射图
cv::Mat R = cv::Mat::zeros(image.size(), CV_64F);
// 进行Retinex算法求解
for (int i = 0; i < image.rows; ++i)
{
for (int j = 0; j < image.cols; ++j)
{
// 求的光照图的各点像素值
double Il = L.at<double>(i, j) + 1; // 避免出现0像素导致对数求解失败
double I = image.at<uchar>(i, j) + 1;
// 对这两个像素进行对数化相减
double rPixel = static_cast<double>(std::log(I) - std::log(Il));
R.at<double>(i, j) = rPixel;
}
}
// 将仿射图归一化到0-255像素范围内
cv::normalize(R, R, 0, 255, cv::NORM_MINMAX);
// 转换为uchar整形输出图像
R.convertTo(R, CV_8UC1);
return R;
}
五、测试结果图
我分别采用了灰度图进行Retinex算法均衡和转换为Lab图像,对L亮度通道进行Retinex算法均衡,效果如下:

完整代码提供如下:
#include <iostream>
#include <opencv2/opencv.hpp>
#include <vector>
#include <string>
#include <cmath>
// 定义高斯核生成函数
std::vector<std::vector<double>> GerenateGaussianKernel(int kSize, double sigma)
{
std::vector<std::vector<double>>kernel(kSize, std::vector<double>(kSize));
int k = kSize / 2;
double sum = 0.0;
const double PI = 3.14159265358979323846;
for (int i = -k; i <= k; ++i)
{
for (int j = -k; j <= k; ++j)
{
double value = static_cast<double>((1 / (2 * PI * sigma * sigma)) * exp(-(i * i + j * j) / (2 * sigma * sigma)));
kernel[i + k][j + k] = value;
sum += value;
}
}
// 归一化
for (int i = 0; i < kSize; ++i)
{
for (int j = 0; j < kSize; ++j)
{
kernel[i][j] /= sum;
}
}
return kernel;
}
// 卷积操作
cv::Mat ConvolutionFilter2DFunc(const cv::Mat& image, std::vector<std::vector<double>>& kernel)
{
int kSize = kernel.size();
int k = kSize / 2;
cv::Mat dst = cv::Mat::zeros(image.size(), CV_64F);
for (int y = 0; y < image.rows; ++y)
{
for (int x = 0; x < image.cols; ++x)
{
double sum = 0.0; // 每次更新像素时重置sum
for (int i = -k; i <= k; ++i)
{
for (int j = -k; j <= k; ++j)
{
// 选取有效像素进行加权求和
int yy = std::min(std::max((y + i), 0), (image.rows - 1));
int xx = std::min(std::max((x + j), 0), (image.cols - 1));
sum += image.at<uchar>(yy, xx) * kernel[i + k][j + k];
}
}
dst.at<double>(y, x) = sum;
}
}
return dst;
}
// 单尺度Retinex算法
cv::Mat singleScaleRetinex(const cv::Mat& image, double sigma)
{
CV_Assert(image.channels() == 1);
// 求高斯卷积核
std::vector<std::vector<double>> kernel = GerenateGaussianKernel(7, sigma);
// 对关照图做高斯模糊(卷积)
cv::Mat L = ConvolutionFilter2DFunc(image, kernel);
// 定义仿射图
cv::Mat R = cv::Mat::zeros(image.size(), CV_64F);
// 进行Retinex算法求解
for (int i = 0; i < image.rows; ++i)
{
for (int j = 0; j < image.cols; ++j)
{
// 求的光照图的各点像素值
double Il = L.at<double>(i, j) + 1; // 避免出现0像素导致对数求解失败
double I = image.at<uchar>(i, j) + 1;
// 对这两个像素进行对数化相减
double rPixel = static_cast<double>(std::log(I) - std::log(Il));
R.at<double>(i, j) = rPixel;
}
}
// 将仿射图归一化到0-255像素范围内
cv::normalize(R, R, 0, 255, cv::NORM_MINMAX);
// 转换为uchar整形输出图像
R.convertTo(R, CV_8UC1);
return R;
}
int main()
{
cv::Mat srcImage = cv::imread("C:/Users/jiang/Desktop/data/0250.jpg");
cv::Mat grayImage;
cv::cvtColor(srcImage, grayImage, cv::COLOR_BGR2GRAY);
cv::Mat ResultImage = singleScaleRetinex(grayImage, 11);
// 转换为亮度通道图像进行测试
cv::Mat LabImage;
cv::cvtColor(srcImage, LabImage, cv::COLOR_BGR2Lab);
// 分离通道只处理亮度通道
std::vector<cv::Mat> LabImageSplit;
cv::split(LabImage, LabImageSplit);
cv::Mat LabResultImage = singleScaleRetinex(LabImageSplit[0], 11);
// 将处理后的亮度通道进行合并
cv::Mat dstLabRetinexImage;
LabImageSplit[0] = LabResultImage;
cv::merge(LabImageSplit, dstLabRetinexImage);
cv::cvtColor(dstLabRetinexImage, dstLabRetinexImage, cv::COLOR_Lab2BGR);
cv::imshow("GrayImage", grayImage);
cv::imshow("ResultImage", ResultImage);
cv::imshow("LabRetinexImage", dstLabRetinexImage);
cv::waitKey(0);
cv::destroyAllWindows();
return 0;
}
火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐
所有评论(0)