OpenCV 实现对形似宝马标的黄黑四象限标定位

news2025/4/7 23:01:03

文章目录

  • 功能
  • 背景
  • 代码
  • 效果

功能

实现对形似宝马标的黄黑四象限光学识别标定位

背景

大学同学遇到了这个场景,琢磨了下,以备不时之需。

代码

所用opencv版本:4.12

numpy==2.2.4
scikit_learn==1.6.1
import time
import cv2
import numpy as np
import math
from sklearn.cluster import KMeans

def calculate_tilt_angle(a, b):   
    # 确保 a >= b
    if a < b:
        a, b = b, a
    
    # 计算倾斜角度(弧度)
    theta_rad = math.acos(b / a)
    
    # 转为角度
    theta_deg = math.degrees(theta_rad)
    return theta_deg

def compute_intersection(line1, line2):
    """计算两条直线的交点"""
    (x1, y1), (x2, y2) = line1
    (x3, y3), (x4, y4) = line2

    # 计算分母
    den = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)
    if den == 0:  # 直线平行
        return None

    # 计算交点坐标
    t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / den
    u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / den

    # 判断交点是否在线段内
    if 0 <= t <= 1 and 0 <= u <= 1:
        x = x1 + t * (x2 - x1)
        y = y1 + t * (y2 - y1)
        return (int(x), int(y))
    else:
        return None
    
def calculate_angle(line1, line2):
    """计算两条线段之间的夹角(度数)"""
    # 提取线段端点坐标
    (x1, y1), (x2, y2) = line1
    (x3, y3), (x4, y4) = line2
    
    # 计算方向向量
    vec1 = (x2 - x1, y2 - y1)
    vec2 = (x4 - x3, y4 - y3)
    
    # 计算向量模长
    mod1 = np.sqrt(vec1[0]**2 + vec1[1]**2)
    mod2 = np.sqrt(vec2[0]**2 + vec2[1]**2)
    
    if mod1 == 0 or mod2 == 0:
        return None  # 无效向量(线段长度为0)
    
    # 计算点积和夹角余弦
    dot_product = vec1[0] * vec2[0] + vec1[1] * vec2[1]
    cos_theta = dot_product / (mod1 * mod2)
    cos_theta = np.clip(cos_theta, -1.0, 1.0)  # 处理浮点误差
    
    # 计算角度(0°~180°)
    angle = np.degrees(np.arccos(cos_theta))
    return angle

