《数字图像处理-OpenCV/Python》连载(26)绘制椭圆和椭圆弧

news2025/1/12 17:38:06

《数字图像处理-OpenCV/Python》连载(26)绘制椭圆和椭圆弧


本书京东优惠购书链接:https://item.jd.com/14098452.html
本书CSDN独家连载专栏:https://blog.csdn.net/youcans/category_12418787.html

在这里插入图片描述


第 4 章 绘图与鼠标交互


本章介绍OpenCV的绘图功能和简单的鼠标交互处理方法。与Excel或Matplotlib中的可视化数据图不同,OpenCV中的绘图功能主要用于在图像的指定位置绘制几何图形。


本章内容概要

  • 学习OpenCV绘图的基本方法和参数。
  • 通过学习OpenCV绘图函数,能在图像上绘制直线、矩形、圆形和多边形等,以及在图像上添加文字和符号。
  • 介绍鼠标交互操作方法,通过鼠标、键盘与显示图像的实时交互获取数据。

4.5 绘制圆形和椭圆

4.5.2 绘制椭圆和椭圆弧

函数cv.ellipse用于在图像上绘制椭圆轮廓、填充椭圆、椭圆弧或填充椭圆扇区。

函数原型

cv.ellipse(img, center, axes, angle, startAngle, endAngle, color[, thickness=1, lineType=LINE_8, shift=0]) → img
cv.ellipse(img, box, color[, thickness=1, lineType=LINE_8]) → img

参数说明

  • img:输入/输出图像,允许为单通道灰度图像或多通道彩色图像。
  • center:椭圆中心点的坐标,格式为元组(x,y)。
  • axes:椭圆半轴长度,格式为元组 (hfirst,hsecond)。
  • angle:椭圆沿x轴方向的旋转角度(角度制,按顺时针方向)。
  • startAngle:绘制椭圆/圆弧的起始角度。
  • endAngle:绘制椭圆/圆弧的终止角度。
  • box:旋转矩形类RotateRect,格式为元组((x,y),(w,h),angle)。

注意问题

  • (1)椭圆参数的定义非常复杂,容易混淆,以下结合图4-5所示的椭圆绘制参数示意图来解释。
    • axes的值是椭圆主轴长度的一半,而不是主轴长度。第一半轴hfirst指x轴顺时针旋转时首先遇到的轴,与长轴或短轴无关;第二半轴hsecond是与第一半轴垂直的轴。
    • startAngle和endAngle都是指从第一半轴开始顺时针旋转的角度,起止角度与次序无关。(0,360)表示绘制整个椭圆,(0,180)表示绘制半个椭圆。

在这里插入图片描述
图4-5 椭圆绘制参数示意图

  • (2)box是旋转矩形类RotatedRect,由box可确定一个内接于box的椭圆。

    • 旋转矩形类RotatedRect在Python中是形状为((x,y),(w,h),angle)的元组,(x,y)表示矩形中心点坐标,(w,h)表示矩形的宽和高、angle表示旋转角度,范围为[-180,180]。
  • (3) 函数cv.ellipse采用分段线性曲线逼近椭圆弧边界。如果需要对椭圆进行更多控制,可以使用ellipse2Poly检索曲线后进行渲染或填充。


【例程0405】绘制椭圆和椭圆弧

本例程用于绘制椭圆和椭圆弧,注意对照图4-5理解各个参数的含义。


# 【0405】绘制椭圆和椭圆弧
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt

