feat: init OpenCV C++ training course with 8 experiments and detailed tutorial

This commit is contained in:
TuxMonkey
2026-07-27 14:23:06 +08:00
commit 5fdfd9791a
18 changed files with 3843 additions and 0 deletions

11
exp4_canny/CMakeLists.txt Normal file
View File

@@ -0,0 +1,11 @@
# ============================================================
# 实验 4:Canny 边缘检测 - CMake 构建配置文件
# ============================================================
cmake_minimum_required(VERSION 3.10)
project(exp4_canny)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(OpenCV REQUIRED)
add_executable(exp4 main.cpp)
target_link_libraries(exp4 ${OpenCV_LIBS})
include_directories(${OpenCV_INCLUDE_DIRS})

145
exp4_canny/main.cpp Normal file
View 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;
}