我们在将 opencv 的图像显示在了 qt 的label 上, 我们能够将图显示在label 上, 用于显示我们的算法,
我们在 opencv 上一篇文章中介绍了 opencv 的核操作, 我们这里就要进入一个很重要的章节了,图像滤波操作, 也是图像核操作应用的一个很重要的章节,
那我们就从降噪的角度完整的讲一下, 并通过 opencv 核的方式进行图像算法操作, 【技术综述】一文道尽传统图像降噪方法 这篇文章写的还算比较完整, 也是传统的算法的一个综述过程,
数字成像过程中由于电噪声以及其他因素, 导致我们获取到的图像存在噪声,噪声出现在输入部分, 在后续的每个步骤都会受到影响, 所以在数字图像处理的前面必须要进行的一个步骤就是 图像降噪
每个做信号处理的都会接触到一类问题 , 信号降噪, 让人最头疼的一门课,真是感谢老师给过, 但是后面自己用到的时候反而感觉真的好用, 原来是这样, 然后就慢慢学会了怎么使用吧…(感觉还是弱鸡)
知乎可复现的图像降噪算法总结这篇文章列出了一个能够复现的图像降噪操作算法列表, 近年来实现了的算法可以见reproducible-image-denoising-state-of-the-art, 之后使用相应的文章进行算法实现吧.( 又立了一个 flag )
图像降噪主要的目的是在进行去除图像噪声的同时保留尽可能多的主要特征, 对于人眼来说, 区分噪声还算比较容易, 但是对于计算机来说,输入的都是数据, 我怎么区分哪个是噪声, 哪个不是噪声呢, 这里就要引入噪声的理论基础了
我们在之前的章节介绍了图像的程序系统, 实际上在成像过程中可能由于点噪声, 量化过程等造成噪声,
实际上的噪声主要分为三种:
实际上后两种很难解决, 目前处理的都是以 加性噪声为主, 属于随机的噪声信号, 根据统计学的观点, 噪声在无限长时间窗内的噪声和为0, 在第一类中的 n ( x , y ) n(x,y) n(x,y) 随着时间存在正负信号的不确定变化.
上图所示虚线代表真实信号,红蓝线表示的就是随机噪声信号,所有的随机噪声信号求和后结果为0。
这里关于噪声的说明可以参考图像去噪算法简介
噪声在理论上可以定义为“不可预测,只能用概率统计方法来认识的随机误差”。因此将图像噪声看成是多维随机过程是合适的,因而描述噪声的方法完全可以借用随机过程的描述,即用其概率分布函数和概率密度分布函数。但在很多情况下,这样的描述方法是很复杂的,甚至是不可能的。而实际应用往往也不必要。通常是用其数字特征,即均值方差,相关函数等。因为这些数字特征都可以从某些方面反映出噪声的特征。
我认为图像噪声的成因分类与常见图像去噪算法简介这篇文章关于噪声的分类部分讲的还比较细, 可以参考
由于我们认为噪声在时间尺度的随机性, 但是我们可以使用噪声的概率分布与概率密度函数进行描述, 那么我们就能将噪声根据其分布特点进行分类,
我们稍微介绍一下常见的噪声模型吧
噪声模型主要可以分为:
这里能查到的资料很多, 可以看我们的参考部分, 内容都一样, 再写只是浪费时间和精力, 有兴趣的可以自己翻阅
其实吧, 我就不应该讲那么多, 直接开始图像处理部分就行了, 为了开始进行图像处理, 我们要先进行一点小工作, 我们要按造以下步骤进行降噪算法的比较,
在我们进行算法比对之前, 我们选择的是 lena 的图像, 加入随机噪声, 然后计算出来 一个噪声的比例, 进行降噪操作, 再次计算以下噪声参数, 看下效果值.
如果是进行算法比较的时候, 最好选择现有的降噪的数据集进行比较, 比如, Kodak , BSD
我们认为噪声是随机的, 我们生成随机数加在原始图像上便能够得到噪声图像, 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 的均方差值
M S E = 1 m n ∑ i = 0 n − 1 ∑ j = 0 m − 1 ∥ K ( i , j ) − I ( i , j ) ∥ 2 MSE=\frac{1}{mn}\sum_{i=0}^{n-1}\sum_{j=0}^{m-1}\parallel K(i,j)-I(i,j)\parallel^{2} MSE=mn1i=0∑n−1j=0∑m−1∥K(i,j)−I(i,j)∥2
峰值信噪比PSNR衡量图像失真或是噪声水平的客观标准。2个图像之间PSNR值越大,则越相似。普遍基准为30dB,30dB以下的图像劣化较为明显。定义为,
P S N R = 10 l o g 10 ( M A X 2 M S E ) PSNR=10log_{10}(\frac{MAX^{2}}{MSE}) PSNR=10log10(MSEMAX2)
其中 M A X 2 MAX^2 MAX2 为图片可能的最大像素值。如果每个像素都由 8 位二进制来表示,那么就为 255。
SNR用于描述信号与噪声的比值
S N R ( d B ) = 10 l o g 10 [ ∑ x = 0 m − 1 ∑ y = 0 n − 1 ( f ( x , y ) ) 2 ∑ x = 0 m − 1 ∑ y = 0 n − 1 ( f ( x , y ) − f ^ ( x , y ) ) 2 ] SNR (dB)=10 log_{10} \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] SNR(dB)=10log10[∑x=0m−1∑y=0n−1(f(x,y)−f^(x,y))2∑x=0m−1∑y=0n−1(f(x,y))2]
SSIM 描述两个图像的相似性, 通过三个进行比较, 亮度,对比度和结构, 参考图像质量评价指标之 PSNR 和 SSIM
l ( x , y ) = 2 μ x μ y + c 1 μ x 2 + μ y 2 + c 1 c ( x , y ) = 2 σ x σ y + c 2 σ x 2 + σ y 2 + c 2 s ( x , y ) = σ x y + c 3 σ x σ y + c 3 l(x, y)=\frac{2 \mu_{x} \mu_{y}+c_{1}}{\mu_{x}^{2}+\mu_{y}^{2}+c_{1}} c(x, y)=\frac{2 \sigma_{x} \sigma_{y}+c_{2}}{\sigma_{x}^{2}+\sigma_{y}^{2}+c_{2}} s(x, y)=\frac{\sigma_{x y}+c_{3}}{\sigma_{x} \sigma_{y}+c_{3}} l(x,y)=μx2+μy2+c12μxμy+c1c(x,y)=σx2+σy2+c22σxσy+c2s(x,y)=σxσy+c3σxy+c3
SSIM ( x , y ) = ( 2 μ x μ y + c 1 ) ( 2 σ x y + c 2 ) ( μ x 2 + μ y 2 + c 1 ) ( σ x 2 + σ y 2 + c 2 ) \operatorname{SSIM}(x, y)=\frac{\left(2 \mu_{x} \mu_{y}+c_{1}\right)\left(2 \sigma_{x y}+c_{2}\right)}{\left(\mu_{x}^{2}+\mu_{y}^{2}+c_{1}\right)\left(\sigma_{x}^{2}+\sigma_{y}^{2}+c_{2}\right)} SSIM(x,y)=(μx2+μy2+c1)(σx2+σy2+c2)(2μxμy+c1)(2σxy+c2)
一般取 c 3 = c 2 2 c_3 = \frac{c_2}{2} c3=2c2。
u x u_x ux 为 x x x 的均值
u y u_y uy 为 y y y 的均值
σ x 2 \sigma_x^2 σx2 为 x x x 的方差
σ y 2 \sigma_y^2 σy2 为 y y y 的方差
σ x y \sigma_{xy} σxy 为 x x x 和 y y y 的协方差
c 1 = ( k 1 L ) 2 , c 2 = ( k 2 L ) 2 c_1 = (k_1 L)^2, c_2=(k_2 L)^2 c1=(k1L)2,c2=(k2L)2 为两个常数,避免除零
L L L 为像素值的范围, ( 0 , 255 ) (0,255) (0,255)
k 1 = 0.01 , k 2 = 0.03 k_1 = 0.01, k_2 = 0.03 k1=0.01,k2=0.03 为默认值
默认参数 α = 1 , β = 1 , γ = 1 \alpha = 1, \beta = 1, \gamma = 1 α=1,β=1,γ=1
本来不想写这么多的, 但是 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
原本想把滤波一起做了的, 但是越写越, 就不做太多的处理了, 我们算是介绍了噪声的来源, 噪声的模型, 以及个噪声的量化方式,
然后介绍了图像添加噪声的方法 我们分别给图像添加椒盐噪声与高斯噪声, 然后分别量化了噪声的结果值, 进行对比展示,
示例的图不是很多, 程序是在代码库里面的, 可以直接去自己实现, 然后进行 进行更多图的展示