if __name__ == '__main__':
    img = np.ones((400, 600, 3), np.uint8)*224
    img1 = img.copy()
    img2 = img.copy()

    # (1) 半轴长度 (haf) 的影响
    cx, cy = 150, 200  # 圆心坐标
    angle = 120  # 旋转角度
    startAng, endAng = 0, 360  # 开始角度,结束角度
    haf = [50, 100, 150, 180]  # 第一轴的半轴长度
    has = 100  # 第二轴的半轴长度
    for i in range(len(haf)):
        color = (i*50, i*50, 255-i*50)
        cv.ellipse(img1, (cx,cy), (haf[i],has), angle, startAng, endAng, color, 2)
        angPi = angle * np.pi / 180  # 转换为弧度制,便于计算坐标
        xe = int(cx + haf[i]*np.cos(angPi))
        ye = int(cy + haf[i]*np.sin(angPi))
        cv.circle(img1, (xe,ye), 2, color, -1)
        cv.arrowedLine(img1, (cx,cy), (xe,ye), color)  # 从圆心指向第一轴端点
        text = "haF={}".format(haf[i])
        cv.putText(img1, text, (xe+5,ye), cv.FONT_HERSHEY_SIMPLEX, 0.6, color)
    # 绘制第二轴
    xe = int(cx + has*np.sin(angPi))  # 计算第二轴端点坐标
    ye = int(cy - has*np.cos(angPi))
    cv.arrowedLine(img1, (cx, cy), (xe, ye), color)  # 从圆心指向第二轴端点
    text = "haS={}".format(has)
    cv.putText(img1, text, (xe-80, ye+30), cv.FONT_HERSHEY_SIMPLEX, 0.6, color)

    # (2) 旋转角度 (angle) 的影响
    cx, cy = 420, 180  # 圆心坐标
    haf, has = 120, 60  # 半轴长度
    startAng, endAng = 0,360  # 开始角度,结束角度
    angle = [0, 30, 60, 135]  # 旋转角度
    for i in range(len(angle)):
        color = (i*50, i*50, 255-i*50)
        cv.ellipse(img1, (cx,cy), (haf,has), angle[i], startAng, endAng, color, 2)
        angPi = angle[i] * np.pi / 180  # 转换为弧度制,便于计算坐标
        xe = int(cx + haf*np.cos(angPi))
        ye = int(cy + haf*np.sin(angPi))
        cv.circle(img1, (xe,ye), 2, color, -1)
        cv.arrowedLine(img1, (cx,cy), (xe,ye), color)  # 从圆心指向第一轴端点
        text = "rot {}".format(angle[i])
        cv.putText(img1, text, (xe+5,ye), cv.FONT_HERSHEY_SIMPLEX, 0.6, color)

    # (3) 起始角度 (startAngle) 的影响 I
    cx, cy = 60, 80  # 圆心坐标
    haf, has = 45, 30  # 半轴长度
    angle = 0  # 旋转角度
    endAng = 360  # 结束角度
    startAng = [0, 45, 90, 180]  # 开始角度
    for i in range(len(startAng)):
        color = (i*20, i*20, 255-i*20)
        cyi = cy+i*90
        cv.ellipse(img2, (cx,cyi), (haf,has), angle, startAng[i], endAng, color, 2)
        angPi = angle * np.pi / 180  # 转换为弧度制,便于计算坐标
        xe = int(cx + haf*np.cos(angPi))
        ye = int(cyi + haf*np.sin(angPi))
        cv.arrowedLine(img2, (cx,cyi), (xe,ye), 255)  # 从圆心指向第一轴端点
        text = "start {}".format(startAng[i])
        cv.putText(img2, text, (cx-40,cyi), cv.FONT_HERSHEY_SIMPLEX, 0.6, color)
    text = "end={}".format(endAng)
    cv.putText(img2, text, (10, cy-50), cv.FONT_HERSHEY_SIMPLEX, 0.6, 255)

    # (4) 起始角度 (startAngle) 的影响 II
    cx, cy = 180, 80  # 圆心坐标
    haf, has = 45, 30  # 半轴长度
    angle = 30  # 旋转角度
    endAng = 360  # 结束角度
    startAng = [0, 45, 90, 180]  # 开始角度
    for i in range(len(startAng)):
        color = (i*20, i*20, 255-i*20)
        cyi = cy+i*90
        cv.ellipse(img2, (cx,cyi), (haf,has), angle, startAng[i], endAng, color, 2)
        angPi = angle * np.pi / 180  # 转换为弧度制,便于计算坐标
        xe = int(cx + haf*np.cos(angPi))
        ye = int(cyi + haf*np.sin(angPi))
        cv.arrowedLine(img2, (cx,cyi), (xe,ye), 255)  # 从圆心指向第一轴端点
        text = "start {}".format(startAng[i])
        cv.putText(img2, text, (cx-40,cyi), cv.FONT_HERSHEY_SIMPLEX, 0.6, color)
    text = "end={}".format(endAng)
    cv.putText(img2, text, (150,cy-50), cv.FONT_HERSHEY_SIMPLEX, 0.6, 255)

    # (5) 结束角度 (endAngle) 的影响 I
    cx, cy = 300, 80  # 圆心坐标
    haf, has = 45, 30  # 半轴长度
    angle = 0  # 旋转角度
    startAng = 0  # 开始角度
    endAng = [45, 90, 180, 360]  # 结束角度
    for i in range(len(endAng)):
        color = (i*20, i*20, 255-i*20)
        cyi = cy+i*90
        cv.ellipse(img2, (cx,cyi), (haf,has), angle, startAng, endAng[i], color, 2)
        angPi = angle * np.pi / 180  # 转换为弧度制,便于计算坐标
        xe = int(cx + haf*np.cos(angPi))
        ye = int(cyi + haf*np.sin(angPi))
        cv.arrowedLine(img2, (cx,cyi), (xe,ye), 255)  # 从圆心指向第一轴端点
        text = "end {}".format(endAng[i])
        cv.putText(img2, text, (cx-40,cyi), cv.FONT_HERSHEY_SIMPLEX, 0.6, color)
    text = "start={}".format(startAng)
    cv.putText(img2, text, (250,cy-50), cv.FONT_HERSHEY_SIMPLEX, 0.6, 255)

    # (6) 结束角度 (endAngle) 的影响 II
    cx, cy = 420, 80  # 圆心坐标
    haf, has = 45, 30  # 半轴长度
    angle = 30  # 旋转角度
    startAng = 45  # 开始角度
    endAng = [30, 90, 180, 360]  # 结束角度
    for i in range(len(endAng)):
        color = (i*20, i*20, 255-i*20)
        cyi = cy+i*90
        cv.ellipse(img2, (cx,cyi), (haf,has), angle, startAng, endAng[i], color, 2)
        angPi = angle * np.pi / 180  # 转换为弧度制,便于计算坐标
        xe = int(cx + haf*np.cos(angPi))
        ye = int(cyi + haf*np.sin(angPi))
        cv.arrowedLine(img2, (cx,cyi), (xe,ye), 255)  # 从圆心指向第一轴端点
        text = "end {}".format(endAng[i])
        cv.putText(img2, text, (cx-40,cyi), cv.FONT_HERSHEY_SIMPLEX, 0.6, color)
    text = "start={}".format(startAng)
    cv.putText(img2, text, (370,cy-50), cv.FONT_HERSHEY_SIMPLEX, 0.6, 255)

    # (7) 起始角度和结束角度的影响
    cx, cy = 540, 80  # 圆心坐标
    haf, has = 40, 30  # 半轴长度
    angle = 30  # 旋转角度
    startAng = [0, 0, 180, 180 ]  # 开始角度
    endAng = [90, 180, 270, 360]  # 结束角度
    for i in range(len(endAng)):
        color = (i*20, i*20, 255-i*20)
        cyi = cy+i*90
        cv.ellipse(img2, (cx,cyi), (haf,has), angle, startAng[i], endAng[i], color, 2)
        angPi = angle * np.pi / 180  # 转换为弧度制,便于计算坐标
        xe = int(cx + haf*np.cos(angPi))
        ye = int(cyi + haf*np.sin(angPi))
        cv.arrowedLine(img2, (cx,cyi), (xe,ye), 255)  # 从圆心指向第一轴端点
        text = "start {}".format(startAng[i])
        cv.putText(img2, text, (cx-40,cyi-20), cv.FONT_HERSHEY_SIMPLEX, 0.6, color)
        text = "end {}".format(endAng[i])
        cv.putText(img2, text, (cx-40,cyi), cv.FONT_HERSHEY_SIMPLEX, 0.6, color)
    text = "rotate={}".format(angle)
    cv.putText(img2, text, (490,cy-50), cv.FONT_HERSHEY_SIMPLEX, 0.6, 255)

    plt.figure(figsize=(9, 3.5))
    plt.subplot(121), plt.title("1. Ellipse1"), plt.axis('off')
    plt.imshow(cv.cvtColor(img1, cv.COLOR_BGR2RGB))
    plt.subplot(122), plt.title("2. Ellipse2"), plt.axis('off')
    plt.imshow(cv.cvtColor(img2, cv.COLOR_BGR2RGB))
    plt.tight_layout()
    plt.show()


