Python在图片上画多边形:从简单轮廓到复杂区域标注

这是一篇为您定制的技术博客,风格延续了上一篇的实用教程风,重点讲解如何使用 Python 绘制多边形,适合发布在 CSDN、知乎、掘金等技术社区。


在上一篇《Python在图片上画圆形》中,我们掌握了基础的几何绘制。但在实际的计算机视觉任务中,比如图像分割目标检测框选不规则区域标注(如标注车牌、建筑物轮廓),仅仅画圆和矩形是不够的。

多边形(Polygon) 才是描述不规则形状的王者。今天,我们就来深入探讨如何使用 Python 的 OpenCV 和 Pillow 库,在图片上绘制任意形状的多边形。


一、 为什么需要画多边形?

  • 数据标注:在制作 AI 数据集时,需要精确框出物体的边缘(Polygon Annotation)。
  • ROI 提取:指定图像中的感兴趣区域(Region of Interest),屏蔽掉无关背景。
  • 视觉特效:在图片上添加水印遮罩、马赛克块(本质也是多边形组合)。

二、 环境准备

确保你已经安装了必要的库:

bash 复制代码
pip install opencv-python
pip install Pillow
pip install numpy

准备一张测试图片 test_image.jpg


三、 方法一:使用 OpenCV (cv2) ------ 精准控制

OpenCV 是处理多边形的首选,因为它提供了非常底层的控制能力。核心函数是 cv2.polylines()cv2.fillPoly()

1. 核心难点:点的格式

OpenCV 对多边形顶点的格式要求非常严格:必须是 int32 类型的 NumPy 数组,且形状为 (-1, 1, 2)

2. 基础代码:画一个五边形

python 复制代码
import cv2
import numpy as np

# 1. 读取图片
image = cv2.imread('test_image.jpg')
if image is None:
    print("图片读取失败")
    exit()

# 2. 定义多边形的顶点坐标 (x, y)
# 假设我们要画一个五边形,手动定义5个点
points = np.array([
    [100, 300],  # 点1
    [200, 200],  # 点2
    [300, 300],  # 点3
    [250, 400],  # 点4
    [150, 400]   # 点5
], dtype=np.int32)

# 3. 重塑数组形状 (这一步非常重要!)
# 从 (5, 2) 变为 (5, 1, 2)
points = points.reshape((-1, 1, 2))

# 4. 绘制轮廓
# 参数:图像, [顶点数组], 是否闭合, 颜色(BGR), 线条粗细
cv2.polylines(image, [points], isClosed=True, color=(0, 255, 255), thickness=3)

# 5. 显示与保存
cv2.imshow('Polygon (Outline)', image)
cv2.imwrite('result_polyline.jpg', image)
cv2.waitKey(0)
cv2.destroyAllWindows()

3. 参数详解

  • isClosed
    • True:多边形是闭合的(最后一个点会连回第一个点)。
    • False:多边形是开放的(折线)。
  • color :依然是 BGR 格式。(0, 255, 255) 是黄色。
  • thickness
    • 正数(如 3):线条宽度。
    • 负数(如 -1):如果是用 cv2.fillPoly 或者配合特定参数,表示填充。注意:cv2.polylines 不支持直接填充,填充请用下面的方法。

4. 进阶:填充实心多边形

如果你想画一个实心的红色多边形,使用 cv2.fillPoly()

python 复制代码
# 复制原图以免混淆
filled_image = image.copy()

# 使用 fillPoly
# 注意:这里不需要 reshape 成 (-1, 1, 2),直接传 (N, 2) 也可以,但传标准格式更安全
cv2.fillPoly(filled_image, [points], color=(0, 0, 255)) # 红色 (BGR)

cv2.imshow('Polygon (Filled)', filled_image)
cv2.waitKey(0)

5. 高级技巧:半透明多边形(Alpha 混合)

在做标注时,我们常需要半透明的遮罩。OpenCV 没有直接的 "Alpha" 参数,需要手动混合:

python 复制代码
# 创建一个和原图一样大的透明层
overlay = image.copy()

# 在透明层上画实心多边形
cv2.fillPoly(overlay, [points], color=(0, 0, 255)) # 红色

# 混合原图和透明层 (原图权重0.7, 覆盖层权重0.3)
alpha = 0.3
cv2.addWeighted(overlay, alpha, image, 1 - alpha, 0, image)

cv2.imshow('Transparent Polygon', image)
cv2.waitKey(0)

四、 方法二:使用 Pillow (PIL) ------ 简单直观

Pillow 的 API 设计更符合人类直觉,不需要复杂的数组重塑。

1. 基础代码示例

python 复制代码
from PIL import Image, ImageDraw

# 1. 打开图片
image = Image.open('test_image.jpg')
draw = ImageDraw.Draw(image)

# 2. 定义顶点 (直接是扁平列表 [x1, y1, x2, y2, ...] 或 元组列表)
# Pillow 支持 [(x1, y1), (x2, y2), ...] 格式,更友好
polygon_points = [(100, 300), (200, 200), (300, 300), (250, 400), (150, 400)]

