跳转至

平滑

在数字图像上进行平滑操作,就是需要用模板(高斯模板,均值模板)在图像上做卷积,得到新值,这就是模糊的原理。卷积核中的每个元素叫做卷积系数,卷积核中卷积系数的大小、方向及排列次序决定图像卷积后处理的最终效果。改变卷积核中的加权系数,会影响到综合的数值与符号,从而影响到所求像素的新值。常用卷积核有3×3、5×5、7×7等,所有卷积核的行、列都是奇数。均值滤波、高斯滤波以及各种自定义的滤波都可以通过模板操作来实现。 其中常见的模板有:

  • 4邻域均值模板:
1/4\begin{bmatrix} 0 & 1 &0 \\ 1 & 1 & 1\\ 0 & 1 & 0 \end{bmatrix}
  • 8邻域均值
1/8\begin{bmatrix} 1 & 1 &1 \\ 1 & 0 & 1\\ 1 & 1 & 1 \end{bmatrix}
  • 均值模板
1/9\begin{bmatrix} 1 & 1 &1 \\ 1 & 1 & 1\\ 1 & 1 & 1 \end{bmatrix}
  • 高斯模板
1/16\begin{bmatrix} 1 & 2 &1 \\ 2 & 4 & 2\\ 1 & 2 & 1 \end{bmatrix}

代码实现

实现一个均值模糊:

Mat TemplateBlur(Mat src) {
    int row = src.rows;
    int col = src.cols;
    Mat dst(row, col, CV_8UC3);
    int border = 1;
    for (int i = border; i < row - border; i++) {
        for (int j = border; j < col - border; j++) {
            for (int k = 0; k < 3; k++) {
                int sum = src.at<Vec3b>(i, j)[k] + src.at<Vec3b>(i + 1, j)[k] + src.at<Vec3b>(i - 1, j)[k] +
                    src.at<Vec3b>(i, j - 1)[k] + src.at<Vec3b>(i + 1, j - 1)[k] + src.at<Vec3b>(i - 1, j - 1)[k] +
                    src.at<Vec3b>(i, j + 1)[k] + src.at<Vec3b>(i + 1, j + 1)[k] + src.at<Vec3b>(i - 1, j + 1)[k];
                sum /= 9;
                dst.at<Vec3b>(i, j)[k] = sum;
            }
        }
    }
    return dst;
}

中值模糊

中值滤波是一种非线性滤波。中值滤波在一定条件下可以克服线性滤波器如最小均方滤波、均值滤波(BoxFilter)等带来的图像细节的模糊,而且对滤除椒盐灵三点的噪声最为有效。但对于一些细节多,特别是点、线、尖顶细节多的图像不宜采用中值滤波。

中值滤波一般采用一个含有奇数个点的滑动窗口,将窗口中各点灰度值的中值来替代指定点的灰度值。对于奇数个元素,中值是值排序后中间的数值;对于偶数则是排数后中间两个元素的平均值。常用的中值滤波窗口有线状、方形、原型、十字型等,本文算法只实现方形的中值滤波,其他形状的中值滤波实现方式相似。

# 代码

Mat MedianFilter(Mat src, int ksize) {
    int row = src.rows;
    int col = src.cols;
    int border = (ksize - 1) / 2;
    int mid = (ksize*ksize - 1) / 2;
    Mat dst(row, col, CV_8UC3);
    for (int i = border; i < row - border; i++) {
        for (int j = border; j < col - border; j++) {
            for (int k = 0; k < 3; k++) {
                vector <int> v;
                for (int x = -border; x <= border; x++) {
                    for (int y = -border; y <= border; y++) {
                        v.push_back(src.at<Vec3b>(i + x, j + y)[k]);
                    }
                }
                sort(v.begin(), v.end());
                dst.at<Vec3b>(i, j)[k] = v[mid];
            }
        }
    }
    return dst;
}

高斯模糊

一维高斯公式为:G(x)=\frac{1}{\sqrt{2\pi}\delta}e^{\frac{-x^2}{2\delta^2}} 二维高斯公式为: G(x,y)=\frac{1}{2\pi\delta^2}e^{-\frac{x^2+y^2}{2\delta^2}} 理论上,高斯分布在所有定义域上都有非负值,这就需要一个无限大的卷积核。实际上,仅需要取均值周围3倍标准差内的值,以外部份直接去掉即可。 如下图为一个标准差为1.0的整数值高斯核:

