Opencv实现仪表盘颜色分割和质心提取

目录

1 记录背景

2 识别任务介绍

3 整体架构流程

4 实现思路

4.1 问题提出与解决方案       

4.2 关键代码实现

 4.2.1 颜色质心提取

4.2.2 指针质心提取:

结束语


1 记录背景

        在2024年高校智能机器人创意大赛四足机器人比赛中,由于6分钟答辩出现意外以及组委会规则制定不详细,导致我们队止步于预选赛初赛决赛,但是识别任务完成效果很不错,由于不甘心,故将此次比赛识别任务完成方法记录于此,希望为有需要的人提供帮助和技术支持!

2 识别任务介绍

        在2024年高校智能机器人创意大赛四足机器人比赛中,有这样一个识别任务:

3 整体架构流程

        在本次比赛中,仪表盘是需要打印出来,然后再通过摄像头识别,一个重大的难点就是仪表盘的的旋转角度不定,就是不会像上面那样正着摆放。我测试过用yolo识别,但效果不佳。所以还是决定使用opencv来实现。

4 实现思路

4.1 问题提出与解决方案       

        对于仪表盘最明显的几个特征莫过于三个颜色区域以及中间的黑色指针。由于本次比赛仪表盘放置方向不定,所以不能像往年处理逻辑那样,通过黑色指针的角度来判断当前指针指向哪一个区域。其实,以上的方法就是不用再找参考系了(以水平为参考坐标系),而今年旋转的仪表盘使得我们不能单单选择水平坐标系了。于是我们首先想到的就是找一个和指针位置相对静止的参考点,我们想到的是指针下方的“SSI”那个矩形标志,如图2.1所示:

            

图2.1

但由于此参考点在裁剪、滤波、轮廓提取测试后,效果远达不到我们的预期,所以只能放弃。

        后面,我们想到了一个可行的方法:就是尝试将三个颜色区域的质心坐标找出来、以及中间黑色指针的端点找出来,通过判断黑色指针端点到三个颜色质心的距离,谁离指针最近那么指针指向的就是谁对应的颜色区域。经过测试,发现效果很好,可行性很高。

4.2 关键代码实现

 4.2.1 颜色质心提取

        颜色质心提取思路就是将图片色彩空间转换为hsv,然后通过调出黄、绿、红三色的颜色区间,通过掩模运算变为二值图像,二值图像通过轮廓提取和矩运算就可以得到轮廓的质心,也就是颜色的质心!

        关键代码如下:

def getRedCenter(img):
    HSV_img = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
    #  可能需要根据实际情况进行调整
    Min_red = np.array([0, 24, 52])
    Max_red = np.array([9, 108, 255])
    mask = cv2.inRange(HSV_img, Min_red, Max_red)  # 获取指定颜色区域的掩膜

    k = np.ones((5, 5), dtype=np.uint8)
    mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, k)  # 去除内部噪点
    # 获取颜色区域质心
    M = cv2.moments(mask)
    cX = int(M["m10"] / M["m00"])
    cY = int(M["m01"] / M["m00"])
    point = np.array((cX, cY))
    return point

提示:另外两色和上面几乎一样,只需要改hsv的区间。

4.2.2 指针质心提取:

        首先我们需要找到仪表盘的位置,通过霍夫圆检查可以锁定仪表盘的位置bbox,接着将仪表盘的bbox取各方向的一半,最后将中心区域二值化->取出指针轮廓->算出质心位置

        关键代码如下:

import cv2
import cv2 as cv
import numpy as np


