OpenCV-Python 颜色识别(红色)并拟合矫正目标区域

news2024/11/28 16:41:16

OpenCV版本:4.6.0.66

算法实现思路:

  1. 颜色识别(红色)
  2. 形态学去噪
  3. 轮廓检测
  4. 多边形拟合
  5. 透视矫正

代码实现:

import cv2
import numpy as np


# 可视化
def img_show(name, img):
    cv2.namedWindow(name, 0)
    cv2.resizeWindow(name, 1000, 500)
    cv2.imshow(name, img)
    cv2.waitKey(0)


def color_warped(path):
    img = cv2.imread(path)
    hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

    # 颜色识别(红色),过滤红色区域
    lower_red1 = np.array([0, 43, 46])  # 红色阈值下界
    higher_red1 = np.array([10, 255, 255])  # 红色阈值上界
    mask_red1 = cv2.inRange(hsv, lower_red1, higher_red1)
    lower_red2 = np.array([156, 43, 46])  # 红色阈值下界
    higher_red2 = np.array([180, 255, 255])  # 红色阈值上界
    mask_red2 = cv2.inRange(hsv, lower_red2, higher_red2)
    mask_red = cv2.add(mask_red1, mask_red2)  # 拼接过滤后的mask
    img_show('mask_red', mask_red)

    # 形态学去噪,cv2.MORPH_OPEN先腐蚀再膨胀,cv2.MORPH_CLOSE先膨胀再腐蚀
    kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
    mask_red = cv2.morphologyEx(mask_red, cv2.MORPH_OPEN, kernel, iterations=1)
    kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (7, 7))
    mask_red = cv2.morphologyEx(mask_red, cv2.MORPH_CLOSE, kernel, iterations=3)
    img_show('mask_red', mask_red)

    # 轮廓检测,找出线条的轮廓
    draw_cnt = img.copy()
    cnts = cv2.findContours(mask_red, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)[0]
    cv2.drawContours(draw_cnt, cnts, -1, (0, 255, 0), 2)
    img_show('draw_cnt', draw_cnt)

    # 四边形拟合,找到相应的的顶点
    draw_approx = img.copy()
    point1, point2 = list(), list()
    for cnt in cnts:
        for epsilon in range(50):
            approx = cv2.approxPolyDP(cnt, epsilon, True)
            if len(approx) == 4:
                break
        cv2.polylines(draw_approx, [approx], True, (0, 0, 255), 2)  # 绘制边
        for i in approx:
            cv2.circle(draw_approx, i[0], 6, (0, 0, 0), -1)  # 绘制顶点

        approx = [i[0] for i in approx.tolist()]
        approx = sorted(approx, key=lambda k: k[1], reverse=False)  # 按y坐标排序,升序

        point1.extend(approx[:2])  # 存放上顶点坐标
        point2.extend(approx[2:])  # 存放下顶点坐标
    point1.sort(key=lambda k: k[0], reverse=False)  # 按x坐标排序,升序
    point2.sort(key=lambda k: k[0], reverse=False)
    img_show('draw_approx', draw_approx)

    # 透视矫正目标区域
    w, h = 900, 300
    rect = [point1[0], point1[-1], point2[-1], point2[0]]  # 顺序为第一个四边形的左上,第四个四边形的右上,第四个四边形的右下,第一个四边形的左下
    pts1 = np.array(rect, dtype="float32")
    pts2 = np.array([rect[0], [rect[0][0] + w, rect[0][1]],
                    [rect[0][0] + w, rect[0][1] + h], [rect[0][0], rect[0][1] + h]], dtype="float32")
    M = cv2.getPerspectiveTransform(pts1, pts2)  # 变换矩阵
    img_warped = cv2.warpPerspective(img, M, (1500, 500))  # 透视变换
    img_show('img_warped1', img_warped)

    img_warped = img_warped[rect[0][1]: rect[0][1] + h, rect[0][0]: rect[0][0] + w]  # 抠出变换后的区域
    img_show('img_warped2', img_warped)


if __name__ == '__main__':
    path = 'data/picture/18.jpg'
    color_warped(path)

原图:

颜色识别(红色)

img = cv2.imread(path)
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

        颜色识别使用的通道是HSV通道。使用HSV通道识别颜色会比使用BGR通道更容易区分。H表示Hue(色调、色相,取值:0-180),S表示Saturation(饱和度、色彩纯净度,取值:0-255),V表示Value(亮度,取值:0-255),详情可见:HSV颜色空间。各颜色的取值范围如下:

         可见红色的取值范围有两个,大部分情况下这两个范围刚好是互补的,可根据实际情况调整。

