openCV3.0 C++ 学习笔记补充(自用 代码+注释)---持续更新 四(91-)

环境:OpenCV3.2.0 + VS2017

91、合并Y方向重叠的轮廓

以轮廓的最小垂直外接矩形框的y为依据,合并y重叠的轮廓。

数学逻辑:几何合并的数学表达

  • 坐标系统:假设矩形由左上角坐标(x, y)和宽高(width, height)定义。
  • 合并公式
    • 合并后左上角:(min(x1, x2), min(y1, y2))
    • 合并后右下角:(max(x1+w1, x2+w2), max(y1+h1, y2+h2))
    • 合并后尺寸:width = max_x - min_xheight = max_y - min_y
  • 91.1:简单直接,但对于大量轮廓可能效率较低(O(n²))

  • 91.2:使用并查集算法,效率更高

	std::vector<std::vector<cv::Point>> contour_end;//最终轮廓std::vector<cv::Rect> bound_Rect_end;if (1) { // 合并 Y方向重叠的轮廓cv::Mat visual_bR = cv::Mat::zeros(480, 640, CV_8UC3);imgOriginal.copyTo(visual_bR);cv::RNG rng(12345);std::vector<std::vector<cv::Point>> mergedContours;//mergedContours = mergeOverlappingContoursY(contour_retained);mergedContours = mergeOverlappingContoursYOptimized(contour_retained); // 优化版本if (debug) cout << "Y方向重叠合并后 mergedContours.size() = " << mergedContours.size() << endl;if (debug) {for (size_t i = 0; i < mergedContours.size(); i++) {cv::Scalar color = cv::Scalar(rng.uniform(0, 256), rng.uniform(0, 256), rng.uniform(0, 256));cv::drawContours(visual_bR, mergedContours, i, color, 2);}if (debug) cv::imshow("Merged Contours", visual_bR);}int remainNum = 0;//剩余的轮廓数std::vector<cv::Rect> boundRect;for (int i = 0; i < mergedContours.size(); i++){std::vector<cv::Point> curContours = mergedContours.at(i);if (curContours.size() < 40) continue;boundRect.push_back(cv::boundingRect(curContours));if (debug) cv::rectangle(visual_bR, boundRect[boundRect.size() - 1].tl(), boundRect[boundRect.size() - 1].br(), cv::Scalar(0, 255, 0), 1);if (debug) cv::putText(visual_bR, std::to_string(boundRect.size() - 1), boundRect[boundRect.size() - 1].tl(), cv::FONT_HERSHEY_COMPLEX, 0.45, cv::Scalar(255, 135, 160), 1);if (debug) cv::putText(visual_bR, std::to_string(boundRect.size() - 1), boundRect[boundRect.size() - 1].br(), cv::FONT_HERSHEY_COMPLEX, 0.45, cv::Scalar(255, 135, 160), 1);//if (debug) cv::drawContours(visual_bR, curContours, i, cv::Scalar(255, 135, 160), -1, CV_AA);if (boundRect.size() - 1 >= 0) {cv::Rect curBR = boundRect.at(boundRect.size() - 1);double whRatio = curBR.width*1.0 / curBR.height;//宽高比double full = curContours.size()*1.0 / (curBR.width*curBR.height);if (debug) std::cout << "--- curBR_" << boundRect.size() - 1 << curBR << whRatio << "  \tfull=" << full << std::endl;//if (whRatio > 1) continue;//宽高比不满足要求的直接 continue//if (curBR.width > imgOriginal.cols / 3) continue;//if (curBR.height > imgOriginal.rows / 3) continue;if (curBR.width  < 150) continue;//if (curBR.height > 150) continue;}cv::RotatedRect curMinRect = cv::minAreaRect(curContours);float longerSide = curMinRect.size.width > curMinRect.size.height ? curMinRect.size.width : curMinRect.size.height;float shorterSide = curMinRect.size.width < curMinRect.size.height ? curMinRect.size.width : curMinRect.size.height;double lsRatio = longerSide * 1.0 / shorterSide;//长宽比if (debug) {cv::Point2f vertices[4];curMinRect.points(vertices);for (int i = 0; i < 4; i++)line(visual_bR, vertices[i], vertices[(i + 1) % 4], cv::Scalar(80, 175, 210), 2);if (debug) cv::putText(visual_bR, std::to_string(i), vertices[1], cv::FONT_HERSHEY_COMPLEX, 0.45, cv::Scalar(80, 175, 210), 1);if (debug) cv::putText(visual_bR, std::to_string(i), vertices[3], cv::FONT_HERSHEY_COMPLEX, 0.45, cv::Scalar(80, 175, 210), 1);cv::RotatedRect curMR = curMinRect;if (debug) std::cout << i << "  curMR.angle=" << curMR.angle << " \t, curMR.center=" << curMR.center << "\t, curMR.size=" << curMR.size << lsRatio << std::endl;}if (lsRatio < 6.4) continue;//if (lsRatio > 2.5) continue;remainNum++;cv::Scalar color = cv::Scalar(rng.uniform(0, 256), rng.uniform(0, 256), rng.uniform(0, 256));cv::drawContours(visual_bR, mergedContours, i, color, -1, CV_AA);//cv::drawContours(visual_bR, mergedContours, i, cv::Scalar(255, 255, 255), -1, CV_AA); //用全黑色填充contour_end.push_back(curContours);bound_Rect_end.push_back(boundRect[boundRect.size() - 1]);}if (debug) cv::putText(visual_bR, std::to_string(remainNum), cv::Point(visual_bR.cols / 3, visual_bR.rows / 3), cv::FONT_HERSHEY_COMPLEX, 1.45, cv::Scalar(80, 75, 210), 1);if (debug) cv::imshow("Merged Contours", visual_bR);//visual_bR.copyTo(canvas);}

