Files
vision-training-2026-opencvcpp/exp6_morphology/main.cpp

183 lines
8.6 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* ============================================================
* 实验 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;
}