class DashboardRecognition:
    def __init__(self, min_area=50 * 50):
        self.min_area = min_area
        self.image_width = 0
        self.image_height = 0
        self.bbox = None

    def detect(self, image, bbox=None):
        if bbox is None:
            bias = np.array([0, 0]).astype(np.int32)
        self.image_height, self.image_width = image.shape[:2]
        self.bbox = None

        # 转换为灰度图像
        gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
        # 使用高斯滤波平滑图像
        blur = cv.GaussianBlur(gray, (5, 5), 0)
        # 使用霍夫变换检测圆形
        circles = cv.HoughCircles(blur, cv.HOUGH_GRADIENT, 1.2, 100)
        # 如果检测到圆形,绘制边界
        if circles is not None:
            # 将圆形坐标转换为整数
            circles = np.round(circles[0, :]).astype("int")
            # 定义一个阈值,表示两个圆心之间的最大距离,用于判断是否合并
            threshold = 10
            # 定义一个列表,用于存储合并后的圆形
            merged_circles = []
            # 遍历每个圆形
            for (x1, y1, r1) in circles:
                # 定义一个标志,表示当前的圆形是否已经被合并过
                merged = False
                # 遍历已经合并过的圆形列表
                for i in range(len(merged_circles)):
                    # 获取已经合并过的圆形的坐标和半径
                    (x2, y2, r2) = merged_circles[i]
                    # 计算两个圆心之间的距离
                    distance = np.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2)
                    # 如果距离小于阈值,说明两个圆形可以合并
                    if distance < threshold:
                        # 将当前的圆形和已经合并过的圆形进行平均,得到新的圆形
                        if r1 >= r2:
                            merged_circles[i] = (x1, y1, r1)
                        else:
                            merged_circles[i] = (x2, y2, r2)
                        # 设置标志为True,表示当前的圆形已经被合并过
                        merged = True
                        # 跳出循环,不再遍历其他已经合并过的圆形
                        break
                # 如果当前的圆形没有被合并过,就将它添加到已经合并过的圆形列表中
                if not merged:
                    merged_circles.append((x1, y1, r1))
            # 遍历已经合并过的圆形列表,找到最大的圆,作为检测结果
            biggest = None
            biggest_r = -1
            for (x, y, r) in merged_circles:
                if r > biggest_r:
                    biggest = (x, y, r)
                    biggest_r = r
            # 将圆转换成bbox
            if biggest is not None:
                self.bbox = np.array([[biggest[0] - biggest[2], biggest[1] - biggest[2]],
                                      [biggest[0] + biggest[2], biggest[1] + biggest[2]]] + bias, np.int32)
                return self.__refine_bbox(self.bbox)
        return None

    def __refine_bbox(self, bbox):
        # 将边界框限制在图像边界内且必须有足够大的尺寸以被认为有效时。
        # bbox是一个(2*2)数组,左上角和右下角坐标
        bbox[:, 0] = np.clip(bbox[:, 0], 0, self.image_width)  # 将框的x限定在图像范围内
        bbox[:, 1] = np.clip(bbox[:, 1], 0, self.image_height)  # 将框的y限定再图像范围内
        w, h = bbox[1] - bbox[0]
        if w <= 0 or h <= 0 or w * h <= self.min_area:
            return None
        else:
            return bbox

    def get_PointerCenter(self, image):
        if self.bbox is not None:
            # 取完整圆的50%部分
            center = np.mean(self.bbox, axis=0)  # 获取质心
            scale_bbox = center + (self.bbox - center) * 0.5  # bbox缩小为一半
            scale_bbox = scale_bbox.astype(np.int32)
            #  相对于原图的左上角偏移量
            x_offset, y_offset = scale_bbox[0][0], scale_bbox[0][1]
            # 图片转换为灰度图
            image = image[scale_bbox[0][1]:scale_bbox[1][1], scale_bbox[0][0]:scale_bbox[1][0], :]  # 获取框中图像
            gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
            # 二值化,+,其他区域设为0
            _, thresh = cv.threshold(gray, 100, 255, cv.THRESH_BINARY_INV)
            # 寻找轮廓
            contours, _ = cv.findContours(thresh, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
            # 找到面积最大的轮廓
            max_area = 0
            max_contour = None
            for c in contours:
                area = cv.contourArea(c)
                if area > max_area:
                    max_area = area
                    max_contour = c
            # 如果找到了最大轮廓,寻找他的质心
            if max_contour is not None:
                mask = np.zeros(image.shape[:2], dtype=np.uint8)
                cv2.drawContours(mask, [max_contour], 0, 255, -1)
                # cv2.imshow('mask', mask)
                M = cv2.moments(mask)
                cX = int(M["m10"] / M["m00"])
                cY = int(M["m01"] / M["m00"])
                h, w, _ = image.shape
                max_dist = 0  # 存储最大距离
                farthest_pt = (cX, cY)  # 存储距离质心最远的点
                for pt in max_contour[:, 0, :]:  # cnt的形状通常是(n, 1, 2)
                    dist = cv2.norm(np.array([cX, cY]), pt)   # 遍历轮廓上每一个点到质心的距离
                    if dist > max_dist:
                        max_dist = dist
                        farthest_pt = pt
                #  还原成原图比例
                cX = int((farthest_pt[0] / w) * (scale_bbox[1][0] - scale_bbox[0][0])) + x_offset
                cY = int((farthest_pt[1] / h) * (scale_bbox[1][1] - scale_bbox[0][1])) + y_offset
                point = np.array((cX, cY))
                return point
            else:
                return None
        else:
            return None

    def visualize(self, image, thickness=1):
        if self.bbox is not None:
            # Draw bounding box on original image (in color)
            cv.rectangle(image, self.bbox[0], self.bbox[1], (0, 255, 0), 2)
        return image

最后通过两点之间的距离算出指针离哪一个颜色最近就可以知道指针指的哪一个区域了!

结束语

        最后还是要吐槽一下这个比赛主办方,预选赛初赛得分点十分详细,要求也说的很清楚,但预选赛决赛搞得这么辣鸡,答辩时间安排一坨,评分标准也没,就靠这短短六分钟,2分钟ppt答辩,2分钟功能测试、2分钟提问,最后比赛情况是:提问没时间提,测试时间紧张,出现一点小意外,于是,这样就将我们淘汰了,从未听说过决赛比初赛还要简陋,我们准备很久,功能也测试无数遍,结果被这短短6分钟线上答辩就淘汰了,谁能甘心?实在是办的太烂了比赛!

        希望我的实现思路分享能帮到正在看博客的你,如果有需要,我可以提供完整源码!请联系我!

  • 20
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值