91.1 直接根据最小垂直外接矩形框的y合并 mergeOverlappingContoursY

/*
合并 Y方向上有重叠的轮廓
contours 输入的轮廓集合
返回:合并后的轮廓集合*/
std::vector<std::vector<cv::Point>> mergeOverlappingContoursY(const std::vector<std::vector<cv::Point>>& contours) 
{if (contours.empty()) return contours;// 存储轮廓及其边界矩形std::vector<std::pair<cv::Rect, std::vector<cv::Point>>> contourRects;for (const auto& contour : contours) {if (!contour.empty()) {contourRects.emplace_back(cv::boundingRect(contour), contour);}}// 按 Y 坐标排序std::sort(contourRects.begin(), contourRects.end(),[](const std::pair<cv::Rect, std::vector<cv::Point>>& a,const std::pair<cv::Rect, std::vector<cv::Point>>& b) {return a.first.y < b.first.y;});// 合并重叠的轮廓std::vector<std::vector<cv::Point>> mergedContours;for (size_t i = 0; i < contourRects.size(); ++i) {cv::Rect currentRect = contourRects[i].first;std::vector<cv::Point> currentContour = contourRects[i].second;// 检查是否已经处理过if (currentRect.width == 0 && currentRect.height == 0) {continue;}// 尝试合并与当前轮廓在 Y 方向上有重叠的轮廓for (size_t j = i + 1; j < contourRects.size(); ++j) {cv::Rect otherRect = contourRects[j].first;// 跳过已处理的轮廓if (otherRect.width == 0 && otherRect.height == 0) {continue;}// 检查 Y 方向是否有重叠bool yOverlap = (currentRect.y <= otherRect.y + otherRect.height) &&(currentRect.y + currentRect.height >= otherRect.y);if (yOverlap) {// 合并轮廓点currentContour.insert(currentContour.end(),contourRects[j].second.begin(),contourRects[j].second.end());//opencv中以重载运算符:从 按位或 变为 矩形合并currentRect |= otherRect; // 更新当前矩形contourRects[j].first = cv::Rect(0, 0, 0, 0); // 标记已处理}}// 将合并后的轮廓添加到结果中mergedContours.push_back(currentContour);}return mergedContours;
}

