关于opencv的contourArea计算方法

cv::contourArea计算的轮廓面积并不等于轮廓点计数,原因是cv::contourArea是基于Green公式计算
在这里插入图片描述

老外的讨论 github

举一个直观的例子,图中有7个像素,橙色为轮廓点连线,按照contourArea的定义,轮廓的面积为橙色所包围的区域=3

在这里插入图片描述

以下代码基于opencv4.8.0

cv::contourArea源码位于.\sources\modules\imgproc\src\shapedescr.cpp

// area of a whole sequence
double cv::contourArea( InputArray _contour, bool oriented )
{CV_INSTRUMENT_REGION();Mat contour = _contour.getMat();int npoints = contour.checkVector(2);int depth = contour.depth();CV_Assert(npoints >= 0 && (depth == CV_32F || depth == CV_32S));if( npoints == 0 )return 0.;double a00 = 0;bool is_float = depth == CV_32F;const Point* ptsi = contour.ptr<Point>();const Point2f* ptsf = contour.ptr<Point2f>();Point2f prev = is_float ? ptsf[npoints-1] : Point2f((float)ptsi[npoints-1].x, (float)ptsi[npoints-1].y);for( int i = 0; i < npoints; i++ ){Point2f p = is_float ? ptsf[i] : Point2f((float)ptsi[i].x, (float)ptsi[i].y);a00 += (double)prev.x * p.y - (double)prev.y * p.x;prev = p;}a00 *= 0.5;if( !oriented )a00 = fabs(a00);return a00;
}

如果计算面积需要考虑轮廓点本身,可以通过cv::drawContoursI填充轮廓获得mask图像后统计非零点个数

cv::drawContours源码位于.\sources\modules\imgproc\src\drawing.cpp,主要包括两步:收集边缘cv::CollectPolyEdges和填充边缘cv::FillEdgeCollection,How does the drawContours function work in OpenCV when a contour is filled?