1/273\begin{bmatrix} 1 & 4 &7 &4 &1 \\ 4 & 16 & 26&16&4\\ 7 & 26 & 41&26&7\\ 4&16&26&16&4\\ 1&4&7&4&1 \end{bmatrix}

在有些场合,可以用下面的模板来对高斯核进行近似:

1/16\begin{bmatrix} 1 & 2 &1 \\ 2 & 4 & 2\\ 1 & 2 & 1 \end{bmatrix}

代码

// 创建高斯核
// kSize:卷积核的大小3、5、7等(3×3、5×5、7×7)
// sigma:方差
const float EPS = 1e-7;
void CreatGaussKernel(float **pdKernel, int kSize, float sigma) {
    int sum = 0;
    float dCenter = (kSize - 1) / 2;
    //生成高斯数据
    for (int i = 0; i < kSize; i++) {
        for (int j = 0; j < kSize; j++) {
            //用和来近似平方和的开方
            float dis = fabsf(i - dCenter) + fabsf(j - dCenter);
            float val = exp(-dis * dis / (2 * sigma * sigma + EPS));
            pdKernel[i][j] = val;
            sum += val;
        }
    }
    //归一化
    for (int i = 0; i < kSize; i++) {
        for (int j = 0; j < kSize; j++) {
            pdKernel[i][j] /= (sum + EPS);
        }
    }
}

Mat GaussBlur(Mat src, int kSize, float sigma) {
    int row = src.rows;
    int col = src.cols;
    //分配高斯核空间
    float **pKernel = new float*[kSize];
    for (int i = 0; i < kSize; i++) {
        pKernel[i] = new float[kSize];
    }
    Mat dst(row, col, CV_8UC3);
    CreatGaussKernel(pKernel, kSize, sigma);
    int border = (kSize - 1) / 2;
    float sum = 0;
    for (int i = border; i < row - border; i++) {
        for (int j = border; j < col - border; j++) {
            for (int k = 0; k < 3; k++) {
                sum = 0;
                for (int x = -border; x <= border; x++) {
                    for (int y = -border; y <= border; y++) {
                        sum += src.at<Vec3b>(i + x, j + y)[k] * pKernel[border + x][border + y];
                    }
                }
                if (sum > 255) sum = 255;
                else if (sum < 0) sum = 0;
                dst.at<Vec3b>(i, j)[k] = sum;
            }
        }
    }
    return dst;
}

运动模糊

原文:https://blog.csdn.net/kezunhai/article/details/41757681

造成图像模糊的原因有很多种,如果是因为在摄像时相机和被摄景物有相对运动而造成的图像模糊则称为运动模糊。所得到的图像中的景物往往会模糊不清,我们称之为运动模糊图像。运动模糊(Motion Blur)是一种抓取物体运动状态效果的滤镜,主要应用物体运动时曝光的摄影手法,模拟出在摄像中拍摄运动物体的间接曝光功能,从而使图像产生出一种动态效果。它通常用来制造物体掠过或移动的效果。

运动模糊滤镜沿特定的方向,并以特定的强度进行模糊处理。首先,在数学上,Y轴向上为正,而在图像处理中,Y轴向下为正,所以在获取用户指定方向角度后,应先将其沿正方形旋转180°;接着,解决图像在指定方向上的位移问题。运动莫不是简单地将图像在指定的图像上移来移去,而是在距离限定的范围内,按某种方式复制并叠加像素。简单地可以看成,将一幅图像的多张副本叠放在指定的方向上,然后取其平均值;最后,要解决的问题就是图像的透明度,处理Alpha分类,这样最终产生的模糊效果才更理想。

代码实现

