原理
sobel滤波主要用于边缘检测,通过测量图像中每个像素点强度的梯度,从而找到像素点强度值变化最快的方向以及这个方向的变化率。检测结果显示了图像强度值在每个像素处的变化大小,从而判断以及该像素点是否为图像的边缘。
在强度恒定的区域对像素进行滤波,得到的结果是近乎为零的向量。
如果再图像的边缘进行检测,则可以得到一个由强度较弱的像素指向强度较亮的向量。
sobel滤波使用两个
3
∗
3
3 * 3
3∗3的卷积核 一个代表水平方向上的变化,另一个代表垂直方向上的变化。用两个卷积核与原始图像做卷积运算,从而计算出导数的近似值。计算如下:
G
x
=
[
−
1
0
1
−
2
0
2
−
1
0
1
]
∗
A
Gx = \begin{bmatrix} -1 & 0 & 1 \\ -2 & 0 & 2\\ -1 & 0 & 1\end{bmatrix} * A
Gx=⎣⎡−1−2−1000121⎦⎤∗A
G
y
=
[
−
1
−
2
−
1
0
0
0
1
2
1
]
∗
A
\\ Gy = \begin{bmatrix} -1 & -2 & -1 \\ 0 & 0 & 0\\ 1 & 2 & 1\end{bmatrix} * A
Gy=⎣⎡−101−202−101⎦⎤∗A
其中
A
A
A是原图像,
G
x
Gx
Gx和
G
y
Gy
Gy分别是包含了图像像素在水平和垂直方向上的导数的近似值的图像。也就是原图像经过横向和纵向检测出的图像灰度值。
为了计算
G
x
Gx
Gx和
G
y
Gy
Gy,我们在输入图像上移动适当的内核(窗口),计算一个像素的值,然后向右移动一个像素。一旦到达该行的末尾,我们就向下移动到下一行的开头。
在网上截了一张计算
G
x
Gx
Gx过程的图片,卷积核与上述略有出入:
同理可得
G
y
Gy
Gy的计算。
在每个像素点处的灰度大小由
G
x
Gx
Gx和
G
y
Gy
Gy共同决定:
G
=
G
x
2
+
G
y
2
G = \sqrt {Gx^2 + Gy^2}
G=Gx2+Gy2
通常为了提高效率使用如下近似:
G
=
∣
G
x
∣
+
∣
G
y
∣
G = |Gx| + |Gy|
G=∣Gx∣+∣Gy∣
实现代码(C++实现,VS2015):
#pragma once
#include <opencv2\opencv.hpp>
#include <iostream>
#include <cmath>
using namespace std;
cv::Mat BGR2GRAY(cv::Mat src) { //转换为灰度图片
int rows = src.rows;
int cols = src.cols;
cv::Mat dst = cv::Mat::zeros(rows, cols, CV_8UC1);
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
dst.at<uchar>(i, j) = 0.2126 * (float)src.at<cv::Vec3b>(i, j)[2]
+ 0.7152 * (float)src.at<cv::Vec3b>(i, j)[1]
+ 0.0722 * (float)src.at<cv::Vec3b>(i, j)[0];
}
}
return dst;
}
void Sobelfilter(cv::Mat &src) {//sobel 滤波
int rows = src.rows;
int cols = src.cols;
double dx = 0, dy = 0;
cv::Mat Gx = cv::Mat::zeros(rows, cols, CV_8UC1);
cv::Mat Gy = cv::Mat::zeros(rows, cols, CV_8UC1);
cv::Mat G = cv::Mat::zeros(rows, cols, CV_8UC1);
double v = 0, vx, vy;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
v = 0;
if (i == 0 || j == 0 || i == rows - 1 || j == cols - 1) {
G.at<uchar>(i, j) = 0;
}
else {
dx = src.at<uchar>(i - 1, j + 1) - src.at<uchar>(i - 1, j - 1)
+ 2 * src.at<uchar>(i, j + 1) - 2 * src.at<uchar>(i, j - 1) +
src.at<uchar>(i + 1, j + 1) - src.at<uchar>(i + 1, j - 1);
dy = src.at<uchar>(i + 1, j - 1) - src.at<uchar>(i - 1, j - 1)
+ 2 * src.at<uchar>(i + 1, j) - 2 * src.at<uchar>(i - 1, j) +
src.at<uchar>(i + 1, j + 1) - src.at<uchar>(i - 1, j + 1);
v = abs(dx) + abs(dy); //G = |Gx| + |Gy|
v = fmax(v, 0);
v = fmin(v, 255);
G.at<uchar>(i, j) = (uchar)v;
}
}
}
for (int i = 0; i < rows; i++) { // 水平方向
for (int j = 0; j < cols; j++) {
vx = 0;
if (i == 0 || j == 0 || i == rows - 1 || j == cols - 1)
Gx.at<uchar>(i, j) = 0;
else {
dx = src.at<uchar>(i - 1, j + 1) - src.at<uchar>(i - 1, j - 1)
+ 2 * src.at<uchar>(i, j + 1) - 2 * src.at<uchar>(i, j - 1) +
src.at<uchar>(i + 1, j + 1) - src.at<uchar>(i + 1, j - 1);
vx = abs(dx);
vx = fmax(vx, 0); vx = fmin(vx, 255);
Gx.at<uchar>(i, j) = (uchar)vx;
}
}
}
for (int i = 0; i < rows; i++) {// 垂直方向
for (int j = 0; j < cols; j++) {
vy = 0;
if (i == 0 || j == 0 || i == rows - 1 || j == cols - 1)
Gy.at<uchar>(i, j) = 0;
else {
dy = src.at<uchar>(i + 1, j - 1) - src.at<uchar>(i - 1, j - 1)
+ 2 * src.at<uchar>(i + 1, j) - 2 * src.at<uchar>(i - 1, j) +
src.at<uchar>(i + 1, j + 1) - src.at<uchar>(i - 1, j + 1);
vy = abs(dy);
vy = fmax(vy, 0); vx = fmin(vy, 255);
Gy.at<uchar>(i, j) = (uchar)vy;
}
}
}
cv::imshow("Gx", Gx);// horizontal
cv::imshow("Gy", Gy);// vertical
cv::imshow("G", G);// gradient
}
int main(int argc, char** argv) {
cv::Mat src, gray;
src = cv::imread("C:/Users/Odysseus96/Pictures/Image/lena.jpg");
cv::imshow("Input", src);
gray = BGR2GRAY(src);
cv::imshow("gray", gray);
Sobelfilter(gray);
cv::waitKey(0);
cv::destroyAllWindows();
return 0;
}
实现效果
1、原图:
2、灰度图:
3、水平方向
4、垂直方向
5、水平方向和垂直方向结合