在光线不足的情况下拍出好照片对非摄影师来说似乎很神奇。完成弱光摄影需要技巧、经验和合适的设备的结合。在低光下拍摄的图像缺乏色彩和独特的边缘。它们还存在能见度低和深度未知的问题。这些缺点使此类图像不适合个人使用或图像处理或计算机视觉任务。我们将学习改善夜间图像的照明。
对于没有摄影技能的人,我们可以使用图像处理技术来增强这些图像。Shi等人在他们的论文“使用亮/暗通道先验对单一图像进行夜间低照度图像增强”中提出了一种方法。这篇论文将作为这篇文章的基础。
改 善 夜 间 图 像 中 的 照 明 前 后 改善夜间图像中的照明前后 改善夜间图像中的照明前后
对于外行来说,弱光的解决方案是使用闪光灯,但您一定注意到了,有时闪光灯会导致红眼、眩光等不良效果。作为对我们亲爱的读者的奖励,我们将讨论如何纠正图片照明不便,并尝试解决该技术面临的局限性。
我们将在整个解释中使用下面给出的这张图片。该图像取自上面引用的论文。
我们的目标是利用基于双通道先验的方法对单个图像进行低照度图像增强。
与使用多个图像相比,使用单个图像进行图像增强更简单。单幅图像增强不需要额外的辅助图像,也不需要不同图像之间精确的点对点融合。
这就是基于双通道先验的解决方案的用武之地。简单地说,就是你可以在你的图像处理问题中使用的图像的“先验信息”。您会想知道为什么我们使用双通道而不是仅使用低光图像的明亮通道,因为它会包含最大的遗漏信息。考虑暗通道可以消除某些区域的块效应,并有助于清楚地看到伪像,如下图所示。
双 通 道 先 验 的 必 要 性 双通道先验的必要性 双通道先验的必要性
在我们深入研究增强图像之前,让我们了解所涉及的步骤。下面的流程图列出了我们将遵循的步骤,以获得夜间图像的照明版本。
首先获得明暗通道图像,这些分别是原始图像局部补丁中的最大和最小像素值。接下来,我们计算全局大气光,因为这给了我们关于图像相对较亮部分的大部分信息。
我们使用通道和大气光值来获得各自的透射图,并在特殊情况下考虑黑暗权重。我们将在这里详细讨论。
改 善 夜 间 图 像 亮 度 的 框 架 流 程 图 改善夜间图像亮度的框架流程图 改善夜间图像亮度的框架流程图
从流程图的步骤 5 中,注意可以使用以下公式找到改进的照明图像:
其中 I ( x ) I(x) I(x) 是增强图像, I n i g h t ( x ) I_{night}(x) Inight(x) 是原始低光图像, A A A 是大气光, t ( x ) t(x) t(x) 是校正后的透射图。
第一步是估计明暗通道先验。它们分别表示局部区域中像素的最大和最小强度。这个过程可以想象成一个滑动卷积窗口,帮助我们找到所有通道的最大值或最小值。
先验估计暗通道:
先验估计明亮通道:
其中 I c I^c Ic 是 I I I 的颜色通道, Ω ( x ) Ω(x) Ω(x) 是以 x 为中心的局部区域。 y 是局部区域 Ω(x) 中的一个像素。
Python
import cv2
import numpy as np
def get_illumination_channel(I, w):
M, N, _ = I.shape
# 通道填充
padded = np.pad(I, ((int(w/2), int(w/2)), (int(w/2), int(w/2)), (0, 0)), 'edge')
darkch = np.zeros((M, N))
brightch = np.zeros((M, N))
for i, j in np.ndindex(darkch.shape):
darkch[i, j] = np.min(padded[i:i + w, j:j + w, :]) # dark channel
brightch[i, j] = np.max(padded[i:i + w, j:j + w, :]) # bright channel
return darkch, brightch
我们首先导入 cv2 和 NumPy 并编写函数来获取光照通道。图像尺寸存储在变量 M 和 N 中。对图像应用内核大小一半的填充以确保它们的大小保持不变。使用 np.min 以获得滑动块中的最低像素值最终获得暗通道。类似地,通过使用 np.max 获得该滑动块中的最高像素值最终获得明亮通道。我们将需要暗通道和亮通道的值以进行进一步的步骤。所以我们返回这些值。类似的代码是为 C++ 编写的,如下所示。
C++
std::pair<cv::Mat, cv::Mat> get_illumination_channel(cv::Mat I, float w) {
int N = I.size[0];
int M = I.size[1];
cv::Mat darkch = cv::Mat::zeros(cv::Size(M, N), CV_32FC1);
cv::Mat brightch = cv::Mat::zeros(cv::Size(M, N), CV_32FC1);
int padding = int(w/2);
// padding for channels
cv::Mat padded = cv::Mat::zeros(cv::Size(M + 2*padding, N + 2*padding), CV_32FC3);
for (int i=padding; i < padding + M; i++) {
for (int j=padding; j < padding + N; j++) {
padded.at<cv::Vec3f>(j, i).val[0] = (float)I.at<cv::Vec3b>(j-padding, i-padding).val[0]/255;
padded.at<cv::Vec3f>(j, i).val[1] = (float)I.at<cv::Vec3b>(j-padding, i-padding).val[1]/255;
padded.at<cv::Vec3f>(j, i).val[2] = (float)I.at<cv::Vec3b>(j-padding, i-padding).val[2]/255;
}
}
for (int i=0; i < darkch.size[1]; i++) {
int col_up, row_up;
col_up = int(i+w);
for (int j=0; j < darkch.size[0]; j++) {
double minVal, maxVal;
row_up = int(j+w);
cv::minMaxLoc(padded.colRange(i, col_up).rowRange(j, row_up), &minVal, &maxVal);
darkch.at<float>(j,i) = minVal; //dark channel
brightch.at<float>(j,i) = maxVal; //bright channel
}
}
return std::make_pair(darkch, brightch);
}
暗通道和亮通道是通过用零初始化矩阵并用图像数组中的值填充它们来获得的,其中 CV_32FC1 定义了每个元素的深度和通道数。
填充以内核大小的一半应用于图像,以确保它们的大小保持不变。我们迭代矩阵以获得该块中的最低像素值,用于设置暗通道像素值。获得该块中的最高像素值为我们提供了亮通道像素值。 cv::minMaxLoc 用于查找数组中的全局最小值和最大值。
左 − 暗 通 道 先 验 , 右 − 亮 通 道 先 验 左 - 暗通道先验,右 - 亮通道先验 左−暗通道先验,右−亮通道先验
下一步是计算全局大气光照。它是使用上面获得的明亮通道通过取前 10% 强度的平均值来计算的。取 10% 的值是为了确保一个小的异常不会对其产生很大的影响。
全 局 大 气 光 的 计 算 及 其 对 改 善 夜 间 图 像 照 明 的 贡 献 全局大气光的计算及其对改善夜间图像照明的贡献 全局大气光的计算及其对改善夜间图像照明的贡献
Python
def get_atmosphere(I, brightch, p=0.1):
M, N = brightch.shape
flatI = I.reshape(M*N, 3) # reshaping image array
flatbright = brightch.ravel() #flattening image array
searchidx = (-flatbright).argsort()[:int(M*N*p)] # sorting and slicing
A = np.mean(flatI.take(searchidx, axis=0), dtype=np.float64, axis=0)
return A
为了通过代码实现这一点,根据最大强度对图像进行变形、展平和排序。图像矩阵被切片以仅包含前百分之十的像素,然后取这些的平均值。
C++
cv::Mat get_atmosphere(cv::Mat I, cv::Mat brightch, float p=0.1) {
int N = brightch.size[0];
int M = brightch.size[1];
// flattening and reshaping image array
cv::Mat flatI(cv::Size(1, N*M), CV_8UC3);
std::vector<std::pair<float, int>> flatBright;
for (int i=0; i < M; i++) {
for (int j=0; j < N; j++) {
int index = i*N + j;
flatI.at<cv::Vec3b>(index, 0).val[0] = I.at<cv::Vec3b>(j, i).val[0];
flatI.at<cv::Vec3b>(index, 0).val[1] = I.at<cv::Vec3b>(j, i).val[1];
flatI.at<cv::Vec3b>(index, 0).val[2] = I.at<cv::Vec3b>(j, i).val[2];
flatBright.push_back(std::make_pair(-brightch.at<float>(j, i), index));
}
}
// sorting and slicing the array
sort(flatBright.begin(), flatBright.end());
cv::Mat A = cv::Mat::zeros(cv::Size(1, 3), CV_32FC1);
for (int k=0; k < int(M*N*p); k++) {
int sindex = flatBright[k].second;
A.at<float>(0, 0) = A.at<float>(0, 0) + (float)flatI.at<cv::Vec3b>(sindex, 0).val[0];
A.at<float>(1, 0) = A.at<float>(1, 0) + (float)flatI.at<cv::Vec3b>(sindex, 0).val[1];
A.at<float>(2, 0) = A.at<float>(2, 0) + (float)flatI.at<cv::Vec3b>(sindex, 0).val[2];
}
A = A/int(M*N*p);
return A/255;
}
透射图描述了未被散射并到达相机的光部分。在该算法中,将使用以下等式从明亮通道先验估计:
A c A^c Ac 是大气光局部区域的最大值。
Python
def get_initial_transmission(A, brightch):
A_c = np.max(A)
init_t = (brightch-A_c)/(1.-A_c) # 初始透射图
return (init_t - np.min(init_t))/(np.max(init_t) - np.min(init_t)) # 归一化初始透射图
在代码中,使用公式计算初始透射图,然后用于计算归一化初始透射图。
C++
cv::Mat get_initial_transmission(cv::Mat A, cv::Mat brightch) {
double A_n, A_x, minVal, maxVal;
cv::minMaxLoc(A, &A_n, &A_x);
cv::Mat init_t(brightch.size(), CV_32FC1);
init_t = brightch.clone();
// 初始透射图
init_t = (init_t - A_x)/(1.0 - A_x);
cv::minMaxLoc(init_t, &minVal, &maxVal);
// 归一化初始透射图
init_t = (init_t - minVal)/(maxVal - minVal);
return init_t;
}
还根据暗通道先验计算透射图,并计算先验之间的差异。进行此计算是为了纠正从明亮通道先验获得的潜在错误透射估计。
任何具有小于 alpha 设置值(由经验实验确定为 0.4)的 I d i f f e r e n c e I^{difference} Idifference 通道的像素 x 都位于黑暗对象中,这使得其深度不可靠。这也使得像素 x 的透射不可靠。因此,不可靠的透射可以通过获取传输图的乘积来纠正。
Python
def get_corrected_transmission(I, A, darkch, brightch, init_t, alpha, omega, w):
im = np.empty(I.shape, I.dtype);
for ind in range(0, 3):
im[:, :, ind] = I[:, :, ind] / A[ind] #将像素值除以大气光
dark_c, _ = get_illumination_channel(im, w) # 暗通道透射图
dark_t = 1 - omega*dark_c # 修正暗通道透射图
corrected_t = init_t # 用初始透射图初始化校正透射图
diffch = brightch - darkch # 投射图之间的差
for i in range(diffch.shape[0]):
for j in range(diffch.shape[1]):
if(diffch[i, j] < alpha):
corrected_t[i, j] = dark_t[i, j] * init_t[i, j]
return np.abs(corrected_t)
我们使用在第一个代码片段中创建的 get_illumination_channel 函数来获取暗通道透射图。参数 omega 通常设置为 0.75,用于校正初始透射图。校正后的透射图被初始化为初始透射图。如果暗通道和亮通道之间的差异大于 alpha,即 0.4,它的值将保持与初始透射图相同。如果任何地方的差异都小于 alpha,我们就取上面提到的透射图的乘积。
C++
cv::Mat get_corrected_transmission(cv::Mat I, cv::Mat A, cv::Mat darkch, cv::Mat brightch, cv::Mat init_t, float alpha, float omega, int w) {
cv::Mat im3(I.size(), CV_32FC3);
//将像素值除以大气光
for (int i=0; i < I.size[1]; i++) {
for (int j=0; j < I.size[0]; j++) {
im3.at<cv::Vec3f>(j, i).val[0] = (float)I.at<cv::Vec3b>(j, i).val[0]/A.at<float>(0, 0);
im3.at<cv::Vec3f>(j, i).val[1] = (float)I.at<cv::Vec3b>(j, i).val[1]/A.at<float>(1, 0);
im3.at<cv::Vec3f>(j, i).val[2] = (float)I.at<cv::Vec3b>(j, i).val[2]/A.at<float>(2, 0);
}
}
cv::Mat dark_c, dark_t, diffch;
std::pair<cv::Mat, cv::Mat> illuminate_channels = get_illumination_channel(im3, w);
// 暗通道投射图
dark_c = illuminate_channels.first;
// 修正暗通道透射图
dark_t = 1 - omega*dark_c;
cv::Mat corrected_t = init_t;
diffch = brightch - darkch; //投射图之间的差
for (int i=0; i < diffch.size[1]; i++) {
for (int j=0; j < diffch.size[0]; j++) {
if (diffch.at<float>(j, i) < alpha) {
// 用初始透射图初始化校正透射图
corrected_t.at<float>(j, i) = abs(dark_t.at<float>(j, i)*init_t.at<float>(j, i));
}
}
}
return corrected_t;
}
让我们来看看Guided Filter
的定义:Guided Filter与其他滤波操作一样,是一种邻域操作,但在计算输出像素值时,会考虑Guided图像中相应空间邻域中某个区域的统计信息。
质上,它是一个边缘保留平滑滤波器。我已经使用了这个 GitHub 存储库的实现。将此滤波器应用于上面获得的校正透射图以获得更精细的图像。
应 用 G u i d e d F i l t e r 后 获 得 的 透 射 图 应用Guided Filter后获得的透射图 应用GuidedFilter后获得的透射图
各 种 透 射 图 之 间 的 比 较 各种透射图之间的比较 各种透射图之间的比较
需要透射图和大气光值来获得增强图像。现在我们有了所需的值,可以应用第一个方程来获得结果。
Python
def get_final_image(I, A, refined_t, tmin):
refined_t_broadcasted = np.broadcast_to(refined_t[:, :, None], (refined_t.shape[0], refined_t.shape[1], 3)) # 将2D精制图的通道复制到3个通道
J = (I-A) / (np.where(refined_t_broadcasted < tmin, tmin, refined_t_broadcasted)) + A # 得到最终结果
return (J - np.min(J))/(np.max(J) - np.min(J)) # 归一化图像
首先将灰度精制变换图转换为灰度图,保证原图和变换图的通道数相同。接下来,使用等式计算输出图像。然后将此图像进行最大最小归一化并从函数返回。
C++
cv::Mat get_final_image(cv::Mat I, cv::Mat A, cv::Mat refined_t, float tmin) {
cv::Mat J(I.size(), CV_32FC3);
for (int i=0; i < refined_t.size[1]; i++) {
for (int j=0; j < refined_t.size[0]; j++) {
float temp = refined_t.at<float>(j, i);
if (temp < tmin) {
temp = tmin;
}
// finding result
J.at<cv::Vec3f>(j, i).val[0] = (I.at<cv::Vec3f>(j, i).val[0] - A.at<float>(0,0))/temp + A.at<float>(0,0);
J.at<cv::Vec3f>(j, i).val[1] = (I.at<cv::Vec3f>(j, i).val[1] - A.at<float>(1,0))/temp + A.at<float>(1,0);
J.at<cv::Vec3f>(j, i).val[2] = (I.at<cv::Vec3f>(j, i).val[2] - A.at<float>(2,0))/temp + A.at<float>(2,0);
}
}
double minVal, maxVal;
cv::minMaxLoc(J, &minVal, &maxVal);
// normalized image
for (int i=0; i < J.size[1]; i++) {
for (int j=0; j < J.size[0]; j++) {
J.at<cv::Vec3f>(j, i).val[0] = (J.at<cv::Vec3f>(j, i).val[0] - minVal)/(maxVal - minVal);
J.at<cv::Vec3f>(j, i).val[1] = (J.at<cv::Vec3f>(j, i).val[1] - minVal)/(maxVal - minVal);
J.at<cv::Vec3f>(j, i).val[2] = (J.at<cv::Vec3f>(j, i).val[2] - minVal)/(maxVal - minVal);
}
}
return J;
}
虽然图像充满色彩,但看起来很模糊,锐化会改善画面。我们可以将 cv2.detailEnhance()
用于此任务,但这会增加噪音。所以我们可以使用 cv2.edgePreservingFilter()
来限制它。但是,此功能仍会引起一些噪音。因此,如果图像从一开始就很嘈杂,那么这样做并不理想。
要更深入地了解这些技术,请参阅本文。
进 一 步 增 强 的 图 像 进一步增强的图像 进一步增强的图像
原 始 图 像 与 结 果 的 比 较 原始图像与结果的比较 原始图像与结果的比较
如果图像中有任何明确的光源(如灯)或自然光源(如月亮)覆盖了图像的很大一部分,则该方法的效果不佳。为什么这是一个问题?因为这样的光源会提高大气强度。当我们在寻找最亮的10%的像素时,这将导致这些区域过度曝光。
这种因果关系在下面的图像中比较集中显示出来。
为了克服这个问题,让我们分析一下由明亮通道制作的初始透射图。
初 始 透 射 图 初始透射图 初始透射图
该任务似乎是减少这些导致这些区域过度曝光的强烈白色斑点。这可以通过将值从 255 限制为某个最小值来完成。
Python
def reduce_init_t(init_t):
init_t = (init_t*255).astype(np.uint8)
xp = [0, 32, 255]
fp = [0, 32, 48]
x = np.arange(256) # 创建数组[0,...,255]
table = np.interp(x, xp, fp).astype('uint8') # 根据 xp 在 x 范围内插值 fp
init_t = cv2.LUT(init_t, table) # 查找表
init_t = init_t.astype(np.float64)/255 # 标准化透射图
return init_t
为了用代码实现这一点,透射图被转换为 0-255 的范围。然后使用查找表将点从原始值内插到新范围,从而减少高曝光的影响。
C++
cv::Mat reduce_init_t(cv::Mat init_t) {
cv::Mat mod_init_t(init_t.size(), CV_8UC1);
for (int i=0; i < init_t.size[1]; i++) {
for (int j=0; j < init_t.size[0]; j++) {
mod_init_t.at<uchar>(j, i) = std::min((int)(init_t.at<float>(j, i)*255), 255);
}
}
int x[3] = {0, 32, 255};
int f[3] = {0, 32, 48};
// creating array [0,...,255]
cv::Mat table(cv::Size(1, 256), CV_8UC1);
//Linear Interpolation
int l = 0;
for (int k = 0; k < 256; k++) {
if (k > x[l+1]) {
l = l + 1;
}
float m = (float)(f[l+1] - f[l])/(x[l+1] - x[l]);
table.at<int>(k, 0) = (int)(f[l] + m*(k - x[l]));
}
//Lookup table
cv::LUT(mod_init_t, table, mod_init_t);
for (int i=0; i < init_t.size[1]; i++) {
for (int j=0; j < init_t.size[0]; j++) {
// normalizing the transmission map
init_t.at<float>(j, i) = (float)mod_init_t.at<uchar>(j, i)/255;
}
}
return init_t;
}
下图是代码中的这种调整将如何影响像素的直观表示。
减 少 过 度 曝 光 的 图 形 表 示 减少过度曝光的图形表示 减少过度曝光的图形表示
更 改 后 的 透 射 图 更改后的透射图 更改后的透射图
透 射 图 对 比 透射图对比 透射图对比
我们可以看到使用论文中的方法增强获得的图像与我们刚刚讨论的解决方法获得的结果之间的差异。
左 − 原 始 图 像 , 中 心 − 较 早 生 成 的 增 强 图 像 , 右 − 进 一 步 增 强 的 图 像 。 左 - 原始图像,中心 - 较早生成的增强图像,右 - 进一步增强的图像。 左−原始图像,中心−较早生成的增强图像,右−进一步增强的图像。
最后一步是创建一个结合所有技术并将其作为图像传递的函数。
Python
def dehaze(I, tmin=0.1, w=15, alpha=0.4, omega=0.75, p=0.1, eps=1e-3, reduce=False):
I = np.asarray(im, dtype=np.float64) # Convert the input to a float array.
I = I[:, :, :3] / 255
m, n, _ = I.shape
Idark, Ibright = get_illumination_channel(I, w)
A = get_atmosphere(I, Ibright, p)
init_t = get_initial_transmission(A, Ibright)
if reduce:
init_t = reduce_init_t(init_t)
corrected_t = get_corrected_transmission(I, A, Idark, Ibright, init_t, alpha, omega, w)
normI = (I - I.min()) / (I.max() - I.min())
refined_t = guided_filter(normI, corrected_t, w, eps) # applying guided filter
J_refined = get_final_image(I, A, refined_t, tmin)
enhanced = (J_refined*255).astype(np.uint8)
f_enhanced = cv2.detailEnhance(enhanced, sigma_s=10, sigma_r=0.15)
f_enhanced = cv2.edgePreservingFilter(f_enhanced, flags=1, sigma_s=64, sigma_r=0.2)
return f_enhanced
C++
int main() {
cv::Mat img = cv::imread("dark.png");
float tmin = 0.1;
int w = 15;
float alpha = 0.4;
float omega = 0.75;
float p = 0.1;
double eps = 1e-3;
bool reduce = false;
std::pair<cv::Mat, cv::Mat> illuminate_channels = get_illumination_channel(img, w);
cv::Mat Idark = illuminate_channels.first;
cv::Mat Ibright = illuminate_channels.second;
cv::Mat A = get_atmosphere(img, Ibright);
cv::Mat init_t = get_initial_transmission(A, Ibright);
if (reduce) {
init_t = reduce_init_t(init_t);
}
double minVal, maxVal;
// Convert the input to a float array
cv::Mat I(img.size(), CV_32FC3), normI;
for (int i=0; i < img.size[1]; i++) {
for (int j=0; j < img.size[0]; j++) {
I.at<cv::Vec3f>(j, i).val[0] = (float)img.at<cv::Vec3b>(j, i).val[0]/255;
I.at<cv::Vec3f>(j, i).val[1] = (float)img.at<cv::Vec3b>(j, i).val[1]/255;
I.at<cv::Vec3f>(j, i).val[2] = (float)img.at<cv::Vec3b>(j, i).val[2]/255;
}
}
cv::minMaxLoc(I, &minVal, &maxVal);
normI = (I - minVal)/(maxVal - minVal);
cv::Mat corrected_t = get_corrected_transmission(img, A, Idark, Ibright, init_t, alpha, omega, w);
cv::Mat refined_t(normI.size(), CV_32FC1);
// applying guided filter
refined_t = guidedFilter(normI, corrected_t, w, eps);
cv::Mat J_refined = get_final_image(I, A, refined_t, tmin);
cv::Mat enhanced(img.size(), CV_8UC3);
for (int i=0; i < img.size[1]; i++) {
for (int j=0; j < img.size[0]; j++) {
enhanced.at<cv::Vec3b>(j, i).val[0] = std::min((int)(J_refined.at<cv::Vec3f>(j, i).val[0]*255), 255);
enhanced.at<cv::Vec3b>(j, i).val[1] = std::min((int)(J_refined.at<cv::Vec3f>(j, i).val[1]*255), 255);
enhanced.at<cv::Vec3b>(j, i).val[2] = std::min((int)(J_refined.at<cv::Vec3f>(j, i).val[2]*255), 255);
}
}
cv::Mat f_enhanced;
cv::detailEnhance(enhanced, f_enhanced, 10, 0.15);
cv::edgePreservingFilter(f_enhanced, f_enhanced, 1, 64, 0.2);
cv::imshow("im", f_enhanced);
cv::waitKey(0);
return 0;
}
看看下面的 gif 显示了使用此算法增强的其他一些图像。
左 : 原 始 图 像 , 右 : 增 强 图 像 左:原始图像,右:增强图像 左:原始图像,右:增强图像
左 − 原 始 图 像 , 中 心 − 较 早 生 成 的 增 强 图 像 , 右 − 进 一 步 增 强 的 图 像 。 左 - 原始图像,中心 - 较早生成的增强图像,右 - 进一步增强的图像。 左−原始图像,中心−较早生成的增强图像,右−进一步增强的图像。
总而言之,我们首先了解与在光线不足或低光照条件下拍摄的图像相关的问题。我们逐步讨论了 Shi 等人提出的方法。来增强这样的图像。我们还讨论了论文中介绍的技术的进一步改进和局限性。
该论文提出了一种提高低光图像照明的出色技术。但是,它仅适用于始终保持恒定照明的图像。正如所承诺的,我们还解释了一种解决方法,以克服具有亮点的图像的局限性,例如图像中的满月或灯。
对于这种方法的未来发展,我们可以尝试通过轨迹栏来控制这种减少。轨迹栏将帮助用户更好地理解增强的适当值并设置单个图像所需的最佳值。
链接:https://pan.baidu.com/s/1cMDB-fDu9CXc0zJ97RT8eA
提取码:123a
https://learnopencv.com/improving-illumination-in-night-time-images/