91.2 使用并查集合并 Y方向上有重叠的轮廓 mergeOverlappingContoursYOptimized

  1. 计算每个轮廓的边界矩形
  2. 使用并查集数据结构跟踪重叠的轮廓 (对轮廓下标做并查集)
  3. 根据并查集结果合并轮廓 (根据下标判断是否属同一集合)
  4. 返回合并后的轮廓
/*
使用并查集合并 Y方向上有重叠的轮廓
contours 输入的轮廓集合
返回:合并后的轮廓集合*/
std::vector<std::vector<cv::Point>> mergeOverlappingContoursYOptimized(const std::vector<std::vector<cv::Point>>& contours) 
{if (contours.empty()) return contours;// 存储轮廓及其边界矩形std::vector<cv::Rect> rects;for (const auto& contour : contours) {if (!contour.empty()) {rects.push_back(cv::boundingRect(contour));}}// 初始化并查集std::vector<int> parent(rects.size());for (int i = 0; i < parent.size(); ++i) {parent[i] = i;}// 查找函数auto find = [&](int x) {while (parent[x] != x) {parent[x] = parent[parent[x]];  // 路径压缩x = parent[x];}return x;};// 合并函数auto unite = [&](int x, int y) {int rootX = find(x);int rootY = find(y);if (rootX != rootY) {parent[rootY] = rootX;}};// 检查重叠并合并for (size_t i = 0; i < rects.size(); ++i) {for (size_t j = i + 1; j < rects.size(); ++j) {// 检查 Y 方向是否有重叠bool yOverlap = (rects[i].y <= rects[j].y + rects[j].height) &&(rects[i].y + rects[i].height >= rects[j].y);if (yOverlap) {unite(i, j);}}}// 根据并查集结果合并轮廓std::vector<std::vector<cv::Point>> mergedContours;std::vector<bool> processed(rects.size(), false); //标记已处理的轮廓for (size_t i = 0; i < rects.size(); ++i) {if (processed[i]) continue;int root = find(i);std::vector<cv::Point> mergedContour;for (size_t j = i; j < rects.size(); ++j) {if (find(j) == root) { //同一集合的轮廓mergedContour.insert(mergedContour.end(),contours[j].begin(),contours[j].end());processed[j] = true; //标记为已处理}}mergedContours.push_back(mergedContour);}return mergedContours;
}

拓展:重叠条件修改

【重叠y超一定比例才合并

// 计算重叠比例
float overlapHeight = std::min(currentRect.y + currentRect.height, otherRect.y + otherRect.height) - std::max(currentRect.y, otherRect.y);
float minHeight = std::min(currentRect.height, otherRect.height);
float overlapRatio = overlapHeight / minHeight;// 只有当重叠比例超过阈值时才合并
if (yOverlap && overlapRatio > 0.5) {// 合并轮廓
}

【X方向重叠条件

// 添加X方向重叠条件
bool xOverlap = (rects[i].x <= rects[j].x + rects[j].width) &&(rects[i].x + rects[i].width >= rects[j].x);
	bool xOverlap = (currentRect.x <= otherRect.x + otherRect.width) &&(currentRect.x + currentRect.width >= otherRect.x);// X方向重叠

【重叠面积阈值

// 添加重叠面积阈值
float overlapArea = calculateOverlapArea(rects[i], rects[j]);
float minOverlapRatio = 0.2; // 20%重叠
if (yOverlap && overlapArea / std::min(rects[i].area(), rects[j].area()) > minOverlapRatio) {unite(i, j);
}

92、

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/922171.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/922171.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

numpy数组的升维和降维的方法集锦

为适配计算包对numpy数组的维度要求&#xff0c;对numpy数组进行升维或降维转化&#xff0c;是非常常见的操作。这里尝试通过多种方式对numpy数组进行升维或降维。1 数组升维1.1 np.expand_dims在0维升维&#xff0c;示例如下a np.array([1,2,3,4,5]) np.expand_dims(a, axis0…