if __name__ == '__main__':
    use_camera_flag = 1
    fps_list = []
    prev_time = 0

    if use_camera_flag:
        cap = cv2.VideoCapture(2)   # 自己修改为摄像头对应ID
    while True:
        if use_camera_flag:
            current_time = time.time()
            ret, image = cap.read()
        else:
            # 读取图像
            image = cv2.imread("label.jpeg")
            # image = cv2.imread("label0.png")
            # image = cv2.imread("label1.png")
            # image = cv2.imread("label2.jpeg")
        hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)

        # 定义黄色和黑色的HSV阈值范围(需根据实际图像调整)
        lower_yellow = np.array([20, 100, 100])
        upper_yellow = np.array([40, 255, 255])

        lower_black_h = np.array([35,10,0])
        upper_black_h = np.array([120,230,255])
        lower_black_l = np.array([0,0,0])
        upper_black_l = np.array([120,120,60])

        lower_white = np.array([0,0,125])
        upper_white = np.array([180,50,255])

        # 创建黄色和黑色的掩模
        mask_yellow = cv2.inRange(hsv, lower_yellow, upper_yellow)
        mask_black = cv2.bitwise_or(cv2.inRange(hsv, lower_black_h, upper_black_h), cv2.inRange(hsv, lower_black_l, upper_black_l))
        mask_white = cv2.inRange(hsv, lower_white, upper_white)

        # 合并黑黄区域的掩模并减去白色部分的掩模
        mask_combined = cv2.bitwise_or(mask_yellow, mask_black) & ~mask_white

        # 形态学操作(去噪+连接区域)
        kernel = np.ones((5,5), np.uint8)
        mask_processed = cv2.morphologyEx(mask_combined, cv2.MORPH_CLOSE, kernel)
        mask_yellow = cv2.morphologyEx(mask_yellow, cv2.MORPH_CLOSE, kernel)
        mask_black = cv2.morphologyEx(mask_black, cv2.MORPH_CLOSE, kernel)

        # 查找轮廓
        contours, _ = cv2.findContours(mask_processed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

        image_height, image_width = image.shape[:2]

        # 筛选最大轮廓(假设标记是图像中最显著的物体)
        if contours:
            for cnt in contours:
                if len(cnt) >= 5:  # 至少需要5个点才能拟合椭圆
                    ellipse = cv2.fitEllipse(cnt)
                    (center, (width, height), angle) = ellipse
                    # 过滤条件:长宽均大于阈值
                    if width > image_width / 50 and height > image_height / 50:

                        # 创建空白掩膜(与原图同尺寸)
                        ellipse_mask = np.zeros_like(image[:, :, 0], dtype=np.uint8)
                        # 绘制填充的椭圆(白色)
                        cv2.ellipse(ellipse_mask, ellipse, 255, -1)

                        # --- 计算椭圆区域内的黄色和黑色占比 ---
                        # 提取椭圆区域内的黄色部分
                        yellow_in_ellipse = cv2.bitwise_and(mask_yellow, mask_yellow, mask=ellipse_mask)
                        yellow_area = cv2.countNonZero(yellow_in_ellipse)
                        
                        # 提取椭圆区域内的黑色部分
                        black_in_ellipse = cv2.bitwise_and(mask_black, mask_black, mask=ellipse_mask)
                        black_area = cv2.countNonZero(black_in_ellipse)

                        # 计算椭圆总面积
                        ellipse_area = cv2.countNonZero(ellipse_mask)

                        # 计算比例(避免除零错误)
                        if ellipse_area > 0:
                            yellow_ratio = yellow_area / ellipse_area
                            black_ratio = black_area / ellipse_area
                            
                            # 过滤条件:黄黑区域各占至少30%
                            if yellow_ratio >= 0.3 and black_ratio >= 0.3:
                                # 使用椭圆掩膜提取原图
                                label_in_image = cv2.bitwise_and(image, image, mask=ellipse_mask)

                                gray = cv2.cvtColor(label_in_image, cv2.COLOR_BGR2GRAY)                      
                                edges = cv2.Canny(gray, threshold1=50, threshold2=150)

                                # 直线检测与交点计算
                                lines = cv2.HoughLinesP(
                                    edges, 
                                    rho=1,                  # 距离分辨率(像素单位)
                                    theta=np.pi/180,        # 角度分辨率(弧度单位)
                                    threshold=50,           # 检测阈值(累加器投票数阈值)
                                    minLineLength=((width + height) / 2) / 4,       # 最小线段长度(短于此长度的线段被丢弃)
                                    maxLineGap=10           # 允许线段间的最大间隔(小于此间隔的线段合并)
                                )

                                # 延长线段
                                extended_lines = []
                                if lines is not None:
                                    for line in lines:
                                        x1, y1, x2, y2 = line[0]
                                        # 延长线段,例如延长比例t=1.0
                                        t=1.0
                                        dx = x2 - x1
                                        dy = y2 - y1
                                        new_x1 = int(x1 - t * dx)
                                        new_y1 = int(y1 - t * dy)
                                        new_x2 = int(x2 + t * dx)
                                        new_y2 = int(y2 + t * dy)
                                        extended_lines.append([(new_x1, new_y1), (new_x2, new_y2)])

                                # 计算交点
                                intersections = []
                                if extended_lines:
                                    for i in range(len(extended_lines)):
                                        for j in range(i+1, len(extended_lines)):
                                            line1 = extended_lines[i]
                                            line2 = extended_lines[j]

                                            # 计算夹角并过滤5°~175°以外的结果
                                            angle = calculate_angle(line1, line2)
                                            delta_angle = 85
                                            if angle is None or not (90 - delta_angle <= angle <= 90 + delta_angle):
                                                continue

                                            pt = compute_intersection(line1, line2)
                                            if pt:
                                                # 检查交点是否在椭圆内
                                                # 椭圆参数来自ellipse变量
                                                # ellipse的格式是((h, k), (a, b), theta)
                                                (h, k), (a, b), theta_deg = ellipse
                                                theta = np.deg2rad(theta_deg)
                                                x, y = pt
                                                # 转换到椭圆的标准坐标系
                                                x_trans = x - h
                                                y_trans = y - k
                                                # 旋转
                                                x_rot = x_trans * np.cos(theta) + y_trans * np.sin(theta)
                                                y_rot = -x_trans * np.sin(theta) + y_trans * np.cos(theta)
                                                # 判断是否在椭圆内
                                                if (x_rot**2)/(a**2) + (y_rot**2)/(b**2) <= 1:
                                                    intersections.append(pt)
            
                                # 聚类确定中心
                                if intersections:
                                    X = np.array(intersections)
                                    kmeans = KMeans(n_clusters=1).fit(X)
                                    center_x, center_y = kmeans.cluster_centers_[0].astype(int)
                                    (h, k), (a, b), theta_deg = ellipse
                                    # (center_x, center_y) 即为目标点坐标
                                    cv2.circle(image, (center_x, center_y), int(((width + height) / 2) / 20 + 0.5), (0, 0, 255), int(((width + height) / 2) / 50 + 0.5))
                                    cv2.ellipse(image, ((float(center_x), float(center_y)), (a, b), theta_deg), (0, 255, 0), int(((width + height) / 2) / 50 + 0.5))  
                                    print("angle: {}, {}".format(calculate_tilt_angle(a, b), (a, b)))


        if use_camera_flag:
            fps = 1 / (current_time - prev_time)
            fps_list.append(fps)
            if len(fps_list) > 5:
                fps_list.pop(0)
            avg_fps = sum(fps_list) / len(fps_list)
            # 将帧率文本绘制到左上角
            cv2.putText(
                image,
                f"FPS: {fps:.2f}",  # 显示两位小数
                (10, 30),  # 左上角坐标 (x, y)
                cv2.FONT_HERSHEY_SIMPLEX,  # 字体
                1,  # 字体大小
                (255, 255, 255),  # 颜色 (BGR格式,白色)
                2,  # 字体厚度
            )


        # 显示结果
        cv2.namedWindow("Result", cv2.WINDOW_NORMAL)
        cv2.resizeWindow("Result", 500, 500)
        cv2.imshow("Result", image)
        if not use_camera_flag:
            cv2.waitKey(0)
            cv2.destroyAllWindows()
            cv2.imwrite("output.jpg", image)
            break
        else:
            prev_time = current_time
            if cv2.waitKey(1) == ord('q'):
                cap.release()
                break
    cv2.destroyAllWindows()

效果

部分图片取自c++识别象限标 —— 灯火~

在这里插入图片描述
在这里插入图片描述
具有一定的抗倾斜能力
在这里插入图片描述

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

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

相关文章

2025 年 4 月补丁星期二预测:微软将推出更多 AI 安全功能

微软正在继续构建其 AI 网络安全战略&#xff0c;并于本月宣布在 Microsoft Security Copilot 中引入新代理。 他们引入了用于网络钓鱼分类的代理、用于数据丢失预防和内部风险管理的警报分类、条件访问优化、漏洞修复和威胁情报简报。 这些代理的目标是不断从这些不同学科中…

从吉卜力漫画到艺术创造:GPT-4o多种风格绘图Prompt大全

在3月底&#xff0c;GPT-4o掀起了一阵吉卜力绘图浪潮&#xff0c;大家纷纷输入一张图片&#xff0c;让4o模型进行风格化迁移&#xff0c;其中吉卜力风格的漫画在社交媒体上最为火热。在大家争议4o的训练数据是否侵权和4o背后的技术原理的时候&#xff0c;我们先来玩一玩&#x…

16.1Linux自带的LED灯驱动实验(知识)_csdn

前面我们都是自己编写 LED 灯驱动&#xff0c;其实像 LED 灯这样非常基础的设备驱动&#xff0c; Linux 内核已经集成了。 Linux 内核的 LED 灯驱动采用 platform 框架&#xff0c;因此我们只需要按照要求在设备树文件中添加相应的 LED 节点即可&#xff0c;本章我们就来学习如…

【vLLM】使用 vLLM 对自定义实现模型进行高速推理

推荐超级课程: 本地离线DeepSeek AI方案部署实战教程【完全版】Docker快速入门到精通Kubernetes入门到大师通关课AWS云服务快速入门实战目录 介绍什么是 vLLM?处理 vLLM 中的多模态模型实现独特的视频生成模型转换为 vLLM 模型的策略准备输入标记序列如何添加多个多模式输入如…

SQL Server 数据库实验报告

​​​​​​​ 1.1 实验题目&#xff1a;索引和数据完整性的使用 1.2 实验目的&#xff1a; &#xff08;1&#xff09;掌握SQL Server的资源管理器界面应用&#xff1b; &#xff08;2&#xff09;掌握索引的使用&#xff1b; &#xff08;3&#xff09;掌握数据完整性的…

在响应式网页的开发中使用固定布局、流式布局、弹性布局哪种更好

一、首先看下固定布局与流体布局的区别 &#xff08;一&#xff09;固定布局 固定布局的网页有一个固定宽度的容器&#xff0c;内部组件宽度可以是固定像素值或百分比。其容器元素不会移动&#xff0c;无论访客屏幕分辨率如何&#xff0c;看到的网页宽度都相同。现代网页设计…

代码随想录算法训练营第三十八天 | 322.零钱兑换 279.完全平方数 139.单词拆分

322. 零钱兑换 题目链接&#xff1a;322. 零钱兑换 - 力扣&#xff08;LeetCode&#xff09; 文章讲解&#xff1a;代码随想录 视频讲解&#xff1a;动态规划之完全背包&#xff0c;装满背包最少的物品件数是多少&#xff1f;| LeetCode&#xff1a;322.零钱兑换_哔哩哔哩_b…

linux提取 Suid提权入门 Sudo提权入门

前言 suid基本使用 Suid 是什么命令&#xff1f; suid 是管理员用户&#xff08;root&#xff09;可以对命令文件进行赋权 让其在低权限用户下下也可以保持root权限的执行能力 我现在是管理员我 使用网站用户查找信息的时候总是被阻拦没权限 查找的内容不完整 这个使用我…

Talib库安装教程

1. 打开 https://github.com/cgohlke/talib-build 2. 点击 Releases 3. 选择对应版本下载&#xff08;本人电脑win-amd64&#xff0c;python版本3.12&#xff09; 4. 安装该库&#xff08;进入该文件路径&#xff09; pip install ta_lib-0.6.3-cp312-cp312-win_amd64.whl 5…

LeetCode 249 解法揭秘:如何把“abc”和“bcd”分到一组?

文章目录 摘要描述痛点分析 & 实际应用场景Swift 题解答案可运行 Demo 代码题解代码分析差值是怎么来的&#xff1f;为什么加 26 再 %26&#xff1f; 示例测试及结果时间复杂度分析空间复杂度分析总结 摘要 你有没有遇到过这种情况&#xff1a;有一堆字符串&#xff0c;看…

Python数据可视化-第4章-图表样式的美化

环境 开发工具 VSCode库的版本 numpy1.26.4 matplotlib3.10.1 ipympl0.9.7教材 本书为《Python数据可视化》一书的配套内容&#xff0c;本章为第4章 图表样式的美化 本章主要介绍了图表样式的美化&#xff0c;包括图表样式概述、使用颜色、选择线型、添加数据标记、设置字体…

ROS Master多设备连接

Bash Shell Shell是位于用户与操作系统内核之间的桥梁&#xff0c;当用户在终端敲入命令后&#xff0c;这些输入首先会进入内核中的tty子系统&#xff0c;TTY子系统负责捕获并处理终端的输入输出流&#xff0c;确保数据正确无误的在终端和系统内核之中。Shell在此过程不仅仅是…

系统思考:思考的快与慢

在做重大决策之前&#xff0c;什么原因一定要补充碳水化合物&#xff1f;人类的大脑其实有两套运作模式&#xff1a;系统1&#xff1a;自动驾驶模式&#xff0c;依赖直觉&#xff0c;反应快但易出错&#xff1b;系统2&#xff1a;手动驾驶模式&#xff0c;理性严谨&#xff0c;…

音视频入门基础:RTP专题(21)——使用Wireshark分析海康网络摄像机RTSP的RTP流

一、引言 使用vlc等播放器可以播放海康网络摄像机的RTSP流&#xff1a; 网络摄像机的RTSP流中&#xff0c;RTSP主要用于控制媒体流的传输&#xff0c;如播放、暂停、停止等操作。RTSP本身并不用于转送媒体流数据&#xff0c;而是会通过PLAY方法使用RTP来传输实际的音视频数据。…

04.游戏开发-unity编辑器详细-工具栏、菜单栏、工作识图详解

04.游戏开发&#xff0c;unity编辑器详细-工具栏、菜单栏、工作识图详解 提示&#xff1a;帮帮志会陆续更新非常多的IT技术知识&#xff0c;希望分享的内容对您有用。本章分享的是Python基础语法。前后每一小节的内容是存在的有&#xff1a;学习and理解的关联性&#xff0c;希…

QGIS中第三方POI坐标偏移的快速校正-百度POI

1.百度POI&#xff1a; name,lng,lat,address 龙记黄焖鸡米饭(共享区店),121.908315,30.886636,南汇新城镇沪城环路699弄117号(A1区110室) 好福记黄焖鸡(御桥路店),121.571409,31.162292,沪南路2419弄26号1层B间 御品黄焖鸡米饭(安亭店),121.160322,31.305977,安亭镇新源路792号…

Pycharm 启动时候一直扫描索引/更新索引 Update index/Scanning files to index

多个项目共用一个虚拟环境&#xff0c;有助于加快PyCharm 启动吗 chatgpt 4o认为很有帮助&#xff0c;gemini 2.5pro认为没鸟用&#xff0c;我更认可gemini的观点。不知道他们谁在一本正经胡说八道。 -------- 打开pycharm的时候&#xff0c;下方的进度条一直显示在扫描文件…

Vanna:用检索增强生成(RAG)技术革新自然语言转SQL

引言&#xff1a;为什么我们需要更智能的SQL生成&#xff1f; 在数据驱动的业务环境中&#xff0c;SQL 仍然是数据分析的核心工具。然而&#xff0c;编写正确的 SQL 查询需要专业知识&#xff0c;而大型语言模型&#xff08;LLM&#xff09;直接生成的 SQL 往往存在**幻觉&…

CKPT文件是什么?

检查点&#xff08;Checkpoint&#xff0c;简称ckpt&#xff09;是一种用于记录系统状态或数据变化的技术&#xff0c;广泛应用于数据库管理、机器学习模型训练、并行计算以及网络安全等领域。以下将详细介绍不同领域中ckpt检查点的定义、功能和应用场景。 数据库中的ckpt检查点…

Android使用OpenGL和MediaCodec录制

目录 一,什么是opengl 二,什么是Android OpenGL ES 三, OpenGL 绘制流程 四, OpenGL坐标系 五, OpenGL 着色器 六, GLSL编程语言 七,使用MediaCodec录制在Opengl中渲染架构 八,代码实现 8.1 自定义渲染view继承GLSurfaceView 8.2 自定义渲染器TigerRender 8.3 创建编…