# 颜色识别(红色),过滤红色区域
lower_red1 = np.array([0, 43, 46])  # 红色阈值下界
higher_red1 = np.array([10, 255, 255])  # 红色阈值上界
mask_red1 = cv2.inRange(hsv, lower_red1, higher_red1)
lower_red2 = np.array([156, 43, 46])  # 红色阈值下界
higher_red2 = np.array([180, 255, 255])  # 红色阈值上界
mask_red2 = cv2.inRange(hsv, lower_red2, higher_red2)
mask_red = cv2.add(mask_red1, mask_red2)  # 拼接过滤后的mask

         过滤出来的二值图如下:

形态学去噪

# 形态学去噪,cv2.MORPH_OPEN先腐蚀再膨胀,cv2.MORPH_CLOSE先膨胀再腐蚀
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
mask_red = cv2.morphologyEx(mask_red, cv2.MORPH_OPEN, kernel, iterations=1)
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (7, 7))
mask_red = cv2.morphologyEx(mask_red, cv2.MORPH_CLOSE, kernel, iterations=3)

         颜色识别有时候会有部分噪声被一起识别,这时候可以用形态学进行去噪。在使用cv2.morphologyEx进行形态学去噪之前,需要由cv2.getStructuringElement先定义卷积核形状大小

        cv2.getStructuringElement(shape, ksize)常用参数如下:

  • shape:核的形状,cv2.MORPH_RECT矩形),cv2.MORPH_CROSS十字形),cv2.MORPH_ELLIPSE椭圆形
  • ksize:核的大小,格式为(width, height)

        cv2.morphologyEx(src, op, kernel, iterations) 常用参数如下:

  • src:输入图像
  • op:形态学操作的类型,cv2.MORPH_ERODE腐蚀),cv2.MORPH_DILATE膨胀),cv2.MORPH_OPEN开运算),cv2.MORPH_CLOSE闭运算),cv2.MORPH_GRADIENT梯度运算),cv2.MORPH_TOPHAT礼帽运算),cv2.MORPH_BLACKHAT黑帽运算),详情可见:形态学运算
  • kernel:进行形态学操作的卷积核
  • iterations:形态学操作迭代的次数,默认值为1

        效果如下:

轮廓检测

# 轮廓检测,找出线条的轮廓
draw_cnt = img.copy()
cnts = cv2.findContours(mask_red, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)[0]
cv2.drawContours(draw_cnt, cnts, -1, (0, 255, 0), 2)

        使用cv2.findContours(image, mode, method)检测轮廓,常用参数如下:

  • image:二值图
  • mode:轮廓的检索模式
cv2.RETR_EXTERNAL只检测外轮廓
cv2.RETR_LIST检测的轮廓不建立等级关系
cv2.RETR_CCOMP建立两个等级的轮廓,上面的一层为外边界,里面的一层为内孔的边界信息。如果内孔内还有一个连通物体,这个物体的边界也在顶层
cv2.RETR_TREE建立一个等级树结构的轮廓
  • method:轮廓的近似方法
cv2.CHAIN_APPROX_NONE存储所有的轮廓点,相邻的两个点的像素位置差不超过1
cv2.CHAIN_APPROX_SIMPLE压缩水平方向,垂直方向,对角线方向的元素,只保留该方向的终点坐标

         使用cv2.drawContours绘制轮廓,效果如下:

近似多边形

# 四边形拟合,找到相应的的顶点
draw_approx = img.copy()
point1, point2 = list(), list()
for cnt in cnts:
    for epsilon in range(50):
        approx = cv2.approxPolyDP(cnt, epsilon, True)
        if len(approx) == 4:
            break
    cv2.polylines(draw_approx, [approx], True, (0, 0, 255), 2)  # 绘制边
    for i in approx:
        cv2.circle(draw_approx, i[0], 6, (0, 0, 0), -1)  # 绘制顶点

        使用cv2.approxPolyDP(curve, epsilon, closed)近似多边形,常用参数如下:

  • curve:输入二维点集的数组
  • epsilon:近似结果的精度。这是原曲线和其近似曲线之间的最大距离。epsilon越小,近似结果的折线形状越“接近”曲线。详情可见:近似多边形
  • closed:TrueFalseTrue表示近似曲线是闭合的(第一个顶点与最后一个顶点相互连接),False表示近似曲线是不闭合

        cv2.approxPolyDP返回近似多边形的顶点坐标,这里用范围内的值遍历epsilon精度,当返回的顶点坐标数量4时,即为四边形

        对每个轮廓进行四边形拟合,使用cv2.polylinescv2.circle分别绘制顶点,效果如下:

        四边形拟合完成后,对每个四边形的顶点进行排序

