基于opencv的斜光测距及python实现

1.前言

最近做了一个基于opencv的斜光测距的小项目,东西不多,但是很有意思,值得拿出来学一学。项目里面需要比较精确的定位功能,将前人matlab代码移植到python上,并且做了一些优化,简化逻辑(毕竟我是专业的程序员),也用了tkinter界面包装了一下,最后通过pyinstaller打包成程序给同事使用。

2.原理

在这里插入图片描述

通过使用不同的亮点位置和对应的高度进行多元线性回归建模,再对新的亮点位置进行高度预测。

在这里插入图片描述

如图分别是14,14.5,15,15.5对应的四张光点位置图。

3.获取亮点位置

def get_box(image):
    # 将图像转换为灰度图像
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    # 应用高斯模糊来减少噪声
    blurred = cv2.GaussianBlur(gray, (5, 5), 0)
    max_val = np.max(blurred)
    _, binary = cv2.threshold(blurred, max_val/2, 255, cv2.THRESH_BINARY)
    # 形态学开运算去除噪声
    kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
    opened = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel)
    # 找到轮廓
    contours, _ = cv2.findContours(opened, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    # 如果找到轮廓,计算质心
    if contours:
        largest_contour = max(contours, key=cv2.contourArea)
        M = cv2.moments(largest_contour)
        if M["m00"] != 0:
            cx = int(M["m10"] / M["m00"])
            cy = int(M["m01"] / M["m00"])
        else:
            cx, cy = 0, 0
        centroid = (cx, cy)
        # 计算边界框
        x, y, w, h = cv2.boundingRect(largest_contour)
        p=10
        bbox = (x-p, y-p, w+2*p, h+2*p)
        # 在图像上绘制质心和边界框
        output_image = image.copy()
        cv2.circle(output_image, centroid, 5, (0, 255, 0), -1)
        x,y,w,h=bbox
        cv2.rectangle(output_image, (x, y), (x + w, y + h), (0, 255, 0), 2)
        print(f"亮点的中心位置: {centroid},亮点的边界框: {bbox}")
        return centroid,bbox,output_image
    else:
        return None

4.建模

不想再安装其它的python包了,就基于numpy写的LineRegression。

class LinearRegression:
    def __init__(self):
        self.theta = None

    def fit(self, X, y):
        """
        训练线性回归模型

        参数:
        X:自变量数据,形状为 (m, n),其中 m 是样本数量,n 是特征数量
        y:因变量数据,形状为 (m, 1)
        """
        # 在 X 前面加一列1,以便于计算截距项
        X_b = np.c_[np.ones((X.shape[0], 1)), X]

        # 使用正规方程求解回归系数
        self.theta = np.linalg.inv(X_b.T @ X_b) @ X_b.T @ y

    def predict(self, X):
        """
        对新样本进行预测

        参数:
        X:自变量数据,形状为 (m, n),其中 m 是样本数量,n 是特征数量

        返回值:
        y_pred:预测的因变量数据,形状为 (m, 1)
        """
        if self.theta is None:
            raise ValueError("模型未经过训练,请先调用 fit 方法")

        # 在 X 前面加一列1,以便于计算截距项
        X_b = np.c_[np.ones((X.shape[0], 1)), X]

        # 使用训练得到的回归系数进行预测
        y_pred = X_b @ self.theta

        return y_pred

建模效果
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

5.全部代码

项目地址:https://gitee.com/zhang_jie_sc/auto-focus

import re
import cv2
import numpy as np
import os

from matplotlib import pyplot as plt


def get_box(image):
    # 将图像转换为灰度图像
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    # 应用高斯模糊来减少噪声
    blurred = cv2.GaussianBlur(gray, (5, 5), 0)
    max_val = np.max(blurred)
    _, binary = cv2.threshold(blurred, max_val/2, 255, cv2.THRESH_BINARY)
    # 形态学开运算去除噪声
    kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
    opened = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel)
    # 找到轮廓
    contours, _ = cv2.findContours(opened, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    # 如果找到轮廓,计算质心
    if contours:
        largest_contour = max(contours, key=cv2.contourArea)
        M = cv2.moments(largest_contour)
        if M["m00"] != 0:
            cx = int(M["m10"] / M["m00"])
            cy = int(M["m01"] / M["m00"])
        else:
            cx, cy = 0, 0
        centroid = (cx, cy)
        # 计算边界框
        x, y, w, h = cv2.boundingRect(largest_contour)
        p=10
        bbox = (x-p, y-p, w+2*p, h+2*p)
        # 在图像上绘制质心和边界框
        output_image = image.copy()
        cv2.circle(output_image, centroid, 5, (0, 255, 0), -1)
        x,y,w,h=bbox
        cv2.rectangle(output_image, (x, y), (x + w, y + h), (0, 255, 0), 2)
        print(f"亮点的中心位置: {centroid},亮点的边界框: {bbox}")
        return centroid,bbox,output_image
    else:
        return None

def get_files(dir):
    img_path_list = [f for f in os.listdir(dir) if
                     f.startswith('Point') and f.endswith('.jpg')]  # 获取该文件夹中所有jpg格式的图像
    val_list=[]
    for p in img_path_list:
        # 使用正则表达式匹配_后.前的0或0.5
        match = re.search(r'_(\d+(\.\d+)?)\.', p)
        if match:
            val=match.group(1)
            val_list.append(float(val))
        else:
            raise ValueError('{0}文件名错误,无法提取位置i学那些'.format(p))
    return img_path_list,val_list

def merge_intersecting_boxes(boxes):
    merged_boxes = []

    # 计算包含所有框的大框
    x_min = min(box[0] for box in boxes)
    y_min = min(box[1] for box in boxes)
    x_max = max(box[0] + box[2] for box in boxes)
    y_max = max(box[1] + box[3] for box in boxes)
    big_box = (x_min, y_min, x_max - x_min, y_max - y_min)

    # 返回大框和空的合并框列表
    return big_box, merged_boxes

def r2_score(y_true,y_pred):
    # 计算相关系数
    corr = np.corrcoef(y_true, y_pred)[0, 1]
    # 计算 R 方值
    r2 = corr ** 2
    return r2

def plot_image_and_r2_zzz(image, x, y,r2,theta):
    # 将 BGR 格式转换为 RGB 格式
    image = cv2.cvtColor(image.copy(), cv2.COLOR_BGR2RGB)
    # 创建一个图形和两个子图
    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 5), gridspec_kw={'top': 0.85})
    # 设置窗口标题方式二
    fig.canvas.manager.window.title("建模结果")
    # 在第一个子图中显示图片
    ax1.imshow(image)
    ax1.axis('off')
    ax1.set_title('Box')

    # 在第二个子图中显示拟合直线
    ax2.plot(x, y, 'o', label='Data')
    ax2.plot(x, x, label='Fitted Line')
    # 将每个数字转换为字符串,保留五位小数
    theta_str = "(k1={:.4f}, k2={:.4f}, b={:.4f})".format(*theta)
    ax2.set_title('Fitted Line (theta={}, r2={:.5f})'.format(theta_str,r2))
    # 添加轴标签
    ax2.set_xlabel('y_true')
    ax2.set_ylabel('y_pred')
    ax2.legend()
    # 显示图形
    plt.tight_layout()
    plt.show()

