opencv-9-图像噪声以及评估指标 PSNR 与SSIM
开始之前
我们在将 opencv 的图像显示在了 qt 的label 上, 我们能够将图显示在label 上, 用于显示我们的算法,
我们在 opencv 上一篇文章中介绍了 opencv 的核操作, 我们这里就要进入一个很重要的章节了,图像滤波操作, 也是图像核操作应用的一个很重要的章节,
那我们就从降噪的角度完整的讲一下, 并通过 opencv 核的方式进行图像算法操作, 【技术综述】一文道尽传统图像降噪方法 这篇文章写的还算比较完整, 也是传统的算法的一个综述过程,
目录
前言
数字成像过程中由于电噪声以及其他因素, 导致我们获取到的图像存在噪声,噪声出现在输入部分, 在后续的每个步骤都会受到影响, 所以在数字图像处理的前面必须要进行的一个步骤就是 图像降噪
每个做信号处理的都会接触到一类问题 , 信号降噪, 让人最头疼的一门课,真是感谢老师给过, 但是后面自己用到的时候反而感觉真的好用, 原来是这样, 然后就慢慢学会了怎么使用吧..(感觉还是弱鸡)
知乎可复现的图像降噪算法总结这篇文章列出了一个能够复现的图像降噪操作算法列表, 近年来实现了的算法可以见reproducible-image-denoising-state-of-the-art, 之后使用相应的文章进行算法实现吧.( 又立了一个 flag )
图像降噪理论基础
图像降噪主要的目的是在进行去除图像噪声的同时保留尽可能多的主要特征, 对于人眼来说, 区分噪声还算比较容易, 但是对于计算机来说,输入的都是数据, 我怎么区分哪个是噪声, 哪个不是噪声呢, 这里就要引入噪声的理论基础了
图像噪声的产生
我们在之前的章节介绍了图像的程序系统, 实际上在成像过程中可能由于点噪声, 量化过程等造成噪声,
实际上的噪声主要分为三种:
- 加性噪声: 与输入无关, \(f(x,y) = g(x,y) +n(x,y)\)
- 乘性噪声: 与输入信号有关, \(f(x,y) = g(x,y) + n(x,y) \cdot g(x,y)\)
- 量化噪声: 与输入无关, 在图像量化过程噪声的量化误差导致的噪声,
实际上后两种很难解决, 目前处理的都是以 加性噪声为主, 属于随机的噪声信号, 根据统计学的观点, 噪声在无限长时间窗内的噪声和为0, 在第一类中的 \(n(x,y)\) 随着时间存在正负信号的不确定变化.
上图所示虚线代表真实信号,红蓝线表示的就是随机噪声信号,所有的随机噪声信号求和后结果为0。
这里关于噪声的说明可以参考图像去噪算法简介
噪声在理论上可以定义为“不可预测,只能用概率统计方法来认识的随机误差”。因此将图像噪声看成是多维随机过程是合适的,因而描述噪声的方法完全可以借用随机过程的描述,即用其概率分布函数和概率密度分布函数。但在很多情况下,这样的描述方法是很复杂的,甚至是不可能的。而实际应用往往也不必要。通常是用其数字特征,即均值方差,相关函数等。因为这些数字特征都可以从某些方面反映出噪声的特征。
我认为图像噪声的成因分类与常见图像去噪算法简介这篇文章关于噪声的分类部分讲的还比较细, 可以参考
图像噪声的模型
由于我们认为噪声在时间尺度的随机性, 但是我们可以使用噪声的概率分布与概率密度函数进行描述, 那么我们就能将噪声根据其分布特点进行分类,
我们稍微介绍一下常见的噪声模型吧
噪声模型主要可以分为:
- 高斯噪声,高斯噪声模型经常被用于实践中。
- 脉冲噪声(椒盐噪声),图像上一个个点,也可称为散粒和尖峰噪声。
- 伽马噪声
- 瑞利噪声
- 指数分布噪声
- 均匀分布噪声
这里能查到的资料很多, 可以看我们的参考部分, 内容都一样, 再写只是浪费时间和精力, 有兴趣的可以自己翻阅
图像降噪操作
其实吧, 我就不应该讲那么多, 直接开始图像处理部分就行了, 为了开始进行图像处理, 我们要先进行一点小工作, 我们要按造以下步骤进行降噪算法的比较,
- 选择标准图像--- lena.png
- 添加噪声
- 量化噪声
- 降噪操作
- 量化结果值
- 比较结果
在我们进行算法比对之前, 我们选择的是 lena 的图像, 加入随机噪声, 然后计算出来 一个噪声的比例, 进行降噪操作, 再次计算以下噪声参数, 看下效果值.
噪声添加
我们认为噪声是随机的, 我们生成随机数加在原始图像上便能够得到噪声图像, opencv 没有提供相应的实现, 但是知道原理了, 写起来都比较简单, 我比较喜欢
图像处理基础(1):噪声的添加和过滤 使用的方法, 他使用的是 梅森旋转算法 来实现的伪随机算法,
其实吧这里我也不懂, 但是随机数能用就行了, 我又不是数学家, 然后看到了 谈谈梅森旋转:算法及其爆破
这里就不重复造轮子了, 直接复制他给出的代码就好,
// 添加椒盐噪声 // 生成 随机 num 个 白点
void addSaltNoise(Mat &m, int num)
{
// 随机数产生器
std::random_device rd; //种子
std::mt19937 gen(rd()); // 随机数引擎
auto cols = m.cols * m.channels();
for (int i = 0; i < num; i++)
{
auto row = static_cast<int>(gen() % m.rows);
auto col = static_cast<int>(gen() % cols);
auto p = m.ptr<uchar>(row);
p[col++] = 255;
p[col++] = 255;
p[col] = 255;
}
}
// 添加Gussia噪声
// 使用指针访问
void addGaussianNoise(Mat &m, int mu, int sigma)
{
// 产生高斯分布随机数发生器
std::random_device rd;
std::mt19937 gen(rd());
std::normal_distribution<> d(mu, sigma);
auto rows = m.rows; // 行数
auto cols = m.cols * m.channels(); // 列数
for (int i = 0; i < rows; i++)
{
auto p = m.ptr<uchar>(i); // 取得行首指针
for (int j = 0; j < cols; j++)
{
auto tmp = p[j] + d(gen);
tmp = tmp > 255 ? 255 : tmp;
tmp = tmp < 0 ? 0 : tmp;
p[j] = tmp;
}
}
}
噪声量化方法
这里其实涉及到图像质量评估的领域,可以参考图像质量评价概述(评估指标、传统检测方法)介绍的方法, 存在太多的计算方式,
我们必须选择一个量化噪声的方式进行图像质量的评估, 一般进行噪声评估手段就是噪声比(Signal to Noise Ratio,SNR),峰值信噪比(Peak Signal to Noise Ratio, PSNR) , 均方差值(Mean Square Error, MSE), 结构相似性(Structural SIMilarity, SSIM),
我们一个一个来看, 均方差值是用于比较两幅图像 K, I 的均方差值
\]
峰值信噪比PSNR衡量图像失真或是噪声水平的客观标准。2个图像之间PSNR值越大,则越相似。普遍基准为30dB,30dB以下的图像劣化较为明显。定义为,
\]
其中\(MAX^2\) 为图片可能的最大像素值。如果每个像素都由 8 位二进制来表示,那么就为 255。
SNR用于描述信号与噪声的比值
\left[
\frac{\sum_{x=0}^{m-1} \sum_{y=0}^{n-1}(f(x, y))^{2}}{\sum_{x=0}^{m-1} \sum_{y=0}^{n-1}(f(x, y)-\hat{f}(x, y))^{2}}\right]
\]
SSIM 描述两个图像的相似性, 通过三个进行比较, 亮度,对比度和结构, 参考图像质量评价指标之 PSNR 和 SSIM
\]
\]
一般取\(c_3 = \frac{c_2}{2}\)。
\(u_x\) 为 \(x\) 的均值
\(u_y\) 为 \(y\) 的均值
\(\sigma_x^2\) 为\(x\) 的方差
\(\sigma_y^2\) 为\(y\) 的方差
\(\sigma_{xy}\) 为\(x\) 和\(y\) 的协方差
\(c_1 = (k_1 L)^2, c_2=(k_2 L)^2\) 为两个常数,避免除零
\(L\) 为像素值的范围,\((0,255)\)
\(k_1 = 0.01, k_2 = 0.03\) 为默认值
默认参数\(\alpha = 1, \beta = 1, \gamma = 1\)
opencv 计算 PSNR 和 SSIM
本来不想写这么多的, 但是 opencv 给出了一个例程Similarity check (PNSR and SSIM) on the GPU, 提供了计算的方法, 自己不用去写了, 岂不是很爽, 所以上面就详细介绍了各个方法的使用.
官方给出了普通版本以及 GPU 加速的版本, 我们暂时只使用基础的版本就好,
PSNR返回一个浮点数,如果两个输入在30到50之间相似(越高越好)。
SSIM返回图像的MSSIM。这也是一个介于零和一之间的浮点数(越高越好),但是每个通道都有一个浮点数。因此,我们返回一个Scalar OpenCV数据结构:
double getPSNR(const Mat& I1, const Mat& I2)
{
Mat s1;
absdiff(I1, I2, s1); // |I1 - I2|
s1.convertTo(s1, CV_32F); // cannot make a square on 8 bits
s1 = s1.mul(s1); // |I1 - I2|^2
Scalar s = sum(s1); // sum elements per channel
double sse = s.val[0] + s.val[1] + s.val[2]; // sum channels
if( sse <= 1e-10) // for small values return zero
return 0;
else
{
double mse =sse /(double)(I1.channels() * I1.total());
double psnr = 10.0*log10((255*255)/mse);
return psnr;
}
}
Scalar getMSSIM( const Mat& i1, const Mat& i2)
{
const double C1 = 6.5025, C2 = 58.5225;
/***************************** INITS **********************************/
int d = CV_32F;
Mat I1, I2;
i1.convertTo(I1, d); // cannot calculate on one byte large values
i2.convertTo(I2, d);
Mat I2_2 = I2.mul(I2); // I2^2
Mat I1_2 = I1.mul(I1); // I1^2
Mat I1_I2 = I1.mul(I2); // I1 * I2
/*************************** END INITS **********************************/
Mat mu1, mu2; // PRELIMINARY COMPUTING
GaussianBlur(I1, mu1, Size(11, 11), 1.5);
GaussianBlur(I2, mu2, Size(11, 11), 1.5);
Mat mu1_2 = mu1.mul(mu1);
Mat mu2_2 = mu2.mul(mu2);
Mat mu1_mu2 = mu1.mul(mu2);
Mat sigma1_2, sigma2_2, sigma12;
GaussianBlur(I1_2, sigma1_2, Size(11, 11), 1.5);
sigma1_2 -= mu1_2;
GaussianBlur(I2_2, sigma2_2, Size(11, 11), 1.5);
sigma2_2 -= mu2_2;
GaussianBlur(I1_I2, sigma12, Size(11, 11), 1.5);
sigma12 -= mu1_mu2;
Mat t1, t2, t3;
t1 = 2 * mu1_mu2 + C1;
t2 = 2 * sigma12 + C2;
t3 = t1.mul(t2); // t3 = ((2*mu1_mu2 + C1).*(2*sigma12 + C2))
t1 = mu1_2 + mu2_2 + C1;
t2 = sigma1_2 + sigma2_2 + C2;
t1 = t1.mul(t2); // t1 =((mu1_2 + mu2_2 + C1).*(sigma1_2 + sigma2_2 + C2))
Mat ssim_map;
divide(t3, t1, ssim_map); // ssim_map = t3./t1;
Scalar mssim = mean( ssim_map ); // mssim = average of ssim map
return mssim;
}
算法噪声数据
我们完成了噪声添加以及噪声的量化, 我们来试一下, 给图像随机添加一定的噪声, 然后看下相应的参数变化情况对比来看就好
椒盐噪声测试
我们先来测试椒盐噪声 分别计算没有噪声的图, 以及添加了 1000个 和10000个噪声的数据结果, 并将后面两个显示出来
void MainWindow::testFunc1(void)
{
// 添加椒盐噪声 并计算 PSNR和 SSIM
cv::Mat salt_img;
double psnr = 0;
cv::Scalar mssim;
QString res_temp = "Salt-%1 : psnr:%2, mssim: B:%3 G:%4 R:%5 ";
QString res_str;
// 计算三组图像的参数 0, 1000, 10000
// 复制原始图像, 添加噪声, 计算 psnr和ssim 显示在 ui上
salt_img = gSrcImg.clone();
addSaltNoise(salt_img,0);
psnr = getPSNR(gSrcImg, salt_img);
mssim = getMSSIM(gSrcImg,salt_img);
res_str = res_temp.arg(0)
.arg(psnr)
.arg(mssim.val[0])
.arg(mssim.val[1])
.arg(mssim.val[2]);
ui->pt_log->appendPlainText(res_str);
salt_img = gSrcImg.clone();
addSaltNoise(salt_img,1000);
psnr = getPSNR(gSrcImg, salt_img);
mssim = getMSSIM(gSrcImg,salt_img);
res_str = res_temp.arg(1000)
.arg(psnr)
.arg(mssim.val[0])
.arg(mssim.val[1])
.arg(mssim.val[2]);
ui->pt_log->appendPlainText(res_str);
// 左侧显示 1000 噪声 右侧显示 10000 噪声
ShowMatOnQtLabel(salt_img,ui->lb_src);
salt_img = gSrcImg.clone();
addSaltNoise(salt_img,10000);
psnr = getPSNR(gSrcImg, salt_img);
mssim = getMSSIM(gSrcImg,salt_img);
res_str = res_temp.arg(10000)
.arg(psnr)
.arg(mssim.val[0])
.arg(mssim.val[1])
.arg(mssim.val[2]);
ui->pt_log->appendPlainText(res_str);
ShowMatOnQtLabel(salt_img,ui->lb_dst);
}
我们可以直接计算得到椒盐噪声 psnr 和 ssim 都是越大越好的, 可以明显的看到图像质量退化
Salt-0 : psnr:0, mssim: B:1 G:1 R:1
Salt-1000 : psnr:27.7528, mssim: B:0.865341 G:0.870555 R:0.914122
Salt-10000 : psnr:17.8062, mssim: B:0.311999 G:0.327485 R:0.493874
高斯噪声测试
高斯噪声我们测试了四组 分别使用参数(0,1) (0,10)(10,1)(10,10) 作为高斯参数, 最终得到后面的图, 然后计算得到的结果, 我们做的结果比较简单, 可以参考数字图像处理——添加高斯噪声&椒盐噪声, 给出了很多的图, 可以参考学
void MainWindow::testFunc2(void)
{
// 添加高斯噪声 并计算 PSNR和 SSIM
cv::Mat guass_img;
double psnr = 0;
cv::Scalar mssim;
QString res_temp = "gauss-%1- %2 : psnr:%3, mssim: B:%4 G:%5 R:%6 ";
QString res_str;
// 计算三组图像的参数 (0,1) (0,10), (10,1), (10,10)
// 复制原始图像, 添加噪声, 计算 psnr和ssim 显示在 ui上
guass_img = gSrcImg.clone();
addGaussianNoise(guass_img,0,1);
psnr = getPSNR(gSrcImg, guass_img);
mssim = getMSSIM(gSrcImg,guass_img);
res_str = res_temp.arg(0)
.arg(1)
.arg(psnr)
.arg(mssim.val[0])
.arg(mssim.val[1])
.arg(mssim.val[2]);
ui->pt_log->appendPlainText(res_str);
guass_img = gSrcImg.clone();
addGaussianNoise(guass_img,0,10);
psnr = getPSNR(gSrcImg, guass_img);
mssim = getMSSIM(gSrcImg,guass_img);
res_str = res_temp.arg(0)
.arg(10)
.arg(psnr)
.arg(mssim.val[0])
.arg(mssim.val[1])
.arg(mssim.val[2]);
ui->pt_log->appendPlainText(res_str);
guass_img = gSrcImg.clone();
addGaussianNoise(guass_img,10,1);
psnr = getPSNR(gSrcImg, guass_img);
mssim = getMSSIM(gSrcImg,guass_img);
res_str = res_temp.arg(10)
.arg(1)
.arg(psnr)
.arg(mssim.val[0])
.arg(mssim.val[1])
.arg(mssim.val[2]);
ui->pt_log->appendPlainText(res_str);
guass_img = gSrcImg.clone();
addGaussianNoise(guass_img,10,10);
psnr = getPSNR(gSrcImg, guass_img);
mssim = getMSSIM(gSrcImg,guass_img);
res_str = res_temp.arg(10)
.arg(10)
.arg(psnr)
.arg(mssim.val[0])
.arg(mssim.val[1])
.arg(mssim.val[2]);
ui->pt_log->appendPlainText(res_str);
}
gauss-0- 1 : psnr:46.8791, mssim: B:0.991811 G:0.991622 R:0.992751
gauss-0- 10 : psnr:28.1229, mssim: B:0.614219 G:0.608773 R:0.648285
gauss-10- 1 : psnr:28.5293, mssim: B:0.978448 G:0.980308 R:0.987926
gauss-10- 10 : psnr:25.3511, mssim: B:0.605665 G:0.600491 R:0.646768
总结
原本想把滤波一起做了的, 但是越写越, 就不做太多的处理了, 我们算是介绍了噪声的来源, 噪声的模型, 以及个噪声的量化方式,
然后介绍了图像添加噪声的方法 我们分别给图像添加椒盐噪声与高斯噪声, 然后分别量化了噪声的结果值, 进行对比展示,
示例的图不是很多, 程序是在代码库里面的, 可以直接去自己实现, 然后进行 进行更多图的展示
参考
- 《高斯噪声_百度百科》. 见于 2020年4月30日. https://baike.baidu.com/item/高斯噪声.
- 知乎专栏. 《【技术综述】一文道尽传统图像降噪方法》. 见于 2020年4月29日. https://zhuanlan.zhihu.com/p/51403693.
- 知乎专栏. 《可复现的图像降噪算法总结》. 见于 2020年4月29日. https://zhuanlan.zhihu.com/p/32502816.
- 《梅森旋转算法》. 收入 维基百科,自由的百科全书, 2019年11月4日. https://zh.wikipedia.org/w/index.php?title=梅森旋转算法&oldid=56745942.
- 《实现灰度图像峰值信噪比计算_人工智能_松子茶的专栏-CSDN博客》. 见于 2020年4月30日. https://blog.csdn.net/songzitea/article/details/17529445.
- 《数字图像处理-噪声 - Mohanson》. 见于 2020年4月30日. http://accu.cc/content/pil/noise/.
- 《图像处理基础(1):噪声的添加和过滤 - Brook_icv - 博客园》. 见于 2020年4月30日. https://www.cnblogs.com/wangguchangqing/p/6372025.html.
- 《图像处理PSNR及其计算(OpenCV和matlab实现)_人工智能_无机器不学习-加大码的分享-CSDN博客》. 见于 2020年4月30日. https://blog.csdn.net/laoxuan2011/article/details/51519062.
- 《图像的 SNR 和 PSNR 的计算 - rldts - 博客园》. 见于 2020年4月30日. https://www.cnblogs.com/qrlozte/p/5340216.html.
- 《图像去噪算法简介 - InfantSorrow - 博客园》. 见于 2020年4月29日. https://www.cnblogs.com/CCBB/archive/2011/01/06/1929033.html.
- 《图像噪声的成因分类与常见图像去噪算法简介_Java_qq_27606639的博客-CSDN博客》. 见于 2020年4月30日. https://blog.csdn.net/qq_27606639/article/details/80912071.
- 《图像质量评估指标 SSIM / PSNR / MSE_人工智能_兔角与禅-CSDN博客》. 见于 2020年4月30日. https://blog.csdn.net/edogawachia/article/details/78756680.
- 《图像质量评价概述(评估指标、传统检测方法)_人工智能_qq_23304241的博客-CSDN博客》. 见于 2020年4月30日. https://blog.csdn.net/qq_23304241/article/details/80953613.
- 《影像降噪》. 收入 维基百科,自由的百科全书, 2018年9月20日. https://zh.wikipedia.org/w/index.php?title=影像降噪&oldid=51354600.
opencv-9-图像噪声以及评估指标 PSNR 与SSIM的更多相关文章
- opencv:图像噪声
常见噪声的类型: 椒盐噪声 高斯噪声 其他噪声...... 手动生成图像噪声: #include <opencv2/opencv.hpp> #include <iostream> ...
- 图像质量评价方法PSNR+SSIM&&评估指标SROCC,PLCC
update:2018-04-07 今天发现ssim的计算里面有高斯模糊,为了快速计算,先对每个小块进行计算,然后计算所有块的平均值.可以参考源代码实现,而且代码实现有近似的在里面!matlab中中图 ...
- OpenCV进行图像相似度对比的几种办法
转载请注明出处:http://blog.csdn.net/wangyaninglm/article/details/43853435, 来自:shiter编写程序的艺术 对计算图像相似度的方法,本文做 ...
- 13、OpenCV实现图像的空间滤波——图像平滑
1.空间滤波基础概念 1.空间滤波基础 空间滤波一词中滤波取自数字信号处理,指接受或拒绝一定的频率成分,但是空间滤波学习内容实际上和通过傅里叶变换实现的频域的滤波是等效的,故而也称为滤波.空间滤波主要 ...
- C#使用OpenCV剪切图像中的圆形和矩形
前言 本文主要介绍如何使用OpenCV剪切图像中的圆形和矩形. 准备工作 首先创建一个Wpf项目--WpfOpenCV,这里版本使用Framework4.7.2. 然后使用Nuget搜索[Emgu.C ...
- 评估指标:准确率(Precision)、召回率(Recall)以及F值(F-Measure)
为了能够更好的评价IR系统的性能,IR有一套完整的评价体系,通过评价体系可以了解不同信息系统的优劣,不同检索模型的特点,不同因素对信息检索的影响,从而对信息检索进一步优化. 由于IR的目标是在较短时间 ...
- 聚类结果的评估指标及其JAVA实现
一. 前言 又GET了一项技能.在做聚类算法的时候,由于要评估所提出的聚类算法的好坏,于是需要与一些已知的算法对比,或者用一些人工标注的标签来比较,于是用到了聚类结果的评估指标.我了解了以下几项. 首 ...
- [DeeplearningAI笔记]ML strategy_1_1正交化/单一数字评估指标
机器学习策略 ML strategy 觉得有用的话,欢迎一起讨论相互学习~Follow Me 1.1 什么是ML策略 机器学习策略简介 情景模拟 假设你正在训练一个分类器,你的系统已经达到了90%准确 ...
- 【机器学习】--模型评估指标之混淆矩阵,ROC曲线和AUC面积
一.前述 怎么样对训练出来的模型进行评估是有一定指标的,本文就相关指标做一个总结. 二.具体 1.混淆矩阵 混淆矩阵如图: 第一个参数true,false是指预测的正确性. 第二个参数true,p ...
随机推荐
- JavaScript简单使用
本文参考廖雪峰老师网站:https://www.liaoxuefeng.com/wiki/1022910821149312 JavaScript是一种运行在浏览器中的解释型的编程语言,在Web世界里, ...
- STM32F103ZET6时钟
1.STM32F103ZET6时钟说明 STM32F103ZET6的时钟树图如下所示: STM32F103ZET6有很多个时钟源,分别有: HSE:高速外部时钟信号. HSI:高速内部部时钟信号. L ...
- eolinker测试增强
地址:https://www.eolinker.com Chrome: https://chrome.google.com/webstore/detail/eolinker/mdbgchaihbacj ...
- 1015 Reversible Primes (20 分)
A reversible prime in any number system is a prime whose "reverse" in that number system i ...
- 34.3 转换流 InputStreamReader OutStreamReader
转换流: 把字节输出流转换成字符输出流 标准输入输出流:传输的对象是字节流 System.in . System.out 标准输入输出流 public static final InputStream ...
- 29.2 Iterator 迭代器ConcurrentModificationException:并发修改异常处理
/** Iterator:迭代器* * 需求:判断集合中是否包含元素java,如果有则添加元素android * Exception in thread "main" java.u ...
- es实现mysql的like查询
es版本6.8 因为阿里云的dts同步最高支持es版本就是6.8 构建索引 PUT /z_test/ { "mappings": { "doc": { &quo ...
- MySQL中的事务和MVCC
本篇博客参考掘金小册--MySQL 是怎样运行的:从根儿上理解 MySQL 以及极客时间--MySQL实战45讲. 虽然我们不是DBA,可能对数据库没那么了解,但是对于数据库中的索引.事务.锁,我们还 ...
- Python爬虫系列(二):requests基础
1.发送请求: import requests # 获取数据#r是一个 response 对象.包含请求返回的内容r = requests.get('https://github.com/timeli ...
- java 中类为啥要序列化
java里为什么要序列化?http://zhidao.baidu.com/link?url=7_wAQ8eAl28vcJPE5OKM5Y0Bo4aINNQokHhRmI9XPszEoTO5QF-gNb ...