本实验为计算机科学与技术学院计算机专业大四上限选课,2023-2024-1年度课程实验,较以往实验内容发生较大变化
本实验使用vs2019,c++语言,需要提前安装opencv,具体方法请自行搜索。
•
实现如下图所示的边缘检测基本流程:
–
采用
cv::Sobel
函数计算边缘响应
–
对边缘响应进行非极大值抑制获得边缘
mask
,并和
cv::Canny
函数的结果进行对比,理解高低阈值的作用。
#include <opencv2/opencv.hpp>
#include <iostream>
#include <cmath>
using namespace cv;
using namespace std;
Mat img;
Mat pic1, pic2;
void customCanny(Mat& s, Mat& output, double ht, double lt)
{
Mat gray = Mat::zeros(s.size(), CV_8UC1);//gray用于存储灰度图像
Mat new_gray = Mat::zeros(gray.size(), gray.type());
output = Mat::zeros(gray.size(), gray.type());
GaussianBlur(s, gray, cv::Size(5, 5), 0, 0);
cvtColor(gray, gray, COLOR_RGB2GRAY);
Mat gradientX, gradientY;//gradientX和gradientY用于存储x横向和纵向的梯度
Sobel(gray, gradientX, CV_64F, 1, 0);
Sobel(gray, gradientY, CV_64F, 0, 1);
Mat gradientD = Mat::zeros(gradientX.size(), gradientX.type());//gradientD用于存储梯度的幅值
Mat gradientTAN = Mat::zeros(gradientX.size(), gradientX.type());//gradientTAN用于存储梯度的方向
for (int x = 0; x < gray.rows; x++)//遍历,计算梯度幅值和方向
{
for (int y = 0; y < gray.cols; y++)
{
gradientD.at<double>(x, y) = sqrt(pow(gradientX.at<double>(x, y), 2.0) + pow(gradientY.at<double>(x, y), 2.0));
gradientTAN.at<double>(x, y) = gradientY.at<double>(x, y) / gradientX.at<double>(x, y);
}
}
//非极大值抑制
for (int x = 0; x < new_gray.rows; x++)
{
for (int y = 0; y < new_gray.cols; y++)
{
double temp1 = gradientX.at<double>(x, y);
double temp2 = gradientY.at<double>(x, y);
if (temp1 == 0 && temp2 == 0)//无视梯度为0的情况
continue;
if (temp1 == 0 && temp2 != 0)
{//垂直边缘
if (x == 0)//如果是第一行,只对比他的下面的像素
if (gradientD.at<double>(x, y) >= gradientD.at<double>(x + 1, y))
new_gray.at<uchar>(x, y) = (int)gradientD.at<double>(x, y);
if (x == new_gray.rows - 1)//如果是最后一行,只对比他上面的像素
if (gradientD.at<double>(x, y) >= gradientD.at<double>(x - 1, y))
new_gray.at<uchar>(x, y) = (int)gradientD.at<double>(x, y);
if (x != 0 && x != new_gray.rows - 1)//中间的那么就对比上下
if ((gradientD.at<double>(x, y) >= gradientD.at<double>(x + 1, y)) && (gradientD.at<double>(x, y) >= gradientD.at<double>(x - 1, y)))
new_gray.at<uchar>(x, y) = (int)gradientD.at<double>(x, y);
}
if (temp1 != 0 && temp2 == 0)
{//水平边缘
if (y == 0)
if (gradientD.at<double>(x, y) >= gradientD.at<double>(x, y + 1))
new_gray.at<uchar>(x, y) = (int)gradientD.at<double>(x, y);
else if (y == new_gray.cols - 1)
if (gradientD.at<double>(x, y) >= gradientD.at<double>(x, y - 1))
new_gray.at<uchar>(x, y) = (int)gradientD.at<double>(x, y);
else
if ((gradientD.at<double>(x, y) >= gradientD.at<double>(x, y + 1)) && (gradientD.at<double>(x, y) >= gradientD.at<double>(x, y - 1)))
new_gray.at<uchar>(x, y) = (int)gradientD.at<double>(x, y);
}
if (temp1 != 0 && temp2 != 0)
{//斜边缘
double p1 = 0.0, p2 = 0.0;
if (gradientTAN.at<double>(x, y) > 0)
{//正斜率
//做两次线性插值,在梯度方向上进行平滑,以获得更加准确的边缘响应
p1 = gradientTAN.at<double>(x, y) * gradientD.at<double>(x - 1, y + 1) + (1 - gradientTAN.at<double>(x, y) * gradientD.at<double>(x, y + 1));//线性插值,利用上一行和下一列的梯度值
p2 = gradientTAN.at<double>(x, y) * gradientD.at<double>(x + 1, y - 1) + (1 - gradientTAN.at<double>(x, y) * gradientD.at<double>(x, y - 1)); //线性插值,利用下一行和上一列的梯度值
}
if (gradientTAN.at<double>(x, y) < 0)
{
p1 = gradientTAN.at<double>(x, y) * gradientD.at<double>(x - 1, y - 1) + (1 - gradientTAN.at<double>(x, y) * gradientD.at<double>(x, y - 1));
p2 = gradientTAN.at<double>(x, y) * gradientD.at<double>(x + 1, y + 1) + (1 - gradientTAN.at<double>(x, y) * gradientD.at<double>(x, y + 1));
}
if (gradientD.at<double>(x, y) >= p1 && gradientD.at<double>(x, y) >= p2)//如果当前像素点的梯度值大于两个插值,才会被认为是极大值
new_gray.at<uchar>(x, y) = (int)gradientD.at<double>(x, y);
}
}
}
// 使用高低阈值进行边缘检测
for (int x = 0; x < new_gray.rows; x++)
{
for (int y = 0; y < new_gray.cols; y++)
{
if (new_gray.at<uchar>(x, y) >= ht)// 如果像素值大于等于高阈值,保留为边缘
output.at<uchar>(x, y) = new_gray.at<uchar>(x, y);
else if (new_gray.at<uchar>(x, y) >= lt) // 如果像素值大于等于低阈值但小于高阈值
{//则该像素点周围有任一像素值大于高阈值,就保留为边缘
if ((y != 0) && (new_gray.at<uchar>(x, y - 1) >= ht))
{
output.at<uchar>(x, y) = new_gray.at<uchar>(x, y);
continue;
}
if ((y != 0) && ((x != 0)) && (new_gray.at<uchar>(x - 1, y - 1) >= ht))
{
output.at<uchar>(x, y) = new_gray.at<uchar>(x, y);
continue;
}
if ((x != 0) && (new_gray.at<uchar>(x - 1, y) >= ht))
{
output.at<uchar>(x, y) = new_gray.at<uchar>(x, y);
continue;
}
if ((y != new_gray.cols - 1) && ((x != 0)) && (new_gray.at<uchar>(x - 1, y + 1) >= ht))
{
output.at<uchar>(x, y) = new_gray.at<uchar>(x, y);
continue;
}
if ((y != new_gray.cols - 1) && (new_gray.at<uchar>(x, y + 1) >= ht))
{
output.at<uchar>(x, y) = new_gray.at<uchar>(x, y);
continue;
}
if ((y != new_gray.cols - 1) && ((x != new_gray.rows - 1)) && (new_gray.at<uchar>(x + 1, y + 1) >= ht))
{
output.at<uchar>(x, y) = new_gray.at<uchar>(x, y);
continue;
}
if ((x != new_gray.rows - 1) && (new_gray.at<uchar>(x + 1, y) >= ht))
{
output.at<uchar>(x, y) = new_gray.at<uchar>(x, y);
continue;
}
if ((y != 0) && ((x != new_gray.rows - 1)) && (new_gray.at<uchar>(x + 1, y - 1) >= ht))
{
output.at<uchar>(x, y) = new_gray.at<uchar>(x, y);
continue;
}
}
}
}
}
int main() {
// 读取图像
img = imread("C:/Users/13441/Desktop/数字图像/bb.png");
customCanny(img, pic1, 165, 30);
Canny(img, pic2, 450, 280);
cvtColor(img, img, COLOR_RGB2GRAY);
imshow("源图像", img);
imshow("examImage", pic1);
imshow("cv::Canny", pic2);
waitKey(0);
return 0;
}
源图像:
输出图像:
cv::canny图像: