图像的边缘定义为两个强度明显不同的区域之间的过渡,图像的梯度函数即图像灰度变化的速率将在这些过渡边界上存在最大值。如果一个点位于边缘点上,那么它的灰度值会出现阶跃性的变化,对应于一阶导数的极值点、二阶导数的过零点,如图所示。
由图可知,一阶导数的极值点与二阶导数的过零点可用来检测边缘。另外,一阶导数与二阶导数对噪声非常敏感,尤其是二阶导数。因此,在进行边缘检测之前,需充分考虑图像的平滑,以减少噪声的影响。
1)一阶微分边缘检测
图像灰度函数
f
(
x
,
y
)
在点
(
x
,
y
)
的梯度(即一阶导数)是一个具有大小和方向的矢量,即
∇
f
(
x
,
y
)
=
[
G
x
,
G
y
]
T
=
[
∂
f
/
∂
x
,
∂
f
/
∂
y
]
T
∇
f
(
x
,
y
)
的幅值为
m
a
g
(
∇
f
)
=
√
(
(
∂
f
/
∂
x
)
2
+
(
∂
f
/
∂
y
)
2
)
2)二阶微分边缘检测
通过找图像灰度二阶导数的零交叉点可以确定边缘。图像灰度函数f(x,y)在点f(x,y)在点(x,y)的二阶微分为
G
(
x
,
y
)
=
∇
2
F
(
x
,
y
)
式中,由二阶导数的定义可得拉普拉斯算子为
∇
2
=
∂
2
/
(
∂
x
2
)
+
∂
2
/
(
∂
y
2
)
在离散区域中,对连续的拉普拉斯算子最简单的近似运算是计算斜面沿每一个轴线的差值:
G
(
x
,
y
)
=
[
F
(
x
,
y
)
−
F
(
x
,
y
−
1
)
]
−
[
F
(
x
,
y
+
1
)
−
F
(
x
,
y
)
]
=
[
F
(
x
,
y
)
−
F
(
x
,
y
+
1
)
]
−
[
F
(
x
−
1
,
y
)
−
F
(
x
,
y
)
]
该四邻域的拉普拉斯算子可以通过卷积来生成:
G
(
x
,
y
)
=
F
(
x
,
y
)
∗
H
(
x
,
y
)
式中
增益标准化的四邻域拉普拉斯算子脉冲响应的定义式为
#include "opencv2/opencv.hpp"
#include <iostream>
using namespace std;
using namespace cv;
void Gaussian(const Mat &inputImg, Mat &outputImg, double sigma) {
//计算窗口大小
int windows = ((int)(6 * sigma - 1)) / 2 * 2 + 1;
double *value = new double[windows];
double sum = 0;
//计算卷积核
int left = -windows / 2, right = windows / 2 + 1, border = windows / 2;
for (int i = left; i < right; i++){
value[i + border] = pow(2.718, -(i * i) / (2 * sigma * sigma)) / (sqrt(2 * 3.1415926) * sigma);
sum += value[i + border];
}
for (int i = left; i < right; i++){
value[i + border] = value[i + border] / sum;
cout << value[i + border] << endl;
}
//存储中间结果图像
Mat tempImg(inputImg.rows + border * 2, inputImg.cols + border * 2, CV_8UC1);
//填充图像
Mat fillImg(inputImg.rows + border * 2, inputImg.cols + border * 2, CV_8UC1);
for (int i = border; i < inputImg.rows + border; i++) {
for (int j = border; j < inputImg.cols + border; j++) {
fillImg.at<uchar>(i, j) = inputImg.at<uchar>((i-border) % (inputImg.rows - 1),(j-border) % (inputImg.cols - 1));
}
}
//填充图像上方
for (int i = 0; i < border; i++) {
for (int j = 0; j < inputImg.cols + border * 2; j++) {
fillImg.at<uchar>(i%fillImg.rows, j%fillImg.cols) = inputImg.at<uchar>(i%(inputImg.rows-1), j%(inputImg.cols-1));
}
}
//填充图像下放
for (int i = inputImg.rows+border; i < inputImg.rows + border * 2; i++) {
for (int j = 0; j < inputImg.cols + border * 2; j++) {
fillImg.at<uchar>(i%fillImg.rows, j%fillImg.cols) = inputImg.at<uchar>((i-border*2) % (inputImg.rows - 1), j % (inputImg.cols - 1));
}
}
//填充左方
for (int i = border; i < inputImg.rows + border; i++) {
for (int j = 0; j < border; j++) {
fillImg.at<Vec3b>(i%fillImg.rows, j%fillImg.cols)[0] = inputImg.at<Vec3b>((i-border) % (inputImg.rows - 1), j % (inputImg.cols - 1))[0];
fillImg.at<Vec3b>(i%fillImg.rows, j%fillImg.cols)[1] = inputImg.at<Vec3b>((i - border) % (inputImg.rows - 1), j % (inputImg.cols - 1))[1];
fillImg.at<Vec3b>(i%fillImg.rows, j%fillImg.cols)[2] = inputImg.at<Vec3b>((i - border) % (inputImg.rows - 1), j % (inputImg.cols - 1))[2];
}
}
//填充右方
for (int i = border; i < inputImg.rows + border; i++) {
for (int j = inputImg.cols+border; j < inputImg.cols + border*2; j++) {
fillImg.at<uchar>(i%fillImg.rows, j%fillImg.cols)= inputImg.at<uchar>((i - border) % (inputImg.rows - 1), (j-border*2) % (inputImg.cols - 1));
}
}
namedWindow("填充后", WINDOW_AUTOSIZE);
imshow("填充后", fillImg);
//对每行进行一维高斯滤波
for (int i = 0; i < tempImg.rows; i++){
for (int j = border; j < tempImg.cols - border; j++){
tempImg.at<uchar>(i, j)= 0;
for (int k = left; k < right; k++){
tempImg.at<uchar>(i, j) += (fillImg.at<uchar>(i, j + k) * value[k + border]);
}
}
}
//对结果的每列进行一维高斯滤波
for (int i = 0; i < outputImg.rows; i++){
for (int j = 0; j < outputImg.cols; j++){
outputImg.at<uchar>(i, j) = 0;
for (int k = left; k < right; k++){
outputImg.at<uchar>(i, j) += (tempImg.at<uchar>(i + k+border, j+border) * value[k + border]);
}
}
}
namedWindow("高斯滤波", WINDOW_AUTOSIZE);
imshow("高斯滤波", outputImg);
}
int main(int argc, char** argv){
Mat pic=imread("d:\\test1.jpg",0);
imshow("testpic", pic);
int lap[3] = { -1, 4, 1 };
Mat outGaussian(pic.rows, pic.cols, CV_8UC1);
Mat out(pic.rows, pic.cols, CV_8UC1);
Mat temp(pic.rows, pic.cols, CV_8UC1);
for (int i = 0; i < pic.rows; i++){
for (int j = 0; j < pic.cols; j++){
outGaussian.at<uchar>(i, j) = pic.at<uchar>(i, j);
out.at<uchar>(i, j) = pic.at<uchar>(i, j);
}
}
for (int i = 1; i < pic.rows - 1; i++){
for (int j = 1; j < pic.cols - 1; j++){
out.at<uchar>(i, j) = (pic.at<uchar>(i, j) * 4 - pic.at<uchar>(i - 1, j) - pic.at<uchar>(i + 1, j) - pic.at<uchar>(i, j - 1) - pic.at<uchar>(i, j + 1)) / 4;
}
}
Gaussian(pic, temp,2);
for (int i = 1; i < pic.rows-1; i++){
for (int j = 1; j < pic.cols-1; j++){
outGaussian.at<uchar>(i, j) = (temp.at<uchar>(i, j) * 4 - temp.at<uchar>(i - 1, j) - temp.at<uchar>(i + 1, j) - temp.at<uchar>(i, j - 1) - temp.at<uchar>(i, j + 1)) / 4;
}
}
imshow("out", out);
imshow("outGaussian", outGaussian);
waitKey();
return 0;
}