/* * ============================================================ * 实验 4:Canny 边缘检测 * 知识点:cv::Canny * ============================================================ * * 【实验目标】 * 1. 理解什么是"边缘"——图像中亮度急剧变化的地方 * 2. 掌握 Canny 边缘检测算法的原理和参数 * 3. 观察不同阈值对边缘检测结果的影响 * * 【生活化比喻】 * 边缘检测就像用铅笔"描边": * - 给你一张彩色照片,让你把照片中所有物体的轮廓描出来 * - Canny 算法就是一个"自动描边机器人" * - threshold1 像是"最低标准":低于这个的统统不是边缘 * - threshold2 像是"最高标准":高于这个的绝对是边缘 * - 介于两者之间的,看它是否和已确认的边缘连着(连着的算,孤立的忽略) * * 这就是所谓的"迟滞阈值"(Hysteresis Threshold): * - 高于高阈值 → 肯定是边缘(强边缘) * - 低于低阈值 → 肯定不是边缘(直接丢弃) * - 介于中间 → 如果和强边缘相邻,就算是边缘;否则丢弃 * - 这就像"朋友的朋友也是朋友"——只要你能通过边缘连到确认真边缘,你就是边缘 * * 【Canny 算法的五个步骤(了解即可)】 * 1. 高斯滤波:去除噪声(因为噪声会被误检测为边缘) * 2. 计算梯度:找到亮度变化最大的方向和大小 * 3. 非极大值抑制:只保留梯度方向上最大的那个像素(细化边缘) * 4. 双阈值检测:用两个阈值筛选强弱边缘 * 5. 边缘连接:通过滞后阈值连接断开的边缘 * * 【函数详解】 * cv::Canny(src, dst, threshold1, threshold2, apertureSize, L2gradient) * * src :输入图像(必须是 8 位单通道灰度图!) * dst :输出图像(二值图,边缘为白色 255,非边缘为黑色 0) * threshold1 :低阈值(用于弱边缘判断) * threshold2 :高阈值(用于强边缘判断) * 推荐比例:threshold2 ≈ 2× threshold1 或 3× threshold1 * apertureSize :Sobel 算子的孔径大小(默认 3,一般不用改) * L2gradient :是否使用更精确的 L2 范数计算梯度(默认 false) */ #include #include int main() { // ========================================== // 第一部分:生成测试图片 // ========================================== std::cout << "[步骤 1] 正在生成测试图片(包含各种边缘结构)..." << std::endl; // 创建一个 400×500 的白色画布 cv::Mat image(400, 500, CV_8UC3, cv::Scalar(255, 255, 255)); // 画各种几何图形 —— 这些都有清晰的边缘 cv::circle(image, cv::Point(100, 100), 60, cv::Scalar(50, 50, 50), -1); // 深灰实心圆 cv::rectangle(image, cv::Point(220, 40), cv::Point(420, 160), cv::Scalar(100, 100, 100), -1); // 灰实心矩形 cv::rectangle(image, cv::Point(300, 60), cv::Point(360, 140), cv::Scalar(200, 200, 200), -1); // 浅灰小矩形(内嵌) // 画一些粗细不等的线条 cv::line(image, cv::Point(20, 200), cv::Point(480, 200), cv::Scalar(0, 0, 0), 2); // 细横线 cv::line(image, cv::Point(20, 250), cv::Point(480, 250), cv::Scalar(0, 0, 0), 8); // 粗横线 cv::line(image, cv::Point(250, 200), cv::Point(250, 380), cv::Scalar(0, 0, 0), 2); // 细竖线 cv::line(image, cv::Point(100, 300), cv::Point(400, 380), cv::Scalar(50, 50, 50), 4); // 斜线 // 画一些文字 cv::putText(image, "Canny", cv::Point(150, 370), cv::FONT_HERSHEY_SIMPLEX, 1.5, cv::Scalar(0, 0, 0), 3); cv::imwrite("original.jpg", image); std::cout << " -> 原始图片已保存为 original.jpg" << std::endl; // ========================================== // 第二部分:转为灰度图(Canny 输入必须是灰度图!) // ========================================== std::cout << "[步骤 2] 转换为灰度图..." << std::endl; cv::Mat gray; cv::cvtColor(image, gray, cv::COLOR_BGR2GRAY); cv::imwrite("gray.jpg", gray); std::cout << " -> 灰度图已保存为 gray.jpg" << std::endl; // ========================================== // 第三部分:Canny 边缘检测 —— 不同阈值对比 // ========================================== // --- Canny 场景 1:低阈值 (50, 150) --- // threshold1=50 意味着:强度大于 50 的可能是弱边缘 // threshold2=150 意味着:强度大于 150 的绝对是强边缘 // 由于两个阈值都相对较低,会检测出很多细节(包括一些纹理和噪声) std::cout << "[步骤 3] Canny 边缘检测 (阈值 50 / 150)..." << std::endl; cv::Mat edges_low; cv::Canny(gray, edges_low, 50, 150); cv::imwrite("edges_low.jpg", edges_low); std::cout << " -> 已保存为 edges_low.jpg(低阈值,细节丰富)" << std::endl; // --- Canny 场景 2:中等阈值 (100, 200) --- // 这是比较常用的阈值范围,能平衡"检测到足够边缘"和"抑制噪声" std::cout << "[步骤 4] Canny 边缘检测 (阈值 100 / 200)..." << std::endl; cv::Mat edges_mid; cv::Canny(gray, edges_mid, 100, 200); cv::imwrite("edges_mid.jpg", edges_mid); std::cout << " -> 已保存为 edges_mid.jpg(中等阈值,适中结果)" << std::endl; // --- Canny 场景 3:高阈值 (200, 400) --- // 两个阈值都很高,只有灰度变化非常剧烈的地方才会被检测为边缘 // 结果:只保留最强的边缘,细节大量丢失 std::cout << "[步骤 5] Canny 边缘检测 (阈值 200 / 400)..." << std::endl; cv::Mat edges_high; cv::Canny(gray, edges_high, 200, 400); cv::imwrite("edges_high.jpg", edges_high); std::cout << " -> 已保存为 edges_high.jpg(高阈值,只保留强边缘)" << std::endl; // --- Canny 场景 4:先模糊再做边缘检测 --- // 这是实际应用中最常见的做法:先高斯模糊去噪,再做 Canny // 对比 edges_low 的结果,你会发现先模糊后边缘更"干净" std::cout << "[步骤 6] 先高斯模糊,再 Canny 边缘检测..." << std::endl; cv::Mat blurred; cv::GaussianBlur(gray, blurred, cv::Size(5, 5), 0); cv::Mat edges_blurred; cv::Canny(blurred, edges_blurred, 50, 150); cv::imwrite("blurred.jpg", blurred); cv::imwrite("edges_after_blur.jpg", edges_blurred); std::cout << " -> 已保存为 blurred.jpg(高斯模糊后)" << std::endl; std::cout << " -> 已保存为 edges_after_blur.jpg(模糊后再做 Canny,边缘更干净)" << std::endl; // 总结 std::cout << std::endl; std::cout << "===== 实验 4 完成 =====" << std::endl; std::cout << "你学会了:" << std::endl; std::cout << " 1. Canny 是一种经典的边缘检测算法" << std::endl; std::cout << " 2. threshold1 和 threshold2 控制检测灵敏度" << std::endl; std::cout << " 3. 输入必须是灰度图(单通道!)" << std::endl; std::cout << " 4. 先模糊 + 再 Canny = 更干净的边缘" << std::endl; return 0; }