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

文章目录

功能

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

背景

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

代码

所用opencv版本:4.12

复制代码
numpy==2.2.4
scikit_learn==1.6.1
python3 复制代码
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++识别象限标 ------ 灯火~


具有一定的抗倾斜能力

相关推荐
lijianhua_97126 小时前
国内某顶级大学内部用的ai自动生成论文的提示词
人工智能
EDPJ6 小时前
当图像与文本 “各说各话” —— CLIP 中的模态鸿沟与对象偏向
深度学习·计算机视觉
蔡俊锋6 小时前
用AI实现乐高式大型可插拔系统的技术方案
人工智能·ai工程·ai原子能力·ai乐高工程
自然语6 小时前
人工智能之数字生命 认知架构白皮书 第7章
人工智能·架构
大熊背6 小时前
利用ISP离线模式进行分块LSC校正的方法
人工智能·算法·机器学习
eastyuxiao7 小时前
如何在不同的机器上运行多个OpenClaw实例?
人工智能·git·架构·github·php
诸葛务农7 小时前
AGI 主要技术路径及核心技术:归一融合及未来之路5
大数据·人工智能
光影少年7 小时前
AI Agent智能体开发
人工智能·aigc·ai编程
极梦网络无忧7 小时前
OpenClaw 基础使用说明(中文版)
python
charlee447 小时前
最小二乘问题详解17:SFM仿真数据生成
c++·计算机视觉·sfm·数字摄影测量·无人机航测