程序说明:
运行结果,绘制椭圆和椭圆弧如图4-6所示。
(1)图4-6(1)的左侧图形用于比较半轴长度的影响,表明第一半轴hfirst与长轴或短轴无关,而是取决于椭圆的旋转方向。
(2)图4-6(1)的右侧图形用于比较旋转角度的影响,旋转角度是指沿x轴方向顺时针旋转的角度。
(3)起始角度和结束角度的定义及关系比较复杂,图4-6(2)给出了详细示例,请对照程序注释与显示图像进行比较和理解。

在这里插入图片描述

图4-6 绘制椭圆和椭圆弧


版权声明:
youcans@xupt 原创作品,转载必须标注原文链接:(https://blog.csdn.net/youcans/article/details/134014737)
Copyright 2023 youcans, XUPT
Crated:2023-10-24

欢迎关注本书CSDN独家连载专栏
《数字图像处理-OpenCV/Python》连载: https://blog.csdn.net/youcans/category_12418787.html

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

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

相关文章

在keil中debug分析单片机数据和函数调用过程(c51为例),使用寄存器组导致错误原因分析

寄存器参考 参考2 [寄存器组使用using参考](https://blog.csdn.net/weixin_46720928/article/details/110221835) keil中的using关键字参考 官方文档里关于using的说明可参阅2个地方,(1)keil软件菜单栏->Help->…

被邀请为期刊审稿时,如何做一个合格的审稿人?官方版本教程来喽

审稿是学术研究中非常重要的环节,它可以确保研究的科学性和严谨性。审稿人的任务是检查文章是否符合学术规范,是否具有创新性,是否具有科学价值,以及是否符合期刊的定位和风格。因此,审稿人需要具有扎实的学术背景和丰…

SHELL编程基础2

文章目录 if语句if单分支应用案例 if多分支案例 for循环while循环正则表达式基本正则Perl兼容的正则 if语句 if单分支 if单分支的语法组成: 方式一: if 条件测试;then 命令序列 fi方式二 if 条件测试then 命令序列 fi应用案例 [rootsom day01]# vim user_v2.…

WPF中的绑定知识详解(含案例源码分享)

✅作者简介:2022年博客新星 第八。热爱国学的Java后端开发者,修心和技术同步精进。 🍎个人主页:Java Fans的博客 🍊个人信条:不迁怒,不贰过。小知识,大智慧。 💞当前专栏…

带你深入了解git

目录 1、Git介绍1.1git是什么工具1.2git起到的作用1.2.1 个人开发:1.2.2 多人开发: 2、Git安装与下载项目代码2.1 下载安装git2.2 从仓库下载代码 3、创建仓库及提交代码3.1 创建仓库3.2 将本地代码以及文件提交到远程仓库3.2.1git全局配置3.2.2 远程仓库…

领域驱动设计:基于DDD的微服务设计实例

文章目录 项目基本信息战略设计战术设计后续的工作 用一个项目来了解 DDD 的战略设计和战术设计,走一遍从领域建模到微服务设计的全过程,一起掌握 DDD 的主要设计流程和关键 点。 项目基本信息 项目的目标是实现在线请假和考勤管理。功能描述如下&…

【数据中台建设系列之二】数据中台-数据采集

​ 【数据中台建设系列之二】数据中台-数据采集 上篇文章介绍了数据中台的元数据管理,相信大家对元数据模块的设计和开发有了一定的了解,本编文章将介绍数据中台另一个重要的模块—数据采集。 一、什么是数据采集 数据采集简单来说就是从各种数据源中抓…

美颜滤镜SDK,企业技术解决方案

企业越来越注重提升用户体验,而美颜滤镜SDK正是满足这一需求的强大工具。美摄美颜滤镜SDK是一款专为企业级应用打造的高效、稳定的美颜滤镜解决方案,能够帮助您的企业在瞬息万变的市场中保持竞争力。 一、强大的美颜滤镜功能 美摄美颜滤镜SDK拥有丰富的…

【wvp】wvp设备上可以开启tcp被动模式

目录 开启了 tcp被动模式 开启UDP模式 地平线不支持这种tcp情况 开启了 tcp被动模式 我的理解是zlm就会是tcp被动收流模式 tcpdump -i any host 10.1.3.7 and tcp 而wvp->浏览器,是SRTP,其实还是基于zlm8000的udp端口出来的 开启UDP模式 tcpdump -i any host…

面试算法40:矩阵中的最大矩形

题目 请在一个由0、1组成的矩阵中找出最大的只包含1的矩形并输出它的面积。例如,在图6.6的矩阵中,最大的只包含1的矩阵如阴影部分所示,它的面积是6。 分析 直方图是由排列在同一基线上的相邻柱子组成的图形。由于题目要求矩形中只包含数字…

解析一个月销售额过千万的商业模式——七人拼团

在当今的商业环境中,营销策略的运用对于企业的成功至关重要。其中,拼团模式作为一种以社交为核心的营销方式,正逐渐受到越来越多企业的关注。本文将探讨七人拼团模式,分析其奖励机制和特点,为企业家提供新的营销思路。…

如何设计出优秀的虚拟展厅,设计虚拟展厅有哪些步骤

引言: 虚拟展厅已经成为了当今数字时代的重要组成部分,无论是展示产品、推广服务,还是展示艺术品和文化遗产,虚拟展厅为用户提供了一个全新的互动体验。如何设计虚拟展厅成了很多人关注的焦点。 一.虚拟展厅设计的基本原则 虚拟…

5G RedCap工业智能网关

5G RedCap工业智能网关是当前工业智能化发展领域的重要技术之一。随着物联网和工业互联网的迅速发展,企业对于实时数据传输和高速通信需求越来越迫切。在这种背景下,5G RedCap工业智能网关以其卓越的性能和功能,成为众多企业的首选。 5G RedC…

双11电视盒子什么牌子好?数码达人测评25款整理电视盒子排名

双11买电视盒子什么牌子好?为了推荐更客观,这段时间我进行了25款主流电视盒子的深度测评,从芯片、内存、网络、散热、系统、广告、流畅度等多方面进行对比,整理了电视盒子排名,双十一想买电视盒子不知道怎么选可以参考…

应用程序无法正常启动0xc000007b的解决策略,多种解决方法分享

当我们在使用特定的软件或游戏时,我们可能会遇到一个特别令人头疼的问题—那就是"应用程序无法正常启动0xc000007b"的错误。但是,为何会出现这类情况和如何解决呢?接下来的内容,将会详细地为你阐释。 一.0xc000007b错误…

【小程序】实现一个定制的音乐播放器

应用地址:https://spacexcode.com/player 介绍 这是为自己制作的一个在线 Web 版的音乐播放器。众所周知,现在市面上的所有的音乐平台都是会员制。而免费的资源却分散在网络上的各个角落,为此,我收集了自己 喜欢的音乐&#xff0…

代码签名证书到期了怎么续费?

我们都知道代码签名证书最长期限可以申请3年,但有的首次申请也会申请1年,这种情况下证书到期了就意味着要重新办理,同样的实名验证步骤还需要再走一遍,尤其目前无论是哪种类型的代码签名证书都会有物理硬件,即使交钱实…

将本地代码上传至码云具体步骤

前言:假如我们在本地创建了一个新项目,现在想将这个项目上传至码云 第一步:码云上创建仓库 第二步:点击创建完成仓库 到这就已经完成了码云仓库的创建!!! 第三步:打开cmd命令输入这…

微信小程序5

一、什么是后台交互? 在小程序中,与后台交互指的是小程序前端与后台服务器之间的数据通信和请求处理过程。通过与后台交互,小程序能够获取服务器端的数据、上传用户数据、发送请求等。 与后台交互可以通过以下方式实现: 发起网络请…

java对象深拷贝(Mapstruct)代码实现

这几天写的需求正在提测中,所以比较有空闲时间,正好来总结一下开发中遇到的问题并记录一下。 在开发过程中遇到这样一个问题:多个对象实体间要进行对象拷贝,并且对象里面还包含别的对象集合,对象名字也不同&#xff0…