山东大学数字图像处理实验(八) 计算机学院 图像边缘检测

本实验为计算机科学与技术学院计算机专业大四上限选课,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图像:

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值