透视矫正

# 透视矫正目标区域
w, h = 900, 300
rect = [point1[0], point1[-1], point2[-1], point2[0]]  # 顺序为第一个四边形的左上,第四个四边形的右上,第四个四边形的右下,第一个四边形的左下
pts1 = np.array(rect, dtype="float32")
pts2 = np.array([rect[0], [rect[0][0] + w, rect[0][1]],
                [rect[0][0] + w, rect[0][1] + h], [rect[0][0], rect[0][1] + h]], dtype="float32")
M = cv2.getPerspectiveTransform(pts1, pts2)  # 变换矩阵
img_warped = cv2.warpPerspective(img, M, (1500, 500))  # 透视变换

         使用cv2.warpPerspective进行透视变换之前,需要先由cv2.getPerspectiveTransform获取变换矩阵

        cv2.getPerspectiveTransform(src, dst)常用参数如下:

  • src:变换前图像四边形顶点坐标数组),其中任意三点不共线
  • dst:变换后图像四边形顶点坐标数组),其中任意三点不共线

        这里输入变换前四边形的顶点坐标,分别为第一个四边形的左上坐标第四个四边形的右上坐标第四个四边形的右下坐标第一个四边形的左下坐标;输入变换后四边形的顶点坐标,是以第一个四边形的左上坐标延长宽和高。

         cv2.getPerspectiveTransform(src, M, dsize)常用参数如下:

  • src:输入图像
  • M:变换矩阵
  • dsize:输出图像大小

         变换效果如下:

img_warped = img_warped[rect[0][1]: rect[0][1] + h, rect[0][0]: rect[0][0] + w]  # 抠出变换后的区域

         将变换后的区域抠出来,效果如下:

参考链接

三分钟带你快速学习RGB、HSV和HSL颜色空间 - 知乎

第8章 形态学操作 -- 8.3 核函数 cv2.getStructuringElement()_Enzo 想砸电脑的博客-CSDN博客

Python +OpenCV CH9:形态学操作(morphologyEx扩展)_liguoxin1990的博客-CSDN博客

Python OpenCV approxPolyDP()函数

多边形逼近cv2.approxPolyDP和Douglas-Peucker算法_00000cj的博客-CSDN博客_多边形逼近算法

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

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

相关文章

Java内存模型与volatile

Java内存模型 Java内存模型Java Memory Model,简称JMM,本身是一种抽象的概念并不真实存在它仅仅描述的是一组约定或规范,通过这组规范定义了程序中(尤其是多线程)各个变量的读写访问方式并决定一个线程对共享变量的写入何时以及如何变成对另…

白鹭群优化算法(ESOA)附matlab代码

✅作者简介:热爱科研的Matlab仿真开发者,修心和技术同步精进,matlab项目合作可私信。 🍎个人主页:Matlab科研工作室 🍊个人信条:格物致知。 更多Matlab仿真内容点击👇 智能优化算法 …

单调区间能写成并吗?【基于间断区间的讨论】

不能。 问题 在做到一道题的时候,发现了两个减区间,于是就用并∪连接到一起了,但是答案是要用和连接。于是想要辨析一下这个并和和的区别。 区别 并:并的意思是将多个区间看成一个并起来的整体,在整个区间上仍然是…

按照 STAR 法则介绍自己做过的项目

大家好啊,我是大田 介绍项目注意两点:1、自己真的做过 2、逻辑表达能力 为什么推荐你用 STAR 法则说呢? STAR 法则是结构化面试中非常重要的理论。 面试官通过这样的描述全面了解你的测试知识、经验、技术能力的掌握程度,通过你发…

本地pycharm连接到远程服务器(超级详细)

本地pycharm连接到远程服务器(超级详细) 文章之前,你需要做的是 1.服务器需要创建好虚拟环境 2.你的本地安装好pycharm 目的是 同步服务器上的文件 在本地进行debug,并将修改后的文件进行更新 一般是将文件代码上传到服务器上面&…

【微服务】Sentinel 控制台

目录 1. 概述 2. 启动控制台 2.1 获取 Sentinel 控制台 2.2 启动 3. 客户端接入控制台 3.1 引入JAR包 3.2 配置启动参数 3.3 触发客户端初始化 4. 查看机器列表以及健康情况 5. 监控 5.1 "簇点链路"中显示刚刚调用的资源(单机实时) …

猿创征文|分享一下我的日常开发工具和常用软件

1、Eclipse IDE for Java Developers 这个软件,个人已经用了很多年的免费 Java 开发软件。 1)配置JDK 2)新建项目 3)运行使用 这里注意到有 Run As 和 Debug As ,区别就是后者是调试模式,你可以在代码任意处打断点跟…

