feat: init OpenCV C++ training course with 8 experiments and detailed tutorial
This commit is contained in:
145
exp4_canny/main.cpp
Normal file
145
exp4_canny/main.cpp
Normal file
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* ============================================================
|
||||
* 实验 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 <opencv2/opencv.hpp>
|
||||
#include <iostream>
|
||||
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user