# 3. 绘制
# outline: 边框颜色 (RGB)
# fill: 填充颜色 (RGB)
# width: 边框宽度
draw.polygon(polygon_points, outline=(255, 0, 0), fill=(0, 255, 0), width=3)

# 4. 显示与保存
image.show()
image.save('result_pillow_poly.jpg')

2. Pillow 的优势

  • 坐标格式 :支持简单的元组列表 [(x, y), ...],不需要 Reshape。
  • 颜色格式:使用标准的 RGB,不用记 BGR。
  • 抗锯齿:Pillow 的绘制引擎自带抗锯齿效果,边缘看起来更平滑(OpenCV 需要手动开启抗锯齿,比较麻烦)。

五、 实战案例:不规则区域标注(模拟 LabelMe)

假设我们要在图片上标注出一个不规则的区域,并打上标签。

使用 OpenCV 实现完整流程

python 复制代码
import cv2
import numpy as np

# 读取图片
img = cv2.imread('test_image.jpg')

# 1. 定义不规则区域的顶点 (模拟鼠标点击选取)
pts = np.array([[50, 50], [400, 100], [300, 300], [100, 350]], np.int32)
pts = pts.reshape((-1, 1, 2))

# 2. 画轮廓 (黄色,粗线)
cv2.polylines(img, [pts], True, (0, 255, 255), 2)

# 3. 计算区域中心点,用于写文字
M = cv2.moments(pts)
cX = int(M["m10"] / M["m00"])
cY = int(M["m01"] / M["m00"])

# 4. 写标签文字
label = "Object A"
cv2.putText(img, label, (cX - 30, cY), 
            cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 255), 2)

# 5. 显示结果
cv2.imshow("Annotation", img)
cv2.waitKey(0)
cv2.destroyAllWindows()

六、 常见坑与 FAQ

Q1: 运行 OpenCV 代码报错 (-2115:Assertion failed) 怎么办?
A: 90% 的情况是点数组的维度或数据类型不对

  • 检查是否执行了 points.reshape((-1, 1, 2))
  • 检查数据类型是否为 np.int32
  • 检查传入的是 [points] (列表包裹数组),而不是直接传 points

Q2: 多边形画出来一部分在屏幕外怎么办?
A: OpenCV 和 Pillow 都会自动裁剪(Clip)到图像边界,不会报错,但超出部分看不见。如果需要处理坐标越界,需要自己写逻辑判断 if 0 <= x < width

Q3: 如何画带虚线的多边形?
A: 这是一个进阶需求。

  • Pillow:不支持直接画虚线多边形,需要自己循环画线段。
  • OpenCV :可以通过计算线段交点或者使用 cv2.line 循环绘制每一条边来模拟虚线效果。

Q4: 如何生成多边形的掩码(Mask)?
A: 这是一个非常常用的操作(用于抠图)。

python 复制代码
# 创建一张全黑的单通道图
mask = np.zeros(img.shape[:2], dtype=np.uint8)
# 在掩码上画白色的实心多边形
cv2.fillPoly(mask, [points], 255)
# 使用 mask 提取原图区域
result = cv2.bitwise_and(img, img, mask=mask)

七、 总结

特性 OpenCV (cv2.polylines) Pillow (ImageDraw.polygon)
易用性 ⭐⭐ (需处理数组维度) ⭐⭐⭐⭐⭐ (非常简单)
性能 ⭐⭐⭐⭐⭐ (极快) ⭐⭐⭐ (够用)
功能 支持掩码、抗锯齿(较复杂)、填充 支持抗锯齿、简单填充
颜色 BGR (易踩坑) RGB (直观)
推荐场景 视频处理、实时标注、生成Mask 简单图片编辑、生成静态图

画多边形是图像处理从"初级"迈向"中级"的标志。当你能熟练地在图片上绘制任意多边形并生成掩码时,你就已经掌握了图像分割和复杂目标检测的基础工具。

下一篇我们将讲解如何结合鼠标事件,实时交互地在图片上画多边形(像 LabelMe 一样),敬请期待!


如果这篇文章帮你解决了问题,欢迎点赞、关注支持!

相关推荐
weixin_381288182 小时前
MongoDB备节点无法读取数据怎么解决_rs.slaveOk()与Secondary读取权限
jvm·数据库·python
南尘NCA86662 小时前
如何解决企业微信防封行业高封号率痛点
python·企业微信
楼田莉子2 小时前
同步/异步日志系统:日志器管理器模块\全局接口\性能测试
linux·服务器·开发语言·c++·后端·设计模式
dyxal2 小时前
内网 Windows 离线安装 uv:极速 Python 包管理器的部署实战
windows·python·uv
qq_654366982 小时前
Vue 3 中集成 Three.js 场景的完整实践指南
jvm·数据库·python
人邮异步社区2 小时前
文科生零基础学 Python 难吗?真不难,难的是找对书!
开发语言·python
qq_424098562 小时前
JavaScript中箭头函数在类方法定义中的this绑定优势
jvm·数据库·python
2301_803875612 小时前
HTML怎么用Lawyer Zone对齐律所图_Lawyer专业主题图片布局
jvm·数据库·python
春栀怡铃声2 小时前
【C++修仙录02】筑基篇:类和对象(上)
开发语言·c++·算法