//添加运动模糊效果
//angle:运动的方向,distance:运动的距离
//这里只是粗略的计算,以dx的长度为准,也可以以dy或者dx+dy等长度微赚
Mat MotionBlur(const Mat &src, int angle = 30, int distance = 100) {
    if (distance < 1) distance = 1;
    else if (distance > 200) distance = 200;
    double radian = ((double)angle + 180.0) / 180.0 * PI;
    int dx = (int)((double)distance * cos(radian) + 0.5);
    int dy = (int)((double)distance * sin(radian) + 0.5);
    int sign;
    if (dx < 0) sign = -1;
    if (dx > 0) sign = 1;
    int height = src.rows;
    int width = src.cols;
    int chns = src.channels();
    Mat dst;
    dst.create(height, width, src.type());
    for (int i = 0; i < height; i++) {
        unsigned  char* dstData = (unsigned char*)dst.data + dst.step * i;
        for (int j = 0; j < width; j++) {
            for (int k = 0; k < chns; k++) {
                int sum = 0, count = 0;
                for (int p = 0; p < abs(dx); p++) {
                    int i0 = i + p*sign;
                    int j0 = j + p*sign;
                    if (i0 >= 0 && i0 < height && j0 >= 0 && j0 < width) {
                        count++;
                        sum += src.at<Vec3b>(i0, j0)[k];
                    }
                }
                if (count == 0) {
                    dstData[j*chns + k] = src.at<Vec3b>(i, j)[k];
                }
                else {
                    dstData[j*chns + k] = int(sum / (double)count + 0.5);
                    if (dstData[j*chns + k] < 0) dstData[j*chns + k] = 0;
                    else if (dstData[j*chns + k] > 255) dstData[j*chns + k] = 255;
                }
            }
        }
    }
    return dst;
}

径向模糊之缩放和旋转

原理:https://blog.csdn.net/xoyojank/article/details/5146297

代码实现

//径向模糊:缩放
//num: 均值力度
Mat RadialBlurZoom(Mat src, int num=10) {
    int row = src.rows;
    int col = src.cols;
    Point center(row / 2, col / 2);
    Mat dst(row, col, CV_8UC3);
    for (int i = 0; i < row; i++) {
        for (int j = 0; j < col; j++) {
            int R = norm(Point(i, j) - center);
            float angle = atan2(float(i - center.x), float(j - center.y));
            int sum1 = 0, sum2 = 0, sum3 = 0;
            for (int k = 0; k < num; k++) {
                int tmpR = (R - k) > 0 ? (R - k) : 0;
                int newX = tmpR * sin(angle) + center.x;
                int newY = tmpR * cos(angle) + center.y;
                if (newX < 0) newX = 0;
                if (newX > row - 1) newX = row - 1;
                if (newY < 0) newY = 0;
                if (newY > col - 1) newY = col - 1;
                sum1 += src.at<Vec3b>(newX, newY)[0];
                sum2 += src.at<Vec3b>(newX, newY)[1];
                sum3 += src.at<Vec3b>(newX, newY)[2];
            }
            dst.at<Vec3b>(i, j)[0] = (uchar)(sum1 / num);
            dst.at<Vec3b>(i, j)[1] = (uchar)(sum2 / num);
            dst.at<Vec3b>(i, j)[2] = (uchar)(sum3 / num);
        }
    }
    return dst;
}

//径向模糊:旋转
Mat RadialBlurRotate(Mat src, int num = 10) {
    int row = src.rows;
    int col = src.cols;
    Point center(row / 2, col / 2);
    Mat dst(row, col, CV_8UC3);
    for (int i = 0; i < row; i++) {
        for (int j = 0; j < col; j++) {
            int R = norm(Point(i, j) - center);
            float angle = atan2(float(i - center.x), float(j - center.y));
            int sum1 = 0, sum2 = 0, sum3 = 0;
            for (int k = 0; k < num; k++) {
                angle += 0.01;
                int newX = R * sin(angle) + center.x;
                int newY = R * cos(angle) + center.y;
                if (newX < 0) newX = 0;
                if (newX > row - 1) newX = row - 1;
                if (newY < 0) newY = 0;
                if (newY > col - 1) newY = col - 1;
                sum1 += src.at<Vec3b>(newX, newY)[0];
                sum2 += src.at<Vec3b>(newX, newY)[1];
                sum3 += src.at<Vec3b>(newX, newY)[2];
            }
            dst.at<Vec3b>(i, j)[0] = (uchar)(sum1 / num);
            dst.at<Vec3b>(i, j)[1] = (uchar)(sum2 / num);
            dst.at<Vec3b>(i, j)[2] = (uchar)(sum3 / num);
        }
    }
    return dst;
}

本文总阅读量