关于opencv的contourArea计算方法

news2024/11/20 8:35:20

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 PolyEdge
            if ((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;
    else
        delta = 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 drawing
    tmp.y0 = INT_MAX;
    edges.push_back(tmp); // after this point we do not add
                          // any elements to edges, thus we can use pointers
    i = 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 point
                prelast->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 list
                prelast = last;
                last = last->next;
            }
            else if( i < total )
            {
                // insert new edge into active list if y reaches its upper point
                prelast->next = e;
                e->next = last;
                prelast = e;
                e = &edges[++i];
            }
            else
                break;

            if( draw )
            {
                if( !clipline )
                {
                    // convert x's from fixed-point to image coordinates
                    uchar *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 line
                    if( 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 edges
                if( 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.coloradmin.cn/o/1109276.html

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

相关文章

如何调整模型缩放比例

1、缩放模型的几种方法 在3D建模软件中&#xff0c;通常有几种方式可以缩放模型。以下是两种常见的方法&#xff1a; 使用缩放工具&#xff1a;大多数3D建模软件都提供了缩放工具&#xff0c;可以通过选择模型并使用该工具来进行缩放。通常&#xff0c;您可以在工具栏或菜单中…

vivado 脚本使用——loogarch指令集 实验exp6

首先从Window-tcl console 调出终端 然后执行进入指定目录 也就是run_vivado 目录 然后打开Tools——Run Tcl script&#xff0c;执行create_project.tcl文件&#xff0c;静待完成。

1、Flowable基础

Flowable是BPMN的一个基于java的软件实现&#xff0c;不过Flowable不仅仅包括BPMN&#xff0c;还有DMN决策表和CMMN Case管理引擎&#xff0c;并且有自己的用户管理、微服务API等一系列功能&#xff0c;是一个服务平台。 官方手册&#xff1a;https://tkjohn.github.io/flowab…

21款奔驰GLS400升级小柏林音响 无损音质 无损安装

小柏林之声音响是13个喇叭1个功放&#xff0c;功率是590W&#xff0c;对应普通音响来说&#xff0c;已经是上等了。像著名的哈曼卡顿音响&#xff0c;还是丹拿音响&#xff0c;或者是BOSE音响&#xff0c;论地位&#xff0c;论音质柏林之声也是名列前茅。 升级小柏林音响&…

2023年8月PMP考试成绩出炉|微思通过率95%以上

热烈祝贺一大波微思学员通过 PMP 认证考试 PMP 认证课程介绍 部分学员成绩单 PMP课程涵盖了项目整合管理、项目范围管理、项目进度管理、项目资源管理、项目采购管理等内容。 国际注册项目管理师(PMP) 证书是项目管理领域含金量最高的职业资格证书&#xff0c;获得该资质是…

oracle 表空间详解以及配置操作

Oracle 数据库是由若干个表空间构成的。任何数据库对象在存储时都必须存储在某个 表空间中。表空间对应于若干个数据文件&#xff0c;即表空间是由一个或多个数据文件构成的。 1、常用表空间&#xff1a; 系统表空间 (system tablespace) 是每个 Oracle 数据库都必须具备的。…

抖音小程序制作源码系统 带完整搭建教程

在当今社交媒体时代&#xff0c;抖音作为一款备受欢迎的短视频应用&#xff0c;已经拥有了庞大的用户群体。与此同时&#xff0c;抖音小程序也成为了商家和开发者们关注的热点。今天小编就来给大家介绍一款抖音小程序制作源码系统&#xff0c;五分钟确实创建一个小程序。 系统特…

3D教程 | 在3D可视化引擎HOOPS Visualize中如何调整场景灯光亮度

在HOOPS Visualize场景中使用远光时&#xff0c;您可能需要调整亮度&#xff0c;并在最暗和最亮的面孔之间获得更好的对比度。 HOOPS中文网查HOOPS中文网查 太亮了 将模型的镜面反射值更改为灰色以降低亮度&#xff0c;或更改为黑色以消除面上的火花&#xff1a; ModelKey.Ge…

自2023年下半年起,软考各级别、各资格、各科目考试方式均由纸笔考试改革为计算机化考试,软考的所有考试科目均改为机考

自2023年下半年起&#xff0c;软考各级别、各资格、各科目考试方式均由纸笔考试改革为计算机化考试&#xff0c;软考的所有考试科目均改为机考。包括高项的综合知识、案例分析、论文&#xff0c;所有考试科目均改为在计算机上作答。 2023年下半年软考实行机考后&#xff0c;关…

企业文件传输使用网盘好还是大文件传输软件好?

在当今的信息化时代&#xff0c;企业之间的沟通和协作离不开文件的传输。无论是内部的工作报告、合同文档、设计图纸&#xff0c;还是外部的客户资料、产品介绍、项目方案&#xff0c;都需要通过网络进行快速、高效、安全的发送和接收。那么&#xff0c;企业在进行文件传输时&a…

[python 刷题] 19 Remove Nth Node From End of List

[python 刷题] 19 Remove Nth Node From End of List 题目&#xff1a; Given the head of a linked list, remove the nth node from the end of the list and return its head. 题目说的是就是移除倒数第 n 个结点&#xff0c;如官方给的案例&#xff1a; 这里提供的 n 就是…

图像分块及拼接(二)python代码

图像分块及拼接 问题python 代码效果图 问题 图像分块及拼接代码中对图像分块不均匀&#xff0c;本文给出的代码&#xff0c;图像分块大小相同。改进方法&#xff1a;图像分块不均匀时&#xff0c;填充事情shape相同 python 代码 import numpy as np import cv2 as cv impor…

一百九十一、Flume——Flume配置文件各参数含义

一、目的 在实际项目的开发过程中&#xff0c;不同Kafka主题的数据规模、数据频率&#xff0c;需要配置不同的Flume参数&#xff0c;而这一切的调试、配置工作&#xff0c;都要建立在对Flume配置文件各参数含义的基础上 二、Flume各参数及其含义 &#xff08;一&#xff09;…

深入探究音视频开源库 WebRTC 中 NetEQ 音频抗网络延时与抗丢包的实现机制

目录 1、引言 2、什么是NetEQ&#xff1f; 3、NetEQ技术详解 3.1、NetEQ概述 3.2、抖动消除技术 3.3、丢包补偿技术 3.4、NetEQ概要设计 3.5、NetEQ的命令机制 3.6、NetEQ的播放机制 3.7、MCU的控制机制 3.8、DSP的算法处理 3.9、DSP算法的模拟测试 4、NetEQ源文件…

【发表案例】IF4+,2区SCI期刊,2个月零3天录用,11天见刊!网络安全领域均可

计算机类SCIE 【期刊简介】IF&#xff1a;4.0-5.0&#xff0c;JCR2区&#xff0c;中科院3区 【检索情况】SCIE 在检&#xff0c;正刊 【征稿领域】提高安全性和隐私性的边缘/云的智能方法的研究&#xff0c;如数字孪生等 【截稿日期】5篇版面 录用案例&#xff1a;2个月零…

VTK 标注类Widget 文字标注 vtkCaptionWidget

目录 Part1: 简介 Part2: 效果 Part3: example Part1: 简介 vtkCaptionWidget:用一个带线框及箭头的文本信息来标注某一对象&#xff1b; 在可视化程序中&#xff0c;经常会对某个对象做一些标注说明&#xff1b; 如&#xff0c;在医学图像诊断中&#xff0c;常常会手动标注…

流量回放工具

作用 在评估某个服务可用性时&#xff0c;一种常见方式是采用一些压测工具(如ab/hey/jmeter/siege/wrk/locust等)压测某几个核心接口&#xff0c;一般看达到某个TPS/QPS时&#xff0c;CPU/内存等资源的水位&#xff1b;或者固定资源的阈值&#xff0c;看最高能达到的TPS/QPS。 …

通过SVN拉取项目 步骤

方法一&#xff1a;文件夹方式 首先新建一个空的文件夹&#xff0c;例如&#xff0c;名为“demo”的空文件夹 在这个空的文件夹中鼠标右键&#xff0c;点击SVN Checkout 会出现下图所示的页面&#xff0c;第一个输入框是svn的项目地址&#xff0c;第二个输入框是拉取项目所放的…

jenkins 安装与使用、用户权限划分

jenkins 安装与使用 安装插件&#xff1a; 开启该插件功能 验证用户管理 创建web01~02 使用web01登录 用户权限划分 安装 Role-Based Strategy 插件后&#xff0c;系统管理 中多了如图下所示的一个功能&#xff0c;用户权限的划分就是靠他来做的 创建角色 重新访问 创建项目…

一氧化碳单位换算 以及环保最低排放

换算单位&#xff1a; 1.0 % 10000 mg/m3 1.0 mg/m3 0.870 ppm 举例&#xff1a;CO表测数据 1.0 %&#xff0c;则10000 mg/m3&#xff0c;则10000*22.4/28 8700 ppm 参考&#xff1a;钢铁工业大气污染物超低排放标准 DB13