介绍 Python Elasticsearch Client 的 ES|QL 查询构建器

作者&#xff1a;来自 Elastic Miguel Grinberg 学习如何使用 ES|QL 查询构建器&#xff0c;这是一个新的 Python Elasticsearch client 功能&#xff0c;可以更轻松地使用熟悉的 Python 语法构建 ES|QL 查询。 想要获得 Elastic 认证吗&#xff1f;快来了解下一期 Elasticsear…

三坐标测量仪:高精度测量内径检测手段及其实际运用

在工业制造领域中&#xff0c;内径尺寸的精准度直接关系到产品的装配性能、运行稳定性乃至使用寿命。传统检测方法如卡尺、内径千分尺等难以满足高精度、复杂结构件的需求。三坐标测量仪技术的出现&#xff0c;打破了这一困境&#xff0c;成为当前工业领域实现高精度内径检测的…

DIPMARK:一种隐蔽、高效且具备鲁棒性的大语言模型水印技术

摘要水印技术为通过在数据中嵌入隐蔽信息来保障数据安全提供了一种很有前景的方法。该领域的一个首要挑战在于&#xff0c;在水印嵌入过程中保持原始数据的分布。我们的研究拓展并优化了现有的水印框架&#xff0c;着重强调了保持分布&#xff08;DiP&#xff09;水印的重要性。…

IMU传感器价格与高精度惯性导航系统供应商分析

本段将对IMU传感器价格及高精度惯性导航系统的市场情况进行概览。IMU传感器作为惯性导航的重要组成部分&#xff0c;其价格水平受到技术、需求和供应商竞争等多重因素的影响。随着无人机、自动驾驶车辆等新兴应用场景的兴起&#xff0c;IMU传感器的市场需求逐渐攀升。这不仅带动…

3-9〔OSCP ◈ 研记〕❘ WEB应用攻击▸利用REST API提权

郑重声明&#xff1a; 本文所有安全知识与技术&#xff0c;仅用于探讨、研究及学习&#xff0c;严禁用于违反国家法律法规的非法活动。对于因不当使用相关内容造成的任何损失或法律责任&#xff0c;本人不承担任何责任。 如需转载&#xff0c;请注明出处且不得用于商业盈利。 …

UE5 基础应用 —— 07 - 角色蓝图 简单使用

目录 一、角色蓝图 1.1 Pawn / Character 1.2 角色基类 1.3 角色基类设置 1.3.1 基础设置 1.3.2 角色移动和相机旋转 1.3.3 角色移动 —— 锁定视角 1.3.4 角色跳跃 1.4 角色派生类设置 1.4.1 添加动画蓝图 一、角色蓝图 1.1 Pawn / Character Pawn / Character 有什…

流畅的Python(二) 丰富的序列

流畅的Python 第二章&#xff1a;丰富的序列 摘要&#xff1a;在日常Python开发中&#xff0c;我们频繁与各种数据结构打交道&#xff0c;其中序列类型&#xff08;如列表、元组、字符串&#xff09;是基石。然而&#xff0c;你是否曾因对它们理解不深&#xff0c;而在性能优化…

嵌入式 - ARM6

一、按键1. 初始化key.c手册C32 - IOMUXC1. 复用功能配置IOMUXC_SW_MUX_CTL_PAD_UART1_CTS_B: 低四位&#xff08;0101&#xff09; IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0);SION(信号监控)1: 0 //0 DISABLED — Input Path is determined by functionality MUX_…

菊水PBZ电源在蓄电池充放电测试中的应用探讨

通过高速双极性电源PBZ系列进行蓄电池恒流&#xff0c;恒压充电的方法 对于仪器厂商来说&#xff0c;要求“请按照使用说明书使用”是产品的使用方针&#xff0c;或者说是正确用法。但是&#xff0c;作为具有代表性的通用产品&#xff0c;直流电源的实际使用方法可谓五花八门&…

