feat: init OpenCV C++ training course with 8 experiments and detailed tutorial
This commit is contained in:
9
exp6_morphology/CMakeLists.txt
Normal file
9
exp6_morphology/CMakeLists.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
# 实验 6:形态学操作:腐蚀与膨胀 - CMake 构建配置
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
project(exp6_morphology)
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
find_package(OpenCV REQUIRED)
|
||||
add_executable(exp6 main.cpp)
|
||||
target_link_libraries(exp6 ${OpenCV_LIBS})
|
||||
include_directories(${OpenCV_INCLUDE_DIRS})
|
||||
182
exp6_morphology/main.cpp
Normal file
182
exp6_morphology/main.cpp
Normal file
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
* ============================================================
|
||||
* 实验 6:形态学操作 —— 腐蚀 (Erode) 与膨胀 (Dilate)
|
||||
* 知识点:cv::erode、cv::dilate、cv::getStructuringElement
|
||||
* ============================================================
|
||||
*
|
||||
* 【实验目标】
|
||||
* 1. 理解"腐蚀"和"膨胀"对二值图像的作用
|
||||
* 2. 了解"开运算"和"闭运算"的组合效果
|
||||
* 3. 掌握结构元素 (kernel) 的概念
|
||||
*
|
||||
* 【生活化比喻】
|
||||
* 腐蚀 (Erosion):
|
||||
* 想象沙滩上写着一些字,涨潮时海水冲上来,把字的边缘"吃掉"一圈
|
||||
* → 白色区域被"侵蚀",变小了、断开了、甚至消失了
|
||||
* → 作用:消除小白点噪声、断开粘连的物体
|
||||
*
|
||||
* 膨胀 (Dilation):
|
||||
* 想象给一栋房子外围再砌一圈围墙,房子"胖了"
|
||||
* → 白色区域被"扩张",变大了、连在一起了
|
||||
* → 作用:填充小黑洞、连接断裂的物体
|
||||
*
|
||||
* 开运算 (Opening) = 先腐蚀,再膨胀:
|
||||
* 想象洗菜时,先让虫子被水"冲掉"(腐蚀),
|
||||
* 然后蔬菜可能会缩一点,再"泡水"让它恢复原大小(膨胀)
|
||||
* → 去掉小白点噪声,同时保持物体大小基本不变
|
||||
*
|
||||
* 闭运算 (Closing) = 先膨胀,再腐蚀:
|
||||
* 想象一件有破洞的衣服,先"拉伸"布料让洞缩小(膨胀),
|
||||
* 再"熨平"恢复形状(腐蚀)
|
||||
* → 填充物体内部的黑色小洞,同时保持物体大小基本不变
|
||||
*
|
||||
* 结构元素 (Structuring Element / Kernel):
|
||||
* 腐蚀和膨胀不是乱"吃"乱"胀"的,而是用一个模板来做的
|
||||
* 这个模板就是"结构元素",它决定了"腐蚀/膨胀的形状和范围"
|
||||
* - 矩形结构元素 (MORPH_RECT):像盖章一样方方正正
|
||||
* - 椭圆形结构元素 (MORPH_ELLIPSE):圆形范围
|
||||
* - 十字形结构元素 (MORPH_CROSS):只影响上下左右
|
||||
*
|
||||
* 【函数详解】
|
||||
* cv::erode(src, dst, kernel, anchor, iterations)
|
||||
* cv::dilate(src, dst, kernel, anchor, iterations)
|
||||
*
|
||||
* src :输入图像(通常是二值图)
|
||||
* dst :输出图像
|
||||
* kernel :结构元素,用 cv::getStructuringElement() 创建
|
||||
* anchor :锚点位置,默认是核的中心 (-1,-1)
|
||||
* iterations :操作重复次数(相当于执行几次腐蚀/膨胀)
|
||||
*
|
||||
* cv::getStructuringElement(shape, ksize)
|
||||
* shape :MORPH_RECT(矩形)、MORPH_ELLIPSE(椭圆)、MORPH_CROSS(十字)
|
||||
* ksize :核大小,如 cv::Size(5,5)
|
||||
*/
|
||||
|
||||
#include <opencv2/opencv.hpp>
|
||||
#include <iostream>
|
||||
|
||||
int main()
|
||||
{
|
||||
// ==========================================
|
||||
// 第一部分:生成一张带噪声的二值测试图
|
||||
// ==========================================
|
||||
std::cout << "[步骤 1] 正在生成二值测试图片..." << std::endl;
|
||||
|
||||
// 创建黑色背景
|
||||
cv::Mat binary(300, 500, CV_8UC1, cv::Scalar(0));
|
||||
|
||||
// 画一个大的白色矩形(作为"主体目标")
|
||||
cv::rectangle(binary, cv::Point(80, 60), cv::Point(420, 240),
|
||||
cv::Scalar(255), -1); // 白色填充矩形
|
||||
|
||||
// 在矩形内部打几个孔(黑色小圆,模拟"目标内部的空洞")
|
||||
cv::circle(binary, cv::Point(180, 150), 20, cv::Scalar(0), -1); // 内部孔洞 1
|
||||
cv::circle(binary, cv::Point(320, 150), 20, cv::Scalar(0), -1); // 内部孔洞 2
|
||||
|
||||
// 在背景中撒一些白色噪点(模拟"背景中的噪声")
|
||||
// 这些小白点不是我们想要的目标,它们应该被腐蚀"消除"
|
||||
for (int i = 0; i < 50; i++)
|
||||
{
|
||||
int x = rand() % 500;
|
||||
int y = rand() % 300;
|
||||
cv::circle(binary, cv::Point(x, y), 3, cv::Scalar(255), -1);
|
||||
}
|
||||
|
||||
cv::imwrite("binary.jpg", binary);
|
||||
std::cout << " -> 原始二值图已保存为 binary.jpg" << std::endl;
|
||||
std::cout << " (白色矩形为主体,内部有小黑洞,背景有白色噪点)" << std::endl;
|
||||
|
||||
// ==========================================
|
||||
// 第二部分:创建结构元素
|
||||
// ==========================================
|
||||
// 结构元素的大小会影响腐蚀/膨胀的效果范围
|
||||
// 核越大,每次操作影响的范围越大
|
||||
// 这里使用 5×5 的矩形核作为演示
|
||||
cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(5, 5));
|
||||
// 你也试试换成椭圆形核:
|
||||
// cv::Mat kernel = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(5,5));
|
||||
|
||||
// ==========================================
|
||||
// 第三部分:腐蚀操作
|
||||
// ==========================================
|
||||
std::cout << "[步骤 2] 执行腐蚀操作 (Erosion)..." << std::endl;
|
||||
|
||||
cv::Mat eroded;
|
||||
cv::erode(binary, eroded, kernel);
|
||||
// 效果预期:
|
||||
// - 白色矩形整体"瘦"了一圈(边缘被腐蚀)
|
||||
// - 背景上的白色小噪点可能被完全消除(因为它们太小了)
|
||||
// - 矩形内部的黑洞变大了(因为黑洞周围的白色被腐蚀了)
|
||||
cv::imwrite("eroded.jpg", eroded);
|
||||
std::cout << " -> 已保存为 eroded.jpg(白色区域变小,噪点可能消失)" << std::endl;
|
||||
|
||||
// ==========================================
|
||||
// 第四部分:膨胀操作
|
||||
// ==========================================
|
||||
std::cout << "[步骤 3] 执行膨胀操作 (Dilation)..." << std::endl;
|
||||
|
||||
cv::Mat dilated;
|
||||
cv::dilate(binary, dilated, kernel);
|
||||
// 效果预期:
|
||||
// - 白色矩形整体"胖"了一圈(边缘被扩张)
|
||||
// - 背景上的白色小噪点也变大了
|
||||
// - 矩形内部的黑洞变小了(因为黑洞外围的白色膨胀进来)
|
||||
cv::imwrite("dilated.jpg", dilated);
|
||||
std::cout << " -> 已保存为 dilated.jpg(白色区域变大,内部黑洞变小)" << std::endl;
|
||||
|
||||
// ==========================================
|
||||
// 第五部分:开运算(先腐蚀再膨胀)
|
||||
// ==========================================
|
||||
std::cout << "[步骤 4] 执行开运算 (Opening = 腐蚀 + 膨胀)..." << std::endl;
|
||||
|
||||
// 开运算没有单独的函数,需要手动组合两个步骤
|
||||
cv::Mat opened;
|
||||
cv::erode(binary, opened, kernel); // 第一步:腐蚀
|
||||
cv::dilate(opened, opened, kernel); // 第二步:在原结果上膨胀
|
||||
// 效果预期:
|
||||
// - 白色的噪点被腐蚀消灭了 → 膨胀也无法复原 → 噪点彻底消失
|
||||
// - 矩形主体的边缘先瘦再胖,大小基本恢复原样
|
||||
// - 但是内部的黑洞变大了(因为腐蚀扩大了黑洞)
|
||||
cv::imwrite("opened.jpg", opened);
|
||||
std::cout << " -> 已保存为 opened.jpg(去掉了噪音,但内部黑洞变大了)" << std::endl;
|
||||
|
||||
// ==========================================
|
||||
// 第六部分:闭运算(先膨胀再腐蚀)
|
||||
// ==========================================
|
||||
std::cout << "[步骤 5] 执行闭运算 (Closing = 膨胀 + 腐蚀)..." << std::endl;
|
||||
|
||||
cv::Mat closed;
|
||||
cv::dilate(binary, closed, kernel); // 第一步:膨胀
|
||||
cv::erode(closed, closed, kernel); // 第二步:在原结果上腐蚀
|
||||
// 效果预期:
|
||||
// - 矩形内部的黑洞被膨胀填上了 → 腐蚀后也不会再出现 → 黑洞消失!
|
||||
// - 背景上的白色噪点膨胀变大 → 无法被腐蚀消除 → 噪点反而更大了
|
||||
// - 矩形主体的边缘先胖再瘦,大小基本恢复原样
|
||||
cv::imwrite("closed.jpg", closed);
|
||||
std::cout << " -> 已保存为 closed.jpg(填充了内部黑洞,但背景噪点变大了)" << std::endl;
|
||||
|
||||
// ==========================================
|
||||
// 第七部分:多次形态学操作
|
||||
// ==========================================
|
||||
std::cout << "[步骤 6] 执行多次腐蚀 (iterations=3)..." << std::endl;
|
||||
|
||||
cv::Mat eroded_multi;
|
||||
// iterations=3 表示连续执行 3 次腐蚀
|
||||
// 等价于手动写三次 cv::erode(..., eroded_multi, kernel)
|
||||
// 每腐蚀一次,白色区域就缩小一圈
|
||||
cv::erode(binary, eroded_multi, kernel, cv::Point(-1, -1), 3);
|
||||
cv::imwrite("eroded_x3.jpg", eroded_multi);
|
||||
std::cout << " -> 已保存为 eroded_x3.jpg(腐蚀 3 次,白色区域大幅缩小)" << std::endl;
|
||||
|
||||
// 总结
|
||||
std::cout << std::endl;
|
||||
std::cout << "===== 实验 6 完成 =====" << std::endl;
|
||||
std::cout << "你学会了:" << std::endl;
|
||||
std::cout << " 1. 腐蚀:白色区域缩小,可以去噪点" << std::endl;
|
||||
std::cout << " 2. 膨胀:白色区域扩大,可以填黑洞" << std::endl;
|
||||
std::cout << " 3. 开运算:去噪点但大体保持目标大小" << std::endl;
|
||||
std::cout << " 4. 闭运算:填空洞但大体保持目标大小" << std::endl;
|
||||
std::cout << " 5. 结构元素决定了操作的形状和范围" << std::endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user