项目地址:ImageProcessing100Wen(极客教程地址)
收集了为图像处理初学者设计的 100 个问题以及对应的理论知识。和蝾螈一起学习基本的图像处理知识,理解图像处理算法吧!
解答这里提出的问题请不要调用OpenCV的API,自己动手实践。相信对你掌握opencv有比较好的帮助。
问题 9-19: | |||||
---|---|---|---|---|---|
9 | 高斯滤波(Gaussian Filter) | ||||
10 | 中值滤波(Median Filter) | ||||
11 | 均值滤波器 | ||||
12 | Motion Filter | ||||
13 | MAX-MIN滤波器 | ||||
14 | 差分滤波器(Differential Filter) | ||||
15 | Sobel滤波器 | ||||
16 | Prewitt滤波器 | ||||
17 | Laplacian滤波器 | ||||
18 | Emboss滤波器 | ||||
19 | LoG滤波器 |
高斯滤波器是一种可以使图像平滑的滤波器,用于去除噪声。可用于去除噪声的滤波器还有 中值滤波器,平滑滤波器、LoG滤波器。
高斯滤波器将中心像素周围的像素按照高斯分布加权平均进行平滑化。这样的(二维)权值通常被称为卷积核(kernel)或者滤波器(filter)。
比较常用的两种 3 × 3 3\times3 3×3 和 5 × 5 5\times5 5×5 大小的高斯模板如下: K = 1 16 [ 1 2 1 2 4 2 1 2 1 ] , K = 1 273 [ 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 ] , K=\frac{1}{16}\ \left[ \begin{matrix} 1 & 2 & 1 \\ 2 & 4 & 2 \\ 1 & 2 & 1 \end{matrix} \right] , \ \ \ K=\frac{1}{273}\ \left[ \begin{matrix} 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{matrix} \right], K=161 ⎣⎡121242121⎦⎤, K=2731 ⎣⎢⎢⎢⎢⎡1474141626164726412674162616414741⎦⎥⎥⎥⎥⎤,
高斯模板中的参数是通过高斯函数计算出来的。
先看一下一维高斯分布公式( μ \mu μ 是服从正态分布的随机变量 x x x 的均值, σ \sigma σ 是 x x x 的标准差,一般记为 0 省略)及图像(源自:高斯滤波): f ( x ) = 1 σ 2 π e − ( x − μ ) 2 2 σ 2 f(x)=\frac{1}{\sigma\sqrt{2\pi}}\ e^{-\frac{(x-\mu)^2}{2\ \sigma^2}} f(x)=σ2π1 e−2 σ2(x−μ)2
二维高斯分布公式,以及公式图像以及按图像的得出的 3 × 3 3\times3 3×3 模板均如下: g ( x , y ) = 1 2 π σ 2 e − x 2 + y 2 2 σ 2 g(x,y)=\frac{1}{2\pi\sigma^2}\ e^{-\frac{x^2+y^2}{2\ \sigma^2}} g(x,y)=2πσ21 e−2 σ2x2+y2
根据计算出来的模板卷积计算图像各个点的值,归一化是保证中心点的灰度值仍处于 [ 0 , 255 ] [0,255] [0,255] 之间,如果权重和大于 1(小于 1),高斯滤波后的图像整体会偏亮(偏暗)。因为要进行归一化, 1 2 π σ 2 \frac{1}{2\pi\sigma^2} 2πσ21 可以省略掉的,在计算时可以只计算 e − x 2 + y 2 2 σ 2 e^{-\frac{x^2+y^2}{2\ \sigma^2}} e−2 σ2x2+y2 的值。
由于图像的长宽不一定是滤波器大小的整数倍,所以边界处理时一般可以通过三种方式来填补缺失的周围点:
使用高斯滤波器( 3 × 3 3\times3 3×3 大小,标准差 σ = 1.3 \sigma=1.3 σ=1.3)来对imori_noise.jpg 进行降噪处理吧!
先在头文件中定义了枚举类型
//三种边界处理方式,补零,复制边缘值,取中心点对面对应点值
enum expandedBorderMode { ZeroPadding, Replicate, Mirror };
//高斯滤波
Mat GaussianFilter(Mat srcImg, double sigma, int kernelSize, expandedBorderMode mode) {
int width = srcImg.cols;
int height = srcImg.rows;
int channel = srcImg.channels();
//求出模板的中心位置(原点坐标)
if (kernelSize % 2 == 0)
cout << "错误!模板尺寸应该是奇数。";
int pad = floor(kernelSize / 2);
int _x = 0, _y = 0;
double kernel_sum = 0;
//创建二维数组
vector<vector<float>> kernel(kernelSize, vector<float>(kernelSize, 0));
//计算卷积核(高斯掩膜)
for (int y = 0; y < kernelSize; y++) {
for (int x = 0; x < kernelSize; x++) {
_y = y - pad;
_x = x - pad;
//kernel[y][x] = 1 / (2 * M_PI*sigma*sigma)*exp(-((_x*_x) + (_y * _y)) / (2 * sigma*sigma));
kernel[y][x] = exp(-((_x * _x) + (_y * _y)) / (2 * sigma * sigma));
kernel_sum += kernel[y][x];
}
}
//归一化
for (int y = 0; y < kernelSize; y++) {
for (int x = 0; x < kernelSize; x++) {
kernel[y][x] /= kernel_sum;
}
}
//滤波(掩膜*图像)
if (mode == NULL)
mode = ZeroPadding;
Mat dstImg = Filtering(srcImg, kernel, mode);
return dstImg;
}
//滤波
template <typename T>
Mat Filtering(Mat srcImg, vector<vector<T>> kernel, expandedBorderMode mode){
int width = srcImg.cols;
int height = srcImg.rows;
int channel = srcImg.channels();
int kernelSize = kernel.size();
int pad = floor(kernelSize / 2);
//根据输入图像的通道数决定输出图像(默认是三通道)
Mat dstImg = Mat::zeros(height, width, CV_8UC3);
if (channel == 1)
dstImg = Mat::zeros(height, width, CV_8UC1);
double val = 0;
Mat tmpImg = ExpandedImg(srcImg, mode, pad);
if (channel == 3) {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
for (int c = 0; c < channel; c++) {
val = 0;
for (int dy = -pad; dy < pad + 1; dy++) {
for (int dx = -pad; dx < pad + 1; dx++) {
val += (double)tmpImg.at<Vec3b>(y + pad + dy, x + pad + dx)[c] * kernel[dy + pad][dx + pad];
}
}
val = fmin(fmax(0, val), 255);
dstImg.at<Vec3b>(y, x)[c] = val;
}
}
}
}
else {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
val = 0;
for (int dy = -pad; dy < pad + 1; dy++) {
for (int dx = -pad; dx < pad + 1; dx++) {
val += (double)tmpImg.at<uchar>(y + pad + dy, x + pad + dx) * kernel[dy + pad][dx + pad];
}
}
val = fmin(fmax(0, val), 255);
dstImg.at<uchar>(y, x) = val;
}
}
}
return dstImg;
}
//扩展图像
Mat ExpandedImg(Mat srcImg, expandedBorderMode mode, int pad) {
int width = srcImg.cols;
int height = srcImg.rows;
int channel = srcImg.channels();
Mat dstImg;
switch (mode) {
case ZeroPadding:
if (channel == 3) {
dstImg = Mat::zeros(height + 2 * pad, width + 2 * pad, CV_8UC3);
//给图像及边缘赋值(四个角落+四条边+中间区域)
for (int y = 0; y < pad; ++y) {
for (int x = 0; x < pad; ++x)
dstImg.at<Vec3b>(y, x) = 0;
for (int x = width + pad; x < width + 2 * pad; ++x)
dstImg.at<Vec3b>(y, x) = 0;
}
for (int y = height + pad; y < height + 2 * pad; ++y) {
for (int x = 0; x < pad; ++x)
dstImg.at<Vec3b>(y, x) = 0;
for (int x = width + pad; x < width + 2 * pad; ++x)
dstImg.at<Vec3b>(y, x) = 0;
}
for (int y = 0; y < height; ++y) {
for (int x = 0; x < pad; ++x)
dstImg.at<Vec3b>(y + pad, x) = 0;
for (int x = width + pad; x < width + 2 * pad; ++x)
dstImg.at<Vec3b>(y + pad, x) = 0;
}
for (int x = 0; x < width; ++x) {
for (int y = 0; y < pad; ++y)
dstImg.at<Vec3b>(y, x + pad) = 0;
for (int y = height + pad; y < height + 2 * pad; ++y)
dstImg.at<Vec3b>(y, x + pad) = 0;
}
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
dstImg.at<Vec3b>(y + pad, x + pad) = srcImg.at<Vec3b>(y, x);
}
}
}
else {
dstImg = Mat::zeros(height + 2 * pad, width + 2 * pad, CV_8UC1);
//给图像及边缘赋值(四个角落+四条边+中间区域)
for (int y = 0; y < pad; ++y) {
for (int x = 0; x < pad; ++x)
dstImg.at<uchar>(y, x) = 0;
for (int x = width + pad; x < width + 2 * pad; ++x)
dstImg.at<uchar>(y, x) = 0;
}
for (int y = height + pad; y < height + 2 * pad; ++y) {
for (int x = 0; x < pad; ++x)
dstImg.at<uchar>(y, x) = 0;
for (int x = width + pad; x < width + 2 * pad; ++x)
dstImg.at<uchar>(y, x) = 0;
}
for (int y = 0; y < height; ++y) {
for (int x = 0; x < pad; ++x)
dstImg.at<uchar>(y + pad, x) = 0;
for (int x = width + pad; x < width + 2 * pad; ++x)
dstImg.at<uchar>(y + pad, x) = 0;
}
for (int x = 0; x < width; ++x) {
for (int y = 0; y < pad; ++y)
dstImg.at<uchar>(y, x + pad) = 0;
for (int y = height + pad; y < height + 2 * pad; ++y)
dstImg.at<uchar>(y, x + pad) = 0;
}
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
dstImg.at<uchar>(y + pad, x + pad) = srcImg.at<uchar>(y, x);
}
}
}
break;
case Replicate:
if (channel == 3) {
dstImg = Mat::zeros(height + 2 * pad, width + 2 * pad, CV_8UC3);
//给图像及边缘赋值(四个角落+四条边+中间区域)
for (int y = 0; y < pad; ++y) {
for (int x = 0; x < pad; ++x)
dstImg.at<Vec3b>(y, x) = srcImg.at<Vec3b>(0,0);
for (int x = width + pad; x < width + 2 * pad; ++x)
dstImg.at<Vec3b>(y, x) = srcImg.at<Vec3b>(0, width - 1);
}
for (int y = height + pad; y < height + 2 * pad; ++y) {
for (int x = 0; x < pad; ++x)
dstImg.at<Vec3b>(y, x) = srcImg.at<Vec3b>(height - 1, 0);
for (int x = width + pad; x < width + 2 * pad; ++x)
dstImg.at<Vec3b>(y, x) = srcImg.at<Vec3b>(height - 1, width - 1);
}
for (int y = 0; y < height; ++y) {
for (int x = 0; x < pad; ++x)
dstImg.at<Vec3b>(y + pad, x) = srcImg.at<Vec3b>(y, 0);
for (int x = width + pad; x < width + 2 * pad; ++x)
dstImg.at<Vec3b>(y + pad, x) = srcImg.at<Vec3b>(y, width - 1);
}
for (int x = 0; x < width; ++x) {
for (int y = 0; y < pad; ++y)
dstImg.at<Vec3b>(y, x + pad) = srcImg.at<Vec3b>(0, x);
for (int y = height + pad; y < height + 2 * pad; ++y)
dstImg.at<Vec3b>(y, x + pad) = srcImg.at<Vec3b>(height - 1, x);
}
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
dstImg.at<Vec3b>(y + pad, x + pad) = srcImg.at<Vec3b>(y, x);
}
}
}
else {
dstImg = Mat::zeros(height + 2 * pad, width + 2 * pad, CV_8UC1);
//给图像及边缘赋值(四个角落+四条边+中间区域)
for (int y = 0; y < pad; ++y) {
for (int x = 0; x < pad; ++x)
dstImg.at<uchar>(y, x) = srcImg.at<uchar>(0, 0);
for (int x = width + pad; x < width + 2 * pad; ++x)
dstImg.at<uchar>(y, x) = srcImg.at<uchar>(0, width - 1);
}
for (int y = height + pad; y < height + 2 * pad; ++y) {
for (int x = 0; x < pad; ++x)
dstImg.at<uchar>(y, x) = srcImg.at<uchar>(height - 1, 0);
for (int x = width + pad; x < width + 2 * pad; ++x)
dstImg.at<uchar>(y, x) = srcImg.at<uchar>(height - 1, width - 1);
}
for (int y = 0; y < height; ++y) {
for (int x = 0; x < pad; ++x)
dstImg.at<uchar>(y + pad, x) = srcImg.at<uchar>(y, 0);
for (int x = width + pad; x < width + 2 * pad; ++x)
dstImg.at<uchar>(y + pad, x) = srcImg.at<uchar>(y, width - 1);
}
for (int x = 0; x < width; ++x) {
for (int y = 0; y < pad; ++y)
dstImg.at<uchar>(y, x + pad) = srcImg.at<uchar>(0, x);
for (int y = height + pad; y < height + 2 * pad; ++y)
dstImg.at<uchar>(y, x + pad) = srcImg.at<uchar>(height - 1, x);
}
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
dstImg.at<uchar>(y + pad, x + pad) = srcImg.at<uchar>(y, x);
}
}
}
break;
case Mirror:
if (channel == 3) {
dstImg = Mat::zeros(height + 2 * pad, width + 2 * pad, CV_8UC3);
//给图像及边缘赋值(四个角落 + 四条边 + 中间区域)
for (int y = 0; y < pad; ++y) {
for (int x = 0; x < pad; ++x)
dstImg.at<Vec3b>(y, x) = srcImg.at<Vec3b>(pad - y, pad - x);
for (int x = width + pad; x < width + 2 * pad; ++x)
dstImg.at<Vec3b>(y, x) = srcImg.at<Vec3b>(pad - y, 2 * width + 1 - x);
}
for (int y = height + pad; y < height + 2 * pad; ++y) {
for (int x = 0; x < pad; ++x)
dstImg.at<Vec3b>(y, x) = srcImg.at<Vec3b>(2 * height + 1 - y, pad - x);
for (int x = width + pad; x < width + 2 * pad; ++x)
dstImg.at<Vec3b>(y, x) = srcImg.at<Vec3b>(2 * height + 1 - y, 2 * width + 1 - x);
}
for (int y = 0; y < height; ++y) {
for (int x = 0; x < pad; ++x)
dstImg.at<Vec3b>(y + pad, x) = srcImg.at<Vec3b>(y, pad - x);
for (int x = width + pad; x < width + 2 * pad; ++x)
dstImg.at<Vec3b>(y + pad, x) = srcImg.at<Vec3b>(y, 2 * width + 1 - x);
}
for (int x = 0; x < width; ++x) {
for (int y = 0; y < pad; ++y)
dstImg.at<Vec3b>(y, x + pad) = srcImg.at<Vec3b>(pad - y, x);
for (int y = height + pad; y < height + 2 * pad; ++y)
dstImg.at<Vec3b>(y, x + pad) = srcImg.at<Vec3b>(2 * height + 1 - y, x);
}
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
dstImg.at<Vec3b>(y + pad, x + pad) = srcImg.at<Vec3b>(y, x);
}
}
}
else {
dstImg = Mat::zeros(height + 2 * pad, width + 2 * pad, CV_8UC1);
//给图像及边缘赋值(四个角落 + 四条边 + 中间区域)
for (int y = 0; y < pad; ++y) {
for (int x = 0; x < pad; ++x)
dstImg.at<uchar>(y, x) = srcImg.at<uchar>(pad - y, pad - x);
for (int x = width + pad; x < width + 2 * pad; ++x)
dstImg.at<uchar>(y, x) = srcImg.at<uchar>(pad - y, 2 * width + 1 - x);
}
for (int y = height + pad; y < height + 2 * pad; ++y) {
for (int x = 0; x < pad; ++x)
dstImg.at<uchar>(y, x) = srcImg.at<uchar>(2 * height + 1 - y, pad - x);
for (int x = width + pad; x < width + 2 * pad; ++x)
dstImg.at<uchar>(y, x) = srcImg.at<uchar>(2 * height + 1 - y, 2 * width + 1 - x);
}
for (int y = 0; y < height; ++y) {
for (int x = 0; x < pad; ++x)
dstImg.at<uchar>(y + pad, x) = srcImg.at<uchar>(y, pad - x);
for (int x = width + pad; x < width + 2 * pad; ++x)
dstImg.at<uchar>(y + pad, x) = srcImg.at<uchar>(y, 2 * width + 1 - x);
}
for (int x = 0; x < width; ++x) {
for (int y = 0; y < pad; ++y)
dstImg.at<uchar>(y, x + pad) = srcImg.at<uchar>(pad - y, x);
for (int y = height + pad; y < height + 2 * pad; ++y)
dstImg.at<uchar>(y, x + pad) = srcImg.at<uchar>(2 * height + 1 - y, x);
}
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
dstImg.at<uchar>(y + pad, x + pad) = srcImg.at<uchar>(y, x);
}
}
}
break;
default:
break;
}
return dstImg;
}
int main(){
//高斯滤波
Mat image = imread("..\\Resource\\imori.jpg");
//image = BGR2GRAY(image);
Mat img = GaussianFilter(image, 1.3, 3);
namedWindow("sample", 0);
imshow("sample", img);
waitKey(0);
destroyAllWindows();
return 0;
}
中值滤波是基于排序统计理论的一种非线性信号处理技术,基本原理是在数字图像或数字序列中,把某一点的值用其邻域中各点值的中值代替,从而消除孤立的噪声点。
使用中值滤波器( 3 × 3 3\times3 3×3大小)来对imori_noise.jpg进行降噪处理吧!
中值滤波器是一种可以使图像平滑的滤波器。这种滤波器用滤波器范围内(在这里是 3 × 3 3\times3 3×3)像素点的中值进行滤波,请在这里也采用 Zero Padding。
//中值滤波
Mat MedianFilter(Mat srcImg, int kernelSize, expandedBorderMode mode) {
int width = srcImg.cols;
int height = srcImg.rows;
int channel = srcImg.channels();
if (kernelSize % 2 == 0)
cout << "错误!模板尺寸应该是奇数。";
int pad = floor(kernelSize / 2);
//根据输入图像的通道数决定输出图像(默认是三通道)
Mat dstImg = Mat::zeros(height, width, CV_8UC3);
if (channel == 1)
dstImg = Mat::zeros(height, width, CV_8UC1);
vector<int> kernelVals(kernelSize*kernelSize, 0);
if (mode == NULL)
mode = ZeroPadding;
Mat tmpImg = ExpandedImg(srcImg, mode, pad);
int i = 0;
if (channel == 3) {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
for (int c = 0; c < channel; c++) {
i = 0;
for (int dy = -pad; dy < pad + 1; dy++) {
for (int dx = -pad; dx < pad + 1; dx++) {
kernelVals[i++] = tmpImg.at<Vec3b>(y + pad + dy, x + pad + dx)[c];
}
}
sort(kernelVals.begin(), kernelVals.end());
dstImg.at<Vec3b>(y, x)[c] = kernelVals[pad+1];
}
}
}
}
else {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
i = 0;
for (int dy = -pad; dy < pad + 1; dy++) {
for (int dx = -pad; dx < pad + 1; dx++) {
kernelVals[i++] = tmpImg.at<uchar>(y + pad + dy, x + pad + dx);
}
}
sort(kernelVals.begin(), kernelVals.end());
dstImg.at<uchar>(y, x) = kernelVals[pad + 1];
}
}
}
return dstImg;
}
int main(){
//中值滤波
Mat imsage = imread("..\\Resource\\imori.jpg");
//image = BGR2GRAY(image);
Mat img = MedianFilter(image, 3);
namedWindow("sample", 0);
imshow("sample", img);
waitKey(0);
destroyAllWindows();
return 0;
}
均值滤波也称为线性滤波,主要采用邻域平均法,基本原理是用某一点邻域值的均值代替原图像中点的像素值。
使用 3 × 3 3\times3 3×3 的均值滤波器来进行滤波吧!
//均值滤波
Mat MeanFilter(Mat srcImg, int kernelSize, expandedBorderMode mode) {
int width = srcImg.cols;
int height = srcImg.rows;
int channel = srcImg.channels();
if (kernelSize % 2 == 0)
cout << "错误!模板尺寸应该是奇数。";
int pad = floor(kernelSize / 2);
//根据输入图像的通道数决定输出图像(默认是三通道)
Mat dstImg = Mat::zeros(height, width, CV_8UC3);
if (channel == 1)
dstImg = Mat::zeros(height, width, CV_8UC1);
double sumVals = 0;
if (mode == NULL)
mode = ZeroPadding;
Mat tmpImg = ExpandedImg(srcImg, mode, pad);
if (channel == 3) {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
for (int c = 0; c < channel; c++) {
sumVals = 0;
for (int dy = -pad; dy < pad + 1; dy++) {
for (int dx = -pad; dx < pad + 1; dx++) {
sumVals += tmpImg.at<Vec3b>(y + pad + dy, x + pad + dx)[c];
}
}
dstImg.at<Vec3b>(y, x)[c] = sumVals / (kernelSize*kernelSize);
}
}
}
}
else {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
sumVals = 0;
for (int dy = -pad; dy < pad + 1; dy++) {
for (int dx = -pad; dx < pad + 1; dx++) {
sumVals += tmpImg.at<uchar>(y + pad + dy, x + pad + dx);
}
}
dstImg.at<uchar>(y, x) = sumVals / (kernelSize*kernelSize);
}
}
}
return dstImg;
}
int main(){
//均值滤波
Mat image = imread("..\\Resource\\imori.jpg");
//image = BGR2GRAY(image);
Mat img = MeanFilter(image, 3);
namedWindow("sample", 0);
imshow("sample", img);
waitKey(0);
destroyAllWindows();
return 0;
}
运动模糊滤波取对角线方向上像素的平均值,像下式这样定义:
[ 1 3 0 0 0 1 3 0 0 0 1 3 ] \left[ \begin{matrix} \ \frac{1}{3}\ & 0 & 0 \\ 0 & \ \frac{1}{3}\ & 0 \\ 0 & 0 & \ \frac{1}{3}\ \end{matrix} \right] ⎣⎡ 31 000 31 000 31 ⎦⎤使用 3 × 3 3\times3 3×3 的Motion Filter来进行滤波吧。
//运动模糊滤波
Mat MotionFilter(Mat srcImg, int kernelSize, expandedBorderMode mode) {
int width = srcImg.cols;
int height = srcImg.rows;
int channel = srcImg.channels();
if (kernelSize % 2 == 0)
cout << "错误!模板尺寸应该是奇数。";
//创建二维数组
vector<vector<float>> kernel(kernelSize, vector<float>(kernelSize, 0));
//计算卷积核(左上至右下斜对角线)
for (int x = 0; x < kernelSize; x++) {
kernel[x][x] = 1.0 * 1 / kernelSize;
}
//滤波(掩膜*图像)
if (mode == NULL)
mode = ZeroPadding;
Mat dstImg = Filtering(srcImg, kernel, mode);
return dstImg;
}
int main(){
//运动模糊滤波
Mat image = imread("..\\Resource\\imori.jpg");
//image = BGR2GRAY(image);
Mat img = MotionFilter(image, 3);
namedWindow("sample", 0);
imshow("sample", img);
waitKey(0);
destroyAllWindows();
return 0;
}
使用MAX-MIN滤波器来进行滤波吧。
MAX-MIN滤波器计算出网格内像素最大值和最小值的差值,来对网格内像素重新赋值,通常用于边缘检测(检测图像中的线)。像这样提取图像中的信息的操作被称为特征提取。
边缘检测通常在灰度图像上进行。
//Max-Min滤波
Mat MaxMinFilter(Mat srcImg, int kernelSize, expandedBorderMode mode) {
int width = srcImg.cols;
int height = srcImg.rows;
int channel = srcImg.channels();
if (kernelSize % 2 == 0)
cout << "错误!模板尺寸应该是奇数。";
int pad = floor(kernelSize / 2);
//如果输入彩色图像,转化为灰度图
Mat dstImg = Mat::zeros(height, width, CV_8UC1);
if (channel == 3)
srcImg = BGR2GRAY(srcImg);
vector<int> kernelVals(kernelSize*kernelSize, 0);
if (mode == NULL)
mode = ZeroPadding;
Mat tmpImg = ExpandedImg(srcImg, mode, pad);
int i = 0;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
i = 0;
for (int dy = -pad; dy < pad + 1; dy++) {
for (int dx = -pad; dx < pad + 1; dx++) {
kernelVals[i++] = tmpImg.at<uchar>(y + pad + dy, x + pad + dx);
}
}
sort(kernelVals.begin(), kernelVals.end());
dstImg.at<uchar>(y, x) = kernelVals[kernelSize*kernelSize - 1] - kernelVals[0];
}
}
return dstImg;
}
int main(){
//运动模糊滤波
Mat image = imread("..\\Resource\\imori.jpg");
//image = BGR2GRAY(image);
Mat img = MaxMinFilter(image, 3);
namedWindow("sample", 0);
imshow("sample", img);
waitKey(0);
destroyAllWindows();
return 0;
}
使用 3 × 3 3\times3 3×3 的差分滤波器来进行滤波吧。
差分滤波器对图像亮度急剧变化的边缘有提取效果,可以获得邻接像素的差值。
纵 向 : [ 0 − 1 0 0 1 0 0 0 0 ] 横 向 : [ 0 0 0 − 1 1 0 0 0 0 ] 纵向: \left[ \begin{matrix} 0 & -1 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 0 \end{matrix} \right] \ \ \ \ \ \ \ 横向:\left[ \begin{matrix} 0 & 0 & \ 0 \\ -1 & 1 & \ 0 \\ 0 & 0 & \ 0 \end{matrix} \right] 纵向:⎣⎡000−110000⎦⎤ 横向:⎣⎡0−10010 0 0 0⎦⎤
头文件声明中默认是水平方向滤波器。
//差分滤波器
Mat DifferentialFilter(Mat srcImg, bool isHorizontal, expandedBorderMode mode) {
int width = srcImg.cols;
int height = srcImg.rows;
int channel = srcImg.channels();
//如果输入彩色图像,转化为灰度图
if (channel == 3)
srcImg = BGR2GRAY(srcImg);
//创建二维数组,计算卷积核
vector<vector<int>> kernel{ {0,0,0},{-1,1,0},{0,0,0} };
if (!isHorizontal) {
kernel[0][1] = -1;
kernel[1][0] = 0;
}
//滤波(掩膜*图像)
if (mode == NULL)
mode = ZeroPadding;
Mat dstImg = Filtering(srcImg, kernel, mode);
return dstImg;
}
int main(){
//差分滤波
Mat image = imread("..\\Resource\\imori.jpg");
//image = BGR2GRAY(image);
Mat img = DifferentialFilter(image);
namedWindow("sample", 0);
imshow("sample", img);
waitKey(0);
destroyAllWindows();
return 0;
}
使用 3 × 3 3\times3 3×3 的Sobel滤波器来进行滤波吧。
Sobel滤波器可以提取特定方向(纵向或横向)的边缘,滤波器按下式定义:
纵 向 : [ 1 2 1 0 0 0 − 1 − 2 − 1 ] 横 向 : [ 1 0 − 1 2 0 − 2 1 0 − 1 ] 纵向: \left[ \begin{matrix} 1 & 2 & 1 \\ 0 & 0 & 0 \\ -1 & -2 & -1 \end{matrix} \right] \ \ \ \ \ \ \ 横向:\left[ \begin{matrix} 1 & \ \ 0 & -1 \\ 2 & \ \ 0 & -2 \\ 1 & \ \ 0 & -1 \end{matrix} \right] 纵向:⎣⎡10−120−210−1⎦⎤ 横向:⎣⎡121 0 0 0−1−2−1⎦⎤略微展开的话,尺寸计算可以看看这里:不同尺寸的Sobel模板 ,尺寸必须是 1,3,5 或 7(尺寸为 1 时,使用 3 × 1 3\times1 3×1 或 1 × 3 1\times3 1×3 内核 ,不进行高斯平滑操作)以及可以通过旋转卷积核(0° 45° 90° 135° … \dots … 360°)来扩展。
头文件声明中默认是水平方向滤波器。
//Sobel滤波器
Mat SobelFilter(Mat srcImg, bool isHorizontal, expandedBorderMode mode) {
int width = srcImg.cols;
int height = srcImg.rows;
int channel = srcImg.channels();
//如果输入彩色图像,转化为灰度图
if (channel == 3)
srcImg = BGR2GRAY(srcImg);
//创建二维数组,计算卷积核
vector<vector<int>> kernel{ {1,0,-1},{2,0,-2},{1,0,-1} };
if (!isHorizontal) {
kernel[0][1] = 2;
kernel[0][2] = 1;
kernel[1][0] = 0;
kernel[1][2] = 0;
kernel[2][0] = -1;
kernel[2][1] = -2;
}
//滤波(掩膜*图像)
if (mode == NULL)
mode = ZeroPadding;
Mat dstImg = Filtering(srcImg, kernel, mode);
return dstImg;
}
int main(){
//Sobel滤波
Mat image = imread("..\\Resource\\imori.jpg");
//image = BGR2GRAY(image);
Mat img = SobelFilter(image);
namedWindow("sample", 0);
imshow("sample", img);
waitKey(0);
destroyAllWindows();
return 0;
}
使用 3 × 3 3\times3 3×3 的Prewitt滤波器来进行滤波吧。
Prewitt滤波器是用于边缘检测的一种滤波器,使用下式定义:
纵 向 : [ − 1 − 1 − 1 0 0 0 1 1 1 ] 横 向 : [ − 1 0 1 − 1 0 1 − 1 0 1 ] 纵向: \left[ \begin{matrix} -1 & -1 & -1 \\ 0 & 0 & 0 \\ 1 & 1 & 1 \end{matrix} \right] \ \ \ \ \ \ \ 横向:\left[ \begin{matrix} -1 & \ \ 0 & \ \ 1 \\ -1 & \ \ 0 & \ \ 1 \\ -1 & \ \ 0 & \ \ 1 \end{matrix} \right] 纵向:⎣⎡−101−101−101⎦⎤ 横向:⎣⎡−1−1−1 0 0 0 1 1 1⎦⎤感觉数值上和差分有点像,形式上和Sobel类似差不多。
//Prewitt滤波器
Mat PrewittFilter(Mat srcImg, bool isHorizontal, expandedBorderMode mode) {
int width = srcImg.cols;
int height = srcImg.rows;
int channel = srcImg.channels();
//如果输入彩色图像,转化为灰度图
if (channel == 3)
srcImg = BGR2GRAY(srcImg);
//创建二维数组,计算卷积核
vector<vector<int>> kernel{ {-1,0,1},{-1,0,1},{-1,0,1} };
if (!isHorizontal) {
kernel[0][1] = -1;
kernel[0][2] = -1;
kernel[1][0] = 0;
kernel[1][2] = 0;
kernel[2][0] = 1;
kernel[2][1] = 1;
}
//滤波(掩膜*图像)
if (mode == NULL)
mode = ZeroPadding;
Mat dstImg = Filtering(srcImg, kernel, mode);
return dstImg;
}
int main(){
//Prewitt滤波
Mat image = imread("..\\Resource\\imori.jpg");
Mat img = PrewittFilter(image);
namedWindow("sample", 0);
imshow("sample", img);
waitKey(0);
destroyAllWindows();
return 0;
}
Laplacian滤波器是一种二阶导数算子,具有旋转不变性,可以满足不同方向的图像边缘锐化的要求。通常情况下,其算子的系数之和需要为零,如下: [ 0 1 0 1 − 4 1 0 1 0 ] \left[ \begin{matrix} 0 & 1 & 0 \\ 1 & -4 & 1 \\ 0 & 1 & 0 \end{matrix} \right] ⎣⎡0101−41010⎦⎤
Mat LaplacianFilter(cv::Mat srcImg, expandedBorderMode mode){
int width = srcImg.cols;
int height = srcImg.rows;
int channel = srcImg.channels();
//如果输入彩色图像,转化为灰度图
if (channel == 3)
srcImg = BGR2GRAY(srcImg);
//创建二维数组,计算卷积核
vector<vector<int>> kernel{ {0,1,0},{1,-4,1},{0,1,0} };
//滤波(掩膜*图像)
if (mode == NULL)
mode = ZeroPadding;
Mat dstImg = Filtering(srcImg, kernel, mode);
return dstImg;
}
int main(){
//LaplacianFilter滤波
Mat image = imread("..\\Resource\\imori.jpg");
Mat img = LaplacianFilter(image);
namedWindow("sample", 0);
imshow("sample", img);
waitKey(0);
destroyAllWindows();
return 0;
}
Emboss滤波器常用于检测图像的边缘和轮廓,能够有效地增强图像的高频信息(边缘和轮廓),并保留图像的低频信息(图像内容)。
[ − 2 − 1 0 − 1 1 1 0 1 2 ] \left[ \begin{matrix} -2 & -1 & 0 \\ -1 & 1 & 1 \\ 0 & 1 & 2 \end{matrix} \right] ⎣⎡−2−10−111012⎦⎤
//Emboss滤波器
Mat EmbossFilter(cv::Mat srcImg, expandedBorderMode mode) {
int width = srcImg.cols;
int height = srcImg.rows;
int channel = srcImg.channels();
//如果输入彩色图像,转化为灰度图
if (channel == 3)
srcImg = BGR2GRAY(srcImg);
//创建二维数组,计算卷积核
vector<vector<int>> kernel{ {-2,-1,0},{-1,1,1},{0,1,2} };
//滤波(掩膜*图像)
if (mode == NULL)
mode = ZeroPadding;
Mat dstImg = Filtering(srcImg, kernel, mode);
return dstImg;
}
int main(){
//Emboss滤波
Mat image = imread("..\\Resource\\imori.jpg");
Mat img = EmbossFilter(image);
namedWindow("sample", 0);
imshow("sample", img);
waitKey(0);
destroyAllWindows();
return 0;
}
LoG即高斯-拉普拉斯(Laplacian of Gaussian)的缩写,使用高斯滤波器使图像平滑化之后再使用拉普拉斯滤波器使图像的轮廓更加清晰。
为了防止拉普拉斯滤波器计算二次微分会使得图像噪声更加明显,所以我们首先使用高斯滤波器来抑制噪声。
LoG 滤波器使用以下式子定义: L O G ( x , y ) = x 2 + y 2 − σ 2 2 π σ 6 e − x 2 + y 2 2 σ 2 LOG(x,y)=\frac{x^2+y^2-\sigma^2}{2\pi\sigma^6}\ e^{-\frac{x^2+y^2}{2\ \sigma^2}} LOG(x,y)=2πσ6x2+y2−σ2 e−2 σ2x2+y2
//LoG滤波器
Mat LoGFilter(Mat srcImg, double sigma, int kernelSize, expandedBorderMode mode) {
int width = srcImg.cols;
int height = srcImg.rows;
int channel = srcImg.channels();
//求出模板的中心位置(原点坐标)
if (kernelSize % 2 == 0)
cout << "错误!模板尺寸应该是奇数。";
int pad = floor(kernelSize / 2);
int _x = 0, _y = 0;
double kernel_sum = 0;
//创建二维数组
vector<vector<double>> kernel(kernelSize, vector<double>(kernelSize, 0));
//计算卷积核(高斯掩膜)
for (int y = 0; y < kernelSize; y++) {
for (int x = 0; x < kernelSize; x++) {
_y = y - pad;
_x = x - pad;
kernel[y][x] = ((_x*_x) + (_y * _y) - (sigma*sigma)) /
(2 * M_PI*pow(sigma, 6))*exp(-((_x*_x) + (_y * _y)) / (2 * sigma*sigma));
kernel_sum += kernel[y][x];
}
}
//归一化
for (int y = 0; y < kernelSize; y++) {
for (int x = 0; x < kernelSize; x++) {
kernel[y][x] /= kernel_sum;
}
}
//滤波(掩膜*图像)
if (mode == NULL)
mode = ZeroPadding;
Mat dstImg = Filtering(srcImg, kernel, mode);
return dstImg;
}
int main(){
//LoG滤波
Mat image = imread("..\\Resource\\imori.jpg");
//image = BGR2GRAY(image);
Mat img = LoGFilter(image,5,3);
namedWindow("sample", 0);
imshow("sample", img);
waitKey(0);
destroyAllWindows();
return 0;
}