二维高斯模糊权重计算公式:
二维高斯函数G(x,y)就是目标像素点周围某个像素点的权重值,其中x 是 这个点到目标像素点的 横向距离,y是纵向距离。sigma( σ)是高斯分布的标准差,一般取卷积矩阵半径r的 1/3较为合适。
这个公式在C++中可以写为:
float GetWeight(int x,int y,float sigma){
float weight = 1/2*3.1415926*sigma*sigma*std::pow(std::exp(1),-(x*x+y*y)/(2*sigma*sigma));
return weight;
}
高斯分布图
从上图可以看出来,高斯分布的特点是中心点越远权重越小。
知道了如何求一个像素点周围的另一个像素点的权重,那就可以计算出一个卷积核(高斯核),这个卷积核一般就是一个像素点及其周围一定距离内的方形像素点组成的矩阵,如下图就是一个半径为1的卷积核:
高斯核算出卷积核之后,还需要对它做归一化处理(让它们的总和为1),这样就得到了一个总和为100%的权重矩阵,也叫高斯核。
有了高斯核之后,接下来只需要对图像每一个像素点进行遍历,然后再遍历它r半径内的像素点,把周围每个位置的像素点指定通道的色值和高斯核对应位置的权重值相乘,再把它们的值加起来就是最终模糊后的色值了。这个过程叫卷积运算。
需要注意的是对于边界像素点的处理,比如图片最左边的像素点没有更加靠左的像素点去做卷积运算,如果不做特殊处理,得到的图片周围都是偏黑色的。比如可以把图片最右边的像素点当成是图片最左边像素点的再左边,反之亦然。
对图片的每一个像素点的每一个通道色值进行卷积运算,完成之后,得到的图像就是高斯模糊之后的了。
QT代码:
void getWeights(double* weights,int r){
int diameter = r*2+1;
double sigma = diameter / 2.0;
double sigma2 = sigma*sigma;
double sum = 0;
double x = 1/sqrt((2*3.1415926*sigma2));
double exp = std::exp(1);
double dSigma2 = 2*sigma2;
for(int i = -r;i<=r;++i){
for(int j = r;j>= -r;--j){
double weight = x*std::pow(exp,-(i*i+j*j)/dSigma2);
weights[(i+r)*diameter+(r-j)] = weight;
sum+=weight;
}
}
//归一化
for(int i = -r;i<=r;++i){
for(int j = r;j>= -r;--j){
int x = (i+r);
int y = (r-j);
weights[x*diameter+y] = weights[x*diameter+y] / sum;
//qDebug()<< weights[x*diameter+y];
}
}
}
void MainWindow::GuassianBlur2D(){
//高斯模糊
int r = 10;//半径10
int diameter = r*2+1;//直径=半径*2+1
double sigma = diameter / 2.0;
double* weights = new (std::nothrow) double[diameter*diameter]();
if(weights == nullptr){
return;
}
// clock_t startTime = clock();
//算出高斯核
getWeights(weights,r);
// clock_t endTime = clock();
//qDebug()<<"获得权重耗时"<<endTime - startTime;
QImage tagImg(m_srcImg);
int width = m_srcImg.width();
int height = m_srcImg.height();
// startTime = clock();
//----------------------------------------------------遍历每一个像素点,并做卷积运算
for(int i = 0;i< width;++i){
for(int j = 0;j<height;++j){
double sumR = 0;
double sumG = 0;
double sumB = 0;
for(int k = -r;k<= r;++k){
for(int l = -r;l<=r;++l){
int pos = (l+r)*diameter+(k+r);
if(i+k>=0 && i+k< width && l+j>=0 && l+j< height){
QColor color(m_srcImg.pixel(i+k,j+l));
int r = color.red();
int g = color.green();
int b = color.blue();
sumR += r*weights[pos];
sumG += g*weights[pos];
sumB += b*weights[pos];
}else{
//如果超出边界
int x = i+k,y = l+j;
if(i+k < 0 || i+k >= width)
x = i-k;
if(j+l < 0 || j+l >= height)
y = j-l;
QColor color(m_srcImg.pixel(x,y));
int r = color.red();
int g = color.green();
int b = color.blue();
sumR += r*weights[pos];
sumG += g*weights[pos];
sumB += b*weights[pos];
}
}
}
QColor finalColor;
int r = std::round(sumR);
int g = std::round(sumG);
int b = std::round(sumB);
finalColor.setRed(r);
finalColor.setGreen(g);
finalColor.setBlue(b);
QPoint point(i,j);
tagImg.setPixelColor(point,finalColor);
}
}
//----------------------------------------------------高斯模糊完成
// endTime = clock();
// qDebug()<<"模糊耗时"<<endTime - startTime;
QPixmap map = QPixmap::fromImage(tagImg);
ui->tvTag->setPixmap(map);
ui->tvTag->setScaledContents(true);
tagImg.save("d:/tag.png");
qDebug()<<"done";
delete [] weights;
}
void MainWindow::on_btnGaussBlur_clicked()
{
GuassianBlur2D();
}
以上代码就是2维高斯模糊的C++实现,它的复杂度是 xyr^2 对于一张1440*1080的图片,卷积核半径设置为10的情况下,release版本耗时11秒多。
原图:
原图二维高斯模糊效果图:
二维高斯模糊效果图
网友评论