struct PolyEdge
{PolyEdge() : y0(0), y1(0), x(0), dx(0), next(0) {}//PolyEdge(int _y0, int _y1, int _x, int _dx) : y0(_y0), y1(_y1), x(_x), dx(_dx) {}int y0, y1;int64 x, dx;PolyEdge *next;
};static void
CollectPolyEdges( Mat& img, const Point2l* v, int count, std::vector<PolyEdge>& edges,const void* color, int line_type, int shift, Point offset )
{int i, delta = offset.y + ((1 << shift) >> 1);Point2l pt0 = v[count-1], pt1;pt0.x = (pt0.x + offset.x) << (XY_SHIFT - shift);pt0.y = (pt0.y + delta) >> shift;edges.reserve( edges.size() + count );for( i = 0; i < count; i++, pt0 = pt1 ){Point2l t0, t1;PolyEdge edge;pt1 = v[i];pt1.x = (pt1.x + offset.x) << (XY_SHIFT - shift);pt1.y = (pt1.y + delta) >> shift;Point2l pt0c(pt0), pt1c(pt1);if (line_type < cv::LINE_AA){t0.y = pt0.y; t1.y = pt1.y;t0.x = (pt0.x + (XY_ONE >> 1)) >> XY_SHIFT;t1.x = (pt1.x + (XY_ONE >> 1)) >> XY_SHIFT;Line(img, t0, t1, color, line_type);// use clipped endpoints to create a more accurate PolyEdgeif ((unsigned)t0.x >= (unsigned)(img.cols) ||(unsigned)t1.x >= (unsigned)(img.cols) ||(unsigned)t0.y >= (unsigned)(img.rows) ||(unsigned)t1.y >= (unsigned)(img.rows)){clipLine(img.size(), t0, t1);if (t0.y != t1.y){pt0c.y = t0.y; pt1c.y = t1.y;pt0c.x = (int64)(t0.x) << XY_SHIFT;pt1c.x = (int64)(t1.x) << XY_SHIFT;}}else{pt0c.x += XY_ONE >> 1;pt1c.x += XY_ONE >> 1;}}else{t0.x = pt0.x; t1.x = pt1.x;t0.y = pt0.y << XY_SHIFT;t1.y = pt1.y << XY_SHIFT;LineAA(img, t0, t1, color);}if (pt0.y == pt1.y)continue;edge.dx = (pt1c.x - pt0c.x) / (pt1c.y - pt0c.y);if (pt0.y < pt1.y){edge.y0 = (int)(pt0.y);edge.y1 = (int)(pt1.y);edge.x = pt0c.x + (pt0.y - pt0c.y) * edge.dx; // correct starting point for clipped lines}else{edge.y0 = (int)(pt1.y);edge.y1 = (int)(pt0.y);edge.x = pt1c.x + (pt1.y - pt1c.y) * edge.dx; // correct starting point for clipped lines}edges.push_back(edge);}
}struct CmpEdges
{bool operator ()(const PolyEdge& e1, const PolyEdge& e2){return e1.y0 - e2.y0 ? e1.y0 < e2.y0 :e1.x - e2.x ? e1.x < e2.x : e1.dx < e2.dx;}
};static void
FillEdgeCollection( Mat& img, std::vector<PolyEdge>& edges, const void* color, int line_type)
{PolyEdge tmp;int i, y, total = (int)edges.size();Size size = img.size();PolyEdge* e;int y_max = INT_MIN, y_min = INT_MAX;int64 x_max = 0xFFFFFFFFFFFFFFFF, x_min = 0x7FFFFFFFFFFFFFFF;int pix_size = (int)img.elemSize();int delta;if (line_type < CV_AA)delta = 0;elsedelta = XY_ONE - 1;if( total < 2 )return;for( i = 0; i < total; i++ ){PolyEdge& e1 = edges[i];CV_Assert( e1.y0 < e1.y1 );// Determine x-coordinate of the end of the edge.// (This is not necessary x-coordinate of any vertex in the array.)int64 x1 = e1.x + (e1.y1 - e1.y0) * e1.dx;y_min = std::min( y_min, e1.y0 );y_max = std::max( y_max, e1.y1 );x_min = std::min( x_min, e1.x );x_max = std::max( x_max, e1.x );x_min = std::min( x_min, x1 );x_max = std::max( x_max, x1 );}if( y_max < 0 || y_min >= size.height || x_max < 0 || x_min >= ((int64)size.width<<XY_SHIFT) )return;std::sort( edges.begin(), edges.end(), CmpEdges() );// start drawingtmp.y0 = INT_MAX;edges.push_back(tmp); // after this point we do not add// any elements to edges, thus we can use pointersi = 0;tmp.next = 0;e = &edges[i];y_max = MIN( y_max, size.height );for( y = e->y0; y < y_max; y++ ){PolyEdge *last, *prelast, *keep_prelast;int draw = 0;int clipline = y < 0;prelast = &tmp;last = tmp.next;while( last || e->y0 == y ){if( last && last->y1 == y ){// exclude edge if y reaches its lower pointprelast->next = last->next;last = last->next;continue;}keep_prelast = prelast;if( last && (e->y0 > y || last->x < e->x) ){// go to the next edge in active listprelast = last;last = last->next;}else if( i < total ){// insert new edge into active list if y reaches its upper pointprelast->next = e;e->next = last;prelast = e;e = &edges[++i];}elsebreak;if( draw ){if( !clipline ){// convert x's from fixed-point to image coordinatesuchar *timg = img.ptr(y);int x1, x2;if (keep_prelast->x > prelast->x){x1 = (int)((prelast->x + delta) >> XY_SHIFT);x2 = (int)(keep_prelast->x >> XY_SHIFT);}else{x1 = (int)((keep_prelast->x + delta) >> XY_SHIFT);x2 = (int)(prelast->x >> XY_SHIFT);}// clip and draw the lineif( x1 < size.width && x2 >= 0 ){if( x1 < 0 )x1 = 0;if( x2 >= size.width )x2 = size.width - 1;ICV_HLINE( timg, x1, x2, color, pix_size );}}keep_prelast->x += keep_prelast->dx;prelast->x += prelast->dx;}draw ^= 1;}// sort edges (using bubble sort)keep_prelast = 0;do{prelast = &tmp;last = tmp.next;PolyEdge *last_exchange = 0;while( last != keep_prelast && last->next != 0 ){PolyEdge *te = last->next;// swap edgesif( last->x > te->x ){prelast->next = te;last->next = te->next;te->next = last;prelast = te;last_exchange = prelast;}else{prelast = last;last = te;}}if (last_exchange == NULL)break;keep_prelast = last_exchange;} while( keep_prelast != tmp.next && keep_prelast != &tmp );}
}

其中填充算法为scan-line polygon filling algorithm,下面转载该文

Polygon Filling? How do you do that?

In order to fill a polygon, we do not want to have to determine the type of polygon that we are filling. The easiest way to avoid this situation is to use an algorithm that works for all three types of polygons. Since both convex and concave polygons are subsets of the complex type, using an algorithm that will work for complex polygon filling should be sufficient for all three types. The scan-line polygon fill algorithm, which employs the odd/even parity concept previously discussed, works for complex polygon filling.

Reminder: The basic concept of the scan-line algorithm is to draw points from edges of odd parity to even parity on each scan-line.
What is a scan-line? A scan-line is a line of constant y value, i.e., y=c, where c lies within our drawing region, e.g., the window on our computer screen.

The scan-line algorithm is outlined next.

When filling a polygon, you will most likely just have a set of vertices, indicating the x and y Cartesian coordinates of each vertex of the polygon. The following steps should be taken to turn your set of vertices into a filled polygon.

1.Initializing All of the Edges:
The first thing that needs to be done is determine how the polygon’s vertices are related. The all_edges table will hold this information.
Each adjacent set of vertices (the first and second, second and third, …, last and first) defines an edge. For each edge, the following information needs to be kept in a table:
缩进 1.The minimum y value of the two vertices.
缩进 2.The maximum y value of the two vertices.
缩进 3.The x value associated with the minimum y value.
缩进 4.The slope of the edge.
The slope of the edge can be calculated from the formula for a line:
y = mx + b;
where m = slope, b = y-intercept,
y0 = maximum y value,
y1 = minimum y value,
x0 = maximum x value,
x1 = minimum x value

The formula for the slope is as follows:
m = (y0 - y1) / (x0 - x1).

For example, the edge values may be kept as follows, where N is equal to the total number of edges - 1 and each index into the all_edges array contains a pointer to the array of edge values.
在这里插入图片描述
2.Initializing the Global Edge Table:
The global edge table will be used to keep track of the edges that are still needed to complete the polygon. Since we will fill the edges from bottom to top and left to right. To do this, the global edge table table should be inserted with edges grouped by increasing minimum y values. Edges with the same minimum y values are sorted on minimum x values as follows:
缩进 1.Place the first edge with a slope that is not equal to zero in the global edge table.
缩进 2.If the slope of the edge is zero, do not add that edge to the global edge table.
缩进 3.For every other edge, start at index 0 and increase the index to the global edge table once each time the current edge’s y value is greater than that of the edge at the current index in the global edge table.

Next, Increase the index to the global edge table once each time the current edge’s x value is greater than and the y value is less than or equal to that of the edge at the current index in the global edge table.

If the index, at any time, is equal to the number of edges currently in the global edge table, do not increase the index.

Place the edge information for minimum y value, maximum y value, x value, and 1/m in the global edge table at the index.

The global edge table should now contain all of the edge information necessary to fill the polygon in order of increasing minimum y and x values.

3.Initializing Parity
The initial parity is even since no edges have been crossed yet.

4.Initializing the Scan-Line
The initial scan-line is equal to the lowest y value for all of the global edges. Since the global edge table is sorted, the scan-line is the minimum y value of the first entry in this table.

5.Initializing the Active Edge Table
The active edge table will be used to keep track of the edges that are intersected by the current scan-line. This should also contain ordered edges. This is initially set up as follows:

Since the global edge table is ordered on minimum y and x values, search, in order, through the global edge table and, for each edge found having a minimum y value equal to the current scan-line, append the edge information for the maximum y value, x value, and 1/m to the active edge table. Do this until an edge is found with a minimum y value greater than the scan line value. The active edge table will now contain ordered edges of those edges that are being filled as such:
在这里插入图片描述
6.Filling the Polygon
Filling the polygon involves deciding whether or not to draw pixels, adding to and removing edges from the active edge table, and updating x values for the next scan-line.

Starting with the initial scan-line, until the active edge table is empty, do the following:
缩进 1.Draw all pixels from the x value of odd to the x value of even parity edge pairs.
缩进 2.Increase the scan-line by 1.
缩进 3.Remove any edges from the active edge table for which the maximum y value is equal to the scan_line.
缩进 4.Update the x value for for each edge in the active edge table using the formula x1 = x0 + 1/m. (This is based on the line formula and the fact that the next scan-line equals the old scan-line plus one.)
缩进 5.Remove any edges from the global edge table for which the minimum y value is equal to the scan-line and place them in the active edge table.
缩进 6.Reorder the edges in the active edge table according to increasing x value. This is done in case edges have crossed.

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

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

相关文章

小程序:uniapp解决主包体积过大的问题

已经分包但还是体积过大 运行时勾选“运行时是否压缩代码”进行压缩 在manifest.json配置&#xff08;开启分包优化&#xff09; "mp-weixin" : {"optimization" : {"subPackages" : true}//.... },在app.json配置&#xff08;设置组件按需注入…

Android查看签名信息系列 · 使用Android Studio获取签名

前言 Android查看签名信息系列 之使用Android Studio获取签名&#xff0c;通过Android Studio自带的gradle来获取签名信息。 优点&#xff1a;此法可查看 MD5、SHA1 等信息。 缺点&#xff1a;升级某个Studio版本后&#xff0c;没有签名任务了&#xff0c;特别不方便。 实现…

GPT4 Advanced data analysis Code Interpreter 做行业数据分析、可视化处理图像、视频、音频等

1. 跨境电商如何用ChatGPT选品 ChatGPT Jungle scout 案例&#xff1a;跨境电商如何用ChatGFT选品 ChatGPTJungle scout 素材和资料来自&#xff1a; Jungle ScoutEM, Michael Soltis 和 文韬武韬AIGC 1.1 从Jungle scout上下载数据 Date Range > Last 90 days Downlo…

Kafka三种认证模式,Kafka 安全认证及权限控制详细配置与搭建

Kafka三种认证模式,Kafka 安全认证及权限控制详细配置与搭建。 Kafka三种认证模式 使用kerberos认证 bootstrap.servers=hadoop01.com:9092,hadoop02.com:9092,hadoop03.com:9092,hadoop04.com:9092 security.

零基础入门网络渗透到底要怎么学?_网络渗透技术自学

前言&#xff1a; 很多朋友问我&#xff0c;想搞网络安全&#xff0c;编程重要吗&#xff0c;选什么语言呢&#xff1f; 国内其实正经开设网络安全专业的学校很少&#xff0c;大部分同学是来自计算机科学、网络工程、软件工程专业的&#xff0c;甚至很多非计算机专业自学的。…

计算机视觉开源代码汇总

1.【基础网络架构】Regularization of polynomial networks for image recognition 论文地址&#xff1a;https://arxiv.org/pdf/2303.13896.pdf 开源代码:https://github.com/grigorisg9gr/regularized_polynomials 2.【目标检测&#xff1a;域自适应】2PCNet: Two-Phase Cons…

【MultiOTP】在Linux上使用MultiOTP进行SSH登录

在前面的文章中【FreeRADIUS】使用FreeRADIUS进行SSH身份验证已经了解过如何通过Radius去来实现SSH和SUDO的登录&#xff0c;在接下来的文章中只是将密码从【LDAP PASSWORD Googlt OTP】改成了【MultiOTP】生成的passcode&#xff0c;不在需要密码&#xff0c;只需要OTP去登录…

ChatGPT 即将诞生一周年,OpenAI 将有大动作

图片来源&#xff1a;由无界AI生成 下个月就是 ChatGPT 一周年纪念日。OpenAI 正在谋划新的大动作。可以肯定地说&#xff0c;自诞生以来&#xff0c;ChatGPT 就为 OpenAI 提供了不可阻挡的增长动力。 01 营收超预期&#xff0c;OpenAI 缓了一口气 据 The Information 报道&…

Air001 高级定时器输入捕获功能测量脉宽和频率

Air001 高级定时器输入捕获功能测量脉宽和频率 ✨Air001只有1个16位高级定时器&#xff0c;经实际测试发现&#xff0c;通道1用于输入捕获功能失效&#xff0c;不确定是否是IO引脚存在问题还是硬件bug&#xff0c;折腾了好久&#xff0c;最后切换到通道2使用&#xff0c;就可以…

快速解决 Resource not accessible by integration

简介 最近好久没有写博客了&#xff0c;今天在写开源项目 python-package-template 的时候&#xff0c;正好遇到一个问题&#xff0c;记录一下吧。本文将介绍 Resource not accessible by integration 的几种解决方案。 也欢迎大家体验一下 python-package-template 这个项目&…

WSL Ubuntu 22.04.2 LTS 安装paddlepaddle-gpu==2.5.1踩坑日记

环境是wsl的conda环境。 使用conda安装paddlepaddle-gpu: conda install paddlepaddle-gpu2.5.1 cudatoolkit11.7 -c https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/Paddle/ -c conda-forge 等待安装... 报错处理&#xff1a; (1)PreconditionNotMetError: Cannot lo…

linux 防火墙介绍以及iptables的使用

背景介绍 在前几天&#xff0c;于工发现我们内部的150服务器7554端口被外网访问了。该应用提供着内部的摄像头资源。为了避免被入侵&#xff0c;于是我添加了一些iptables规则&#xff0c;防止外网的访问。 解决方式 解决方式有两种&#xff1a; 关闭公司公网路由器对150服务…