element-ui中获取el-divider的组件的ref时为空

element-ui中的el-divider组件是函数式组件,所以当我尝试获取其ref时总是获取不到,因为函数式组件没有this. ![在这里插入图片描述](https://img-blog.csdnimg.cn/496711bf5c2a4dd1bee168e4e2d638d4.png)此时打印this.$refs,控制台输出: 没有h2,翻看el-divider源码发现是函数式…

C++入门学习3-指针与字符数组,函数,指针数组

c入门学习3char型指针的使用p*p*p与p与[整型指针指向整型数组]的区别指针与函数的使用指向函数的指针空指针调用函数从函数中返回指针一维数组和二维数组的类比CHAR型指针数组关于&achar型指针的使用 char型指针可以直接指向一个字符串,如下 char s[]{a,b,c};…

YOLO V7源码解析

1.命令行参数介绍 YOLO v7参数与YOLO v5差不多,我就直接将YOLO v5命令行参数搬过来了,偷个懒 --weights:初始权重--cfg:模型配置文件--data:数据配置文件--hyp:学习率等超参数文件--epochs:迭代次数-imgsz:图像大小--rect:长方…

多目标蜉蝣优化算法(MOMA)附Matlab代码

✅作者简介:热爱科研的Matlab仿真开发者,修心和技术同步精进,matlab项目合作可私信。 🍎个人主页:Matlab科研工作室 🍊个人信条:格物致知。 更多Matlab仿真内容点击👇 智能优化算法 …

统计信号处理基础 习题解答6-6

题目 在本题中,我们扩展标量BLUE的结果。假定 其中θ 是待估计的未知参数,β 是已知常数,数据矢量x 的协方差矩阵是C 。在本题我们定义修正的线性估计量为 证明BLUE为: 另外求最小方差。 解答: 首先确保线性估计量是…

TFT-LCD显示中英文

TFT-LCD显示中英文 在前面编写了屏幕显示ASCII字符和字符串后,本次实现屏幕显示中文字符和中文字符串 中文字符取模 阴码,逐行式,逆向,十六进制数,C51格式, 输入要显示的中文字符,字体选择宋…

【数据结构】搜索二叉树(C++实现)

目录 一、二叉搜索树的概念 二、二叉搜索树的实现 2.1 节点的定义及构造 2.2 树的结构及功能展示 2.3 树的 Insert 2.4 树的中序遍历 2.4 树的 Find 2.5 树的 Erase 2.6 拷贝构造、赋值运算符重载、析构函数 三、递归实现树的增删查 3.1 递归实现 FindR 3.2 递归实…

Vue | Vue.js Composition API(二)

🖥️ Vue.js专栏:Vue.js 初级知识 Composition API(二) 🧑‍💼 个人简介:一个不甘平庸的平凡人🍬 ✨ 个人主页:CoderHing的个人主页 🍀 格言: ☀️ 路漫漫其修远兮,吾将上下而求索☀…

嘿,朋友,其实 CSS 动画超简单的 - 时间函数篇(贝塞尔曲线、steps,看完还不懂算我输)

分配内存 - new 官方定义:new是一个分配内存的内置函数,第一个参数是类型,而不是值,返回的值是指向该类型新分配的零值的指针。 func new(Type) *Type 我们平常在使用指针的时候是需要分配内存空间的,未分配内存空间…

Java自定义注解

目录 一、什么是自定义注解 1)Java注解简介 2)Java注解分类 JDK基本注解 JDK元注解 自定义注解 如何自定义注解? 二、自定义注解 1)获取类上注解值 2)获取类属性上的注解属性值 3)获取方法上的注…

WireShark 常用协议分析

WireShark 常用协议分析 1.3 实战:使用 WireShark 对常用协议抓包并分析原理 协议分析的时候 我们 关闭混淆模式, 避免一些干扰的数据包存在。 1.3.1 常用协议分析 - ARP 协议 地址解析协议 (英语:Address Resolution Protocol&…

从内核角度看网络包发送流程

一、前置知识 1、RingBuffer结构详解 关于RingBuffer网上有很多说法,有的人说RingBuffer是系统启动时就预先申请好的一个环形数组,有的人说RingBuffer是在接收或发送数据时才动态申请的一个环形数组,那么到底RingBuffer的结构是怎么样的呢&…

《吉师作业》(2)之迟来的答案

前言 🍀作者简介:吉师散养学生,为挣钱努力拼搏的一名小学生。 🍁个人主页:吉师职业混子的博客_CSDN博客-python学习,HTML学习,清览题库--C语言程序设计第五版编程题解析领域博主 🫒文章目的:我不…