/* * ============================================================ * 实验 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 #include 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; }