Zephyr嵌入式实时操作系统安装配置

Zephyr简介 Zephyr 是一款由 Linux 基金会 托管的开源实时操作系统(RTOS),专为资源受限的嵌入式设备(从微控制器到小型边缘计算节点)设计,广泛应用于物联网(IoT)、工业自动化、消费电子、医疗设备、汽车电子等领域。其核心优势在于轻量级、高可配置性和对多架构硬件的广…

Linux系统 SELinux 安全管理与故障排查

一、SELinux 安全上下文管理1. SELinux 简介SELinux&#xff08;Security-Enhanced Linux&#xff09;是 Linux 内核的强制访问控制&#xff08;MAC&#xff09;安全子系统&#xff0c;通过基于标签的访问控制实现细粒度权限管理&#xff0c;遵循最小权限原则。SELinux 有三种工…

解密完全二叉树顺序存储之堆结构

前言:各位老铁好&#xff0c;在前面博客中&#xff0c;笔者分享了有关二叉树的博客&#xff0c;在那篇博客中&#xff0c;笔者讲到了完全二叉树的存储结构中有两种存储方式&#xff0c;一种是顺序存储&#xff0c;一种是链式存储&#xff0c;链式存储笔者已经带各位老铁实现过了…

通过针刺!鹏辉能源移动电源电池革新之作 Secu 系列:不燃电解液加持,充电宝安全新选择

9月11日&#xff0c;鹏辉能源对外发布新一代移动电源高安全电池Secu系列。该产品通过采用不燃的电解液破解移动电源产品安全难题&#xff0c;直击当下移动电源安全事故频发的行业痛点&#xff0c;为移动电源行业带来更安全、更可靠的半固态电池解决方案。数字化时代&#xff0c…

软件定义汽车(SDV)与区域电子电气架构(Zonal EEA)的技术革新

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 做到欲望极简,了解自己的真实欲望,不受外在潮流的影响,不盲从,不跟风。把自己的精力全部用在自己。一是去掉多余,凡事找规律,基础是诚信;二是…

在 Docker Compose 中解决文件权限不足的问题

在使用 Docker 和 Docker Compose 构建应用时&#xff0c;由于容器中的文件权限不足而导致某些容器可能无法访问宿主机上的文件&#xff0c;或者容器内的文件系统无法正确读取或写入文件。问题描述在我的项目中&#xff0c;我使用 Docker Compose 来启动多个服务&#xff0c;并…

认知语义学对人工智能自然语言处理的深层语义分析:理论启示与实践路径

摘要随着人工智能&#xff08;AI&#xff09;技术的飞速发展&#xff0c;自然语言处理&#xff08;NLP&#xff09;已成为其核心驱动力之一。然而&#xff0c;尽管以大型语言模型&#xff08;LLMs&#xff09;为代表的现代NLP系统在处理语言任务上取得了前所未有的成功&#xf…

React19 中的交互操作

需要安装的库 antd-mobile、use-immer在App.jsx 中引入组件 Actionimport "./App.css" import Action from "./pages/action" function App() {return (<><Action></Action></>) }export default Appaction.jsx 组件import LearnI…

仓颉编程语言青少年基础教程:数组类型

仓颉编程语言青少年基础教程&#xff1a;数组类型 数组本质上是有序、同类型数据的集合容器&#xff0c;其核心作用是高效组织、访问和处理批量数据&#xff0c;同时结合语言特性&#xff0c;为开发者提供简洁、高性能的数据管理方式。例如&#xff1a; main() { let v1: …

C++微基础蓝桥杯之旅9.9-9.12

这里主要还是强制类型转换的使用//打印字符ASCII码值 //输入一个除空格以外的可见字符 //输出其ASCII值--十进制整数 #include <iostream> using namespace std;int main() {char ch;cin >> ch;//字符cout << (int)ch << endl; return 0; }//打印字符…