class LinearRegression:
    def __init__(self):
        self.theta = None

    def fit(self, X, y):
        """
        训练线性回归模型

        参数:
        X:自变量数据,形状为 (m, n),其中 m 是样本数量,n 是特征数量
        y:因变量数据,形状为 (m, 1)
        """
        # 在 X 前面加一列1,以便于计算截距项
        X_b = np.c_[np.ones((X.shape[0], 1)), X]

        # 使用正规方程求解回归系数
        self.theta = np.linalg.inv(X_b.T @ X_b) @ X_b.T @ y

    def predict(self, X):
        """
        对新样本进行预测

        参数:
        X:自变量数据,形状为 (m, n),其中 m 是样本数量,n 是特征数量

        返回值:
        y_pred:预测的因变量数据,形状为 (m, 1)
        """
        if self.theta is None:
            raise ValueError("模型未经过训练,请先调用 fit 方法")

        # 在 X 前面加一列1,以便于计算截距项
        X_b = np.c_[np.ones((X.shape[0], 1)), X]

        # 使用训练得到的回归系数进行预测
        y_pred = X_b @ self.theta

        return y_pred

if __name__=='__main__':
    file_dir="./20240531_113524"
    img_path_list, locs = get_files(file_dir)
    coors = []
    boxs = []
    for i, image_name in enumerate(img_path_list):  # 逐一读取图像
        item = cv2.imread(os.path.join(file_dir, image_name))
        cneter, box, _ = get_box(item)
        coors.append(list(cneter))
        boxs.append(box)
    merge_box, _ = merge_intersecting_boxes(boxs)
    # 使用线性回归拟合数据
    matx = np.array(coors)
    arr_x = matx[:, 0]
    reg = LinearRegression()
    reg.fit(matx, locs)
    y_true = np.array(locs)
    y_pred = reg.predict(matx)
    r2 = r2_score(y_true, y_pred)
    # 输出 R^2 值
    draw_img = cv2.imread(os.path.join(file_dir, img_path_list[0]), cv2.IMREAD_COLOR)
    x, y, w, h = merge_box
    cv2.rectangle(draw_img, (x, y), (x + w, y + h), (0, 255, 0), 2)
    plot_image_and_r2_zzz(draw_img, y_true, y_pred, r2, reg.theta)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值