commit 33e1954f102bdbf2cd227cfae480ea8c8b247886
Author: Paul597 <2454344015@qq.com>
Date: Wed Jul 9 15:55:01 2025 +0800
7.9 plus Calibration
diff --git a/Visual measurement-straight/.idea/.gitignore b/Visual measurement-straight/.idea/.gitignore
new file mode 100644
index 0000000..35410ca
--- /dev/null
+++ b/Visual measurement-straight/.idea/.gitignore
@@ -0,0 +1,8 @@
+# 默认忽略的文件
+/shelf/
+/workspace.xml
+# 基于编辑器的 HTTP 客户端请求
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/Visual measurement-straight/.idea/Visual measurement-straight.iml b/Visual measurement-straight/.idea/Visual measurement-straight.iml
new file mode 100644
index 0000000..d0876a7
--- /dev/null
+++ b/Visual measurement-straight/.idea/Visual measurement-straight.iml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Visual measurement-straight/.idea/inspectionProfiles/Project_Default.xml b/Visual measurement-straight/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..72ad4ef
--- /dev/null
+++ b/Visual measurement-straight/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Visual measurement-straight/.idea/inspectionProfiles/profiles_settings.xml b/Visual measurement-straight/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000..105ce2d
--- /dev/null
+++ b/Visual measurement-straight/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Visual measurement-straight/.idea/misc.xml b/Visual measurement-straight/.idea/misc.xml
new file mode 100644
index 0000000..1314114
--- /dev/null
+++ b/Visual measurement-straight/.idea/misc.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Visual measurement-straight/.idea/modules.xml b/Visual measurement-straight/.idea/modules.xml
new file mode 100644
index 0000000..02389d2
--- /dev/null
+++ b/Visual measurement-straight/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Visual measurement-straight/py/Calibration.py b/Visual measurement-straight/py/Calibration.py
new file mode 100644
index 0000000..639982f
--- /dev/null
+++ b/Visual measurement-straight/py/Calibration.py
@@ -0,0 +1,143 @@
+import cv2
+import numpy as np
+import glob
+from scipy.optimize import minimize
+import model
+import math
+import calc_way
+
+def needs_correction(dist_coeffs, error_threshold=0.5):
+ k1, k2, p1, p2, k3 = dist_coeffs.ravel()
+ if (abs(k1) > 0.1 or abs(k2) > 0.01 or abs(p1) > 0.005 or
+ abs(p2) > 0.005 or abs(k3) > 0.01):
+ return True
+ return False
+
+def calibrate(image_fold, columns, rows, size):
+ # 设置棋盘格参数
+ chessboard_size = (columns, rows) # 内部角点数量 (columns, rows)
+ square_size = size # 棋盘格方块实际大小(单位:毫米/厘米/英寸等)
+
+ # 准备对象点 (0,0,0), (1,0,0), (2,0,0) ..., (8,5,0)
+ objp = np.zeros((chessboard_size[0] * chessboard_size[1], 3), np.float32)
+ objp[:, :2] = np.mgrid[0:chessboard_size[0], 0:chessboard_size[1]].T.reshape(-1, 2) * square_size
+
+ # 存储对象点和图像点的数组
+ objpoints = [] # 3D点(真实世界坐标)
+ imgpoints = [] # 2D点(图像坐标)
+
+ # 获取标定图像
+ images = glob.glob(image_fold)
+ # print("找到的图像文件:", images)
+
+ for fname in images:
+ img = cv2.imread(fname)
+ gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
+
+ # 查找棋盘格角点
+ ret, corners = cv2.findChessboardCorners(gray, chessboard_size, None)
+
+ # 如果找到,添加对象点和图像点
+ if ret:
+ objpoints.append(objp)
+
+ # 亚像素级精确化
+ criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
+ corners2 = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
+ imgpoints.append(corners2)
+
+ # 绘制并显示角点
+ cv2.drawChessboardCorners(img, chessboard_size, corners2, ret)
+ cv2.imshow('Corners', img)
+ cv2.waitKey(500)
+
+ cv2.destroyAllWindows()
+
+ # 相机标定
+ ret, camera_matrix, dist_coeffs, rvecs, tvecs = cv2.calibrateCamera(
+ objpoints, imgpoints, gray.shape[::-1], None, None)
+
+ # 输出标定结果
+ print("相机内参矩阵(K矩阵):\n", camera_matrix)
+ print("\n畸变系数(k1, k2, p1, p2, k3):\n", dist_coeffs)
+ print("\n重投影误差:", ret)
+
+ if needs_correction(dist_coeffs):
+ print("需要矫正:畸变系数过大")
+ else:
+ print("无需矫正:畸变可忽略")
+ return camera_matrix, dist_coeffs, rvecs, tvecs
+
+def find_corners(image_path, columns, rows):
+ # 读取图像
+ image = cv2.imread(image_path)
+ gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
+
+ # 定义棋盘格尺寸 (内角点数量,非方格数)
+ pattern_size = (columns, rows) # 例如8x8的棋盘有7x7内角点
+
+ # 查找棋盘格角点
+ ret, corners = cv2.findChessboardCorners(gray, pattern_size, None)
+
+ if ret:
+ # 提高角点检测精度
+ criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
+ corners = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
+
+ # 绘制检测结果
+ cv2.drawChessboardCorners(image, pattern_size, corners, ret)
+ cv2.imshow('Chessboard Corners', image)
+ cv2.waitKey(0)
+ cv2.destroyAllWindows()
+ else:
+ print("未检测到棋盘格")
+
+ print(corners)
+ # 将形状从 (N,1,2) 转换为 (N,2)
+ corners_reshaped = corners.reshape(-1, 2)
+ # print("所有角点坐标:\n", corners_reshaped)
+ fixed_value = 960 # 固定值
+ column_index = 1 # 操作第2列(索引从0开始)
+
+ # 方法:固定值 - 列值
+ corners_reshaped[:, column_index] = fixed_value - corners_reshaped[:, column_index]
+ # print(corners_reshaped)
+ return corners_reshaped
+
+def fun_test(x,cls,corners):
+ print("标定开始")
+ # print(corners)
+ error = 0
+ model = cls()
+ f = model.f
+ H = model.H - 9
+ gamma = math.atan(1 / (math.tan(x[0]) * math.tan(x[1])))
+ seta = math.atan(1 / math.sqrt(pow(math.tan(x[1]), 2) + 1 / pow(math.tan(x[0]), 2)))
+ column = 0
+ for index, value in enumerate(corners):
+ if index % 11 == 10:
+ column += 1
+ index += 1
+ continue
+ Xw, Yw = calc_way.calc_distance(value[0], value[1], x[0], x[1])
+ Xw1, Yw1 = calc_way.calc_distance(corners[index+1][0], corners[index+1][1], x[0], x[1])
+ d2 = math.sqrt((Xw1 - Xw) ** 2 + (Yw1 - Yw) ** 2)
+ error = error + abs(d2 - 60)
+ return error/80
+
+def get_result_test(cls,corners):
+ params = cls,corners
+ bounds = [(0.1, 1.7), (0.1, 1.7)]
+ result = minimize(
+ fun_test,
+ x0=[0.7, 0.9],
+ args=params,
+ # method='Nelder-Mead', # 或 'trust-constr'
+ method='L-BFGS-B', bounds=bounds,
+ tol=1e-12, # 高精度容差
+ options={'gtol': 1e-12, 'maxiter': 1000}
+ )
+ return result
+
+
+
diff --git a/Visual measurement-straight/py/__pycache__/calc_slope_line.cpython-312.pyc b/Visual measurement-straight/py/__pycache__/calc_slope_line.cpython-312.pyc
new file mode 100644
index 0000000..b02b4ae
Binary files /dev/null and b/Visual measurement-straight/py/__pycache__/calc_slope_line.cpython-312.pyc differ
diff --git a/Visual measurement-straight/py/__pycache__/calc_way.cpython-312.pyc b/Visual measurement-straight/py/__pycache__/calc_way.cpython-312.pyc
new file mode 100644
index 0000000..5c13dfc
Binary files /dev/null and b/Visual measurement-straight/py/__pycache__/calc_way.cpython-312.pyc differ
diff --git a/Visual measurement-straight/py/__pycache__/get_data.cpython-312.pyc b/Visual measurement-straight/py/__pycache__/get_data.cpython-312.pyc
new file mode 100644
index 0000000..8a5475f
Binary files /dev/null and b/Visual measurement-straight/py/__pycache__/get_data.cpython-312.pyc differ
diff --git a/Visual measurement-straight/py/__pycache__/model.cpython-312.pyc b/Visual measurement-straight/py/__pycache__/model.cpython-312.pyc
new file mode 100644
index 0000000..1d30cdc
Binary files /dev/null and b/Visual measurement-straight/py/__pycache__/model.cpython-312.pyc differ
diff --git a/Visual measurement-straight/py/calc_slope_line.py b/Visual measurement-straight/py/calc_slope_line.py
new file mode 100644
index 0000000..e97dad9
--- /dev/null
+++ b/Visual measurement-straight/py/calc_slope_line.py
@@ -0,0 +1,74 @@
+import math
+import model
+import calc_way
+from scipy import stats
+
+model = model.Model()
+alpha = model.alpha
+beta = model.beta
+
+"""
+计算地面点的竖直垂线在图像中的斜率
+参数:点在图像中的坐标x、y,相机的外部偏转角参数alpha、beta
+返回值:斜率k
+"""
+def get_k(alpha,beta,x,y):
+ gamma = math.atan(1 / (math.tan(alpha) * math.tan(beta)))
+ seta = math.atan(1 / math.sqrt(pow(math.tan(beta), 2) + 1 / pow(math.tan(alpha), 2)))
+ Xw,Yw = calc_way.calc_distance(x,y,alpha,beta)
+ u = Xw * math.cos(gamma) - Yw * math.sin(gamma)
+ v = Xw * math.sin(gamma) + Yw * math.cos(gamma)
+ tanlamda = -v/u/math.sqrt(1+(math.tan(math.pi/2-seta))**2)
+ lamda = math.atan(tanlamda)
+ k = math.tan(math.pi/2-lamda)
+ return k
+
+def get_k2(alpha,beta,Xw,Yw):
+ gamma = math.atan(1 / (math.tan(alpha) * math.tan(beta)))
+ seta = math.atan(1 / math.sqrt(pow(math.tan(beta), 2) + 1 / pow(math.tan(alpha), 2)))
+ u = Xw * math.cos(gamma) - Yw * math.sin(gamma)
+ v = Xw * math.sin(gamma) + Yw * math.cos(gamma)
+ tanlamda = -v / u / math.sqrt(1 + (math.tan(math.pi / 2 - seta)) ** 2)
+ lamda = math.atan(tanlamda)
+ k = math.tan(math.pi / 2 - lamda)
+ return k
+
+"""
+计算地面点的竖直垂线在图像中的截距
+参数:点在图像中的坐标x、y,斜率k
+返回值:截距b
+"""
+def get_b(x,y,k):
+ b = y - x * k
+ return b
+
+
+"""
+将检测到的路沿上侧数据点拟合为一条直线
+参数:上侧数据点坐标x_top,y_top
+返回值:拟合出直线的斜率slope、截距intercept、r方值
+"""
+def linear_regression(x, y):
+ slope, intercept, r_value, p_value, std_err = stats.linregress(x, y)
+ return slope, intercept, abs(r_value)
+
+"""
+计算图像中两条直线的交点
+参数: line1: 第一条直线的系数 (A1, B1, C1)
+ line2: 第二条直线的系数 (A2, B2, C2)
+返回值:交点在图像中的二维坐标x、y
+"""
+def find_intersection(line1, line2):
+ A1, B1, C1 = line1
+ A2, B2, C2 = line2
+
+ # 计算行列式
+ denominator = A1 * B2 - A2 * B1
+
+ if denominator == 0:
+ # 直线平行或重合
+ return None
+ else:
+ x = (B1 * C2 - B2 * C1) / denominator
+ y = (A2 * C1 - A1 * C2) / denominator
+ return (x, y)
diff --git a/Visual measurement-straight/py/calc_way.py b/Visual measurement-straight/py/calc_way.py
new file mode 100644
index 0000000..c5d7f4c
--- /dev/null
+++ b/Visual measurement-straight/py/calc_way.py
@@ -0,0 +1,198 @@
+import math
+
+import numpy as np
+
+import model
+
+model = model.Model()
+u0 = model.u0
+v0 = model.v0
+f = model.f
+H = model.H
+dx = model.dx
+dy = model.dy
+alpha = model.alpha
+beta = model.beta
+"""
+计算图像中一点到车辆坐标的距离
+参数:点在图像中的坐标x、y,相机的外部偏转角参数alpha、beta
+返回值:点在车辆坐标系下的二维坐标
+"""
+def calc_distance(x, y, alpha, beta):
+ Xp = (x - u0) * dx
+ Yp = -(y - v0) * dy
+ sq = math.sqrt(f * f + Yp * Yp)
+ eta = math.atan(Yp / f)
+ gamma = math.atan(1 / (math.tan(alpha) * math.tan(beta)))
+ seta = math.atan(1/math.sqrt(pow(math.tan(beta), 2) + 1 / pow(math.tan(alpha), 2)))
+ MP = H * math.fabs(Xp) / (math.sqrt(f ** 2 + Yp ** 2) * math.sin(seta + eta))
+ OM = H / math.tan(seta + eta)
+ epsilon = math.atan((H * Xp / (math.sqrt(f ** 2 + Yp ** 2) * math.sin(seta + eta))) / OM)
+ d = math.sqrt(MP ** 2 + OM ** 2)
+ Xw = d * math.cos(gamma + epsilon)
+ Yw = -d * math.sin(gamma + epsilon)
+ return Xw, Yw
+
+"""
+计算车辆坐标系下某点在图像中的位置
+参数:点在车辆坐标系下的坐标Xw、Yw,相机的外部偏转角参数alpha、beta
+返回值:点在图像中的二维坐标
+"""
+def calc_distance2(Xw, Yw, alpha, beta):
+ d = math.sqrt(Xw ** 2 + Yw ** 2)
+ seta = math.atan(1 / math.sqrt(pow(math.tan(beta), 2) + 1 / pow(math.tan(alpha), 2)))
+ gamma = math.atan(1 / (math.tan(alpha) * math.tan(beta)))
+ # 计算组合角度
+ angle = math.atan2(-Yw, Xw) # 使用atan2处理所有象限
+ # 解出epsilon
+ epsilon = angle - gamma
+ # 将结果调整到[-pi, pi]范围内
+ epsilon = (epsilon + math.pi) % (2 * math.pi) - math.pi
+ OM = math.fabs(d * math.cos(epsilon))
+ MP = math.fabs(d * math.sin(epsilon))
+ eta = math.atan(H / OM) - seta
+ Yp = math.tan(eta) * f
+ Xp = MP * math.sqrt(f ** 2 + Yp ** 2) * math.sin(seta + eta)/H
+ if epsilon > 0 :
+ Xp = Xp
+ else:
+ Xp = -Xp
+ x = Xp / dx +u0
+ y = -Yp / dy +v0
+ return int(x), int(y)
+
+
+
+"""
+计算图像中一点距离地面的高度
+参数:地面端点在图像中的坐标x_bot、y_bot,垂线上端点在图像中的坐标x_top、y_top,相机的外部偏转角参数alpha、beta
+返回值:点距离地面的高度
+"""
+def calc_height(x_bot,y_bot,x_top,y_top,alpha,beta):
+ Xp = (x_bot - u0) * dx
+ Yp = -(y_bot - v0) * dy
+ sq = math.sqrt(f * f + Yp * Yp)
+ eta = math.atan(Yp / f)
+ gamma = math.atan(1 / (math.tan(alpha) * math.tan(beta)))
+ seta = math.atan(1/math.sqrt(pow(math.tan(beta), 2) + 1 / pow(math.tan(alpha), 2)))
+ MP = H * math.fabs(Xp) / (math.sqrt(f ** 2 + Yp ** 2) * math.sin(seta + eta))
+ OM = H/math.tan(seta + eta)
+ Yp1 = -(y_top - v0) * dy
+ sq1 = math.sqrt(f * f + Yp1 * Yp1)
+ eta1 = math.atan(Yp1 / f)
+ # print(f"Yp1: {Yp1},eta1: {eta1}")
+ Zw = H - OM*math.tan(seta + eta1)
+ return Zw
+
+"""
+计算图像中车辆坐标系Xw、Yw所对应的轴线
+参数:
+返回值:轴线上点在图像中的二维坐标x、y
+"""
+def calc_zeros_yto0(val):
+ # 定义搜索范围和步长
+ x_range = np.linspace(0, 1280, 1000)
+ y_range = np.linspace(0, 960, 1000)
+
+ # 存储解
+ solutions = []
+ tolerance = 1e-3 # 容忍误差
+
+ # 网格搜索
+ for x in x_range:
+ for y in y_range:
+ error = equations_yto0(x, y, val)
+ if error < tolerance:
+ solutions.append((x, y))
+
+ # 去除近似重复的解
+ unique_solutions = []
+ for sol in solutions:
+ # 检查是否与已找到的解近似
+ is_unique = True
+ for usol in unique_solutions:
+ if np.allclose(sol, usol, atol=1):
+ is_unique = False
+ break
+ if is_unique:
+ unique_solutions.append(sol)
+ x = []
+ y = []
+ # print("找到的解:")
+ for sol in unique_solutions:
+ # print(f"x = {sol[0]:.4f}, y = {sol[1]:.4f}")
+ x.append(sol[0])
+ y.append(sol[1])
+ return x, y
+
+def equations_yto0(x, y, val):
+ Xp = (x - u0) * dx
+ Yp = -(y - v0) * dy
+ sq = math.sqrt(f * f + Yp * Yp)
+ eta = math.atan(Yp / f)
+ gamma = math.atan(1 / (math.tan(alpha) * math.tan(beta)))
+ seta = math.atan(1/math.sqrt(pow(math.tan(beta), 2) + 1 / pow(math.tan(alpha), 2)))
+ MP = H * math.fabs(Xp) / (math.sqrt(f ** 2 + Yp ** 2) * math.sin(seta + eta))
+ OM = H / math.tan(seta + eta)
+ epsilon = math.atan((H * Xp / (math.sqrt(f ** 2 + Yp ** 2) * math.sin(seta + eta))) / OM)
+ d = math.sqrt(MP ** 2 + OM ** 2)
+ Xw = d * math.cos(gamma + epsilon)
+ Yw = -d * math.sin(gamma + epsilon)
+ return (Yw - val)**2 # 误差平方和
+
+"""
+计算图像中车辆坐标系Yw=某一固定值所对应的轴线
+参数:
+返回值:轴线上点在图像中的二维坐标x、y
+"""
+def calc_zeros_xto0(val):
+ # 定义搜索范围和步长
+ x_range = np.linspace(0, 1280, 1000)
+ y_range = np.linspace(0, 960, 1000)
+
+ # 存储解
+ solutions = []
+ tolerance = 1e-3 # 容忍误差
+
+ # 网格搜索
+ for x in x_range:
+ for y in y_range:
+ error = equations_xto0(x, y, val)
+ if error < tolerance:
+ solutions.append((x, y))
+
+ # 去除近似重复的解
+ unique_solutions = []
+ for sol in solutions:
+ # 检查是否与已找到的解近似
+ is_unique = True
+ for usol in unique_solutions:
+ if np.allclose(sol, usol, atol=1):
+ is_unique = False
+ break
+ if is_unique:
+ unique_solutions.append(sol)
+ x = []
+ y = []
+ # print("找到的解:")
+ for sol in unique_solutions:
+ # print(f"x = {sol[0]:.4f}, y = {sol[1]:.4f}")
+ x.append(sol[0])
+ y.append(sol[1])
+ return x, y
+
+def equations_xto0(x, y, val):
+ Xp = (x - u0) * dx
+ Yp = -(y - v0) * dy
+ sq = math.sqrt(f * f + Yp * Yp)
+ eta = math.atan(Yp / f)
+ gamma = math.atan(1 / (math.tan(alpha) * math.tan(beta)))
+ seta = math.atan(1/math.sqrt(pow(math.tan(beta), 2) + 1 / pow(math.tan(alpha), 2)))
+ MP = H * math.fabs(Xp) / (math.sqrt(f ** 2 + Yp ** 2) * math.sin(seta + eta))
+ OM = H / math.tan(seta + eta)
+ epsilon = math.atan((H * Xp / (math.sqrt(f ** 2 + Yp ** 2) * math.sin(seta + eta))) / OM)
+ d = math.sqrt(MP ** 2 + OM ** 2)
+ Xw = d * math.cos(gamma + epsilon)
+ Yw = -d * math.sin(gamma + epsilon)
+ return (Xw-val)**2 # 误差平方和
+
diff --git a/Visual measurement-straight/py/get_data.py b/Visual measurement-straight/py/get_data.py
new file mode 100644
index 0000000..4f56b25
--- /dev/null
+++ b/Visual measurement-straight/py/get_data.py
@@ -0,0 +1,150 @@
+import numpy as np
+import pandas as pd
+import calc_way
+from scipy import stats
+import calc_slope_line
+import matplotlib.pyplot as plt
+import model
+import os
+# 数据截断线
+model = model.Model()
+limit_slope = model.limit_slope
+limit_intercept = model.limit_intercept
+def grid_downsample(points, cell_size=15):
+ """网格化降采样,保持空间结构"""
+ df = pd.DataFrame(points, columns=['x', 'y'])
+ df['x_grid'] = (df['x'] // cell_size) * cell_size
+ df['y_grid'] = (df['y'] // cell_size) * cell_size
+ sampled = df.groupby(['x_grid', 'y_grid']).first().reset_index()
+ return sampled[['x', 'y']].values
+
+"""
+读取yolo网络识别路沿的坐标数据,筛选出目标区域的数据点,并将路沿上下侧数据分离
+参数:保存数据的txt文件路径
+返回值:在目标区域内的下侧数据点坐标x_bot、y_bot,上侧数据点坐标x_top,y_top
+"""
+def get_data(txt_name):
+ # 加载数据
+ data = np.loadtxt(txt_name)
+ int_data = data.astype(int)
+
+ # 网格化降采样
+ grid_sampled = grid_downsample(int_data, cell_size=20)
+
+ # 数据截断
+ x = []
+ y = []
+ for i in range(grid_sampled.shape[0]):
+ grid_sampled[i][1] = 960 - int(grid_sampled[i][1])
+ if limit_slope * int(grid_sampled[i][0]) + limit_intercept - int(grid_sampled[i][1]) < 0:
+ continue
+ x.append(int(grid_sampled[i][0]))
+ y.append(int(grid_sampled[i][1]))
+ x = np.array(x)
+ y = np.array(y)
+
+ # 原始数据粗分类
+ slope, intercept, r_2 = calc_slope_line.linear_regression(x, y)
+ y_pred = slope * x + intercept
+ x_bot = []
+ y_bot = []
+ x_top = []
+ y_top = []
+ for i in range(len(x)):
+ if x[i] * slope + intercept - y[i] > 0:
+ x_bot.append(x[i])
+ y_bot.append(y[i])
+ else:
+ x_top.append(x[i])
+ y_top.append(y[i])
+ x_bot = np.array(x_bot)
+ y_bot = np.array(y_bot)
+ x_top = np.array(x_top)
+ y_top = np.array(y_top)
+ slope_bot, intercept_bot, r2_bot = calc_slope_line.linear_regression(x_bot, y_bot)
+ slope_top, intercept_top, r2_top = calc_slope_line.linear_regression(x_top, y_top)
+ print(f"未清洗数据拟合上下沿:r2_bot = {r2_bot},r2_top = {r2_top}")
+
+ # 第一次数据清洗,消除误识别点
+ # 计算残差
+ residuals = y - y_pred
+ # 计算残差的标准差 (MSE的平方根)
+ residual_std = np.sqrt(np.sum(residuals ** 2) / (len(x) - 2))
+ standardized_residuals = residuals / residual_std
+ # 设置阈值 (常用 2.5-3.0 个标准差)
+ threshold = 2.0
+ # 标记异常点
+ outlier_mask = np.abs(standardized_residuals) > threshold
+ outliers_x = x[outlier_mask]
+ outliers_y = y[outlier_mask]
+ print(f"第一次数据清洗发现 {np.sum(outlier_mask)} 个异常点:")
+ for i, (x_val, y_val) in enumerate(zip(outliers_x, outliers_y)):
+ print(f"点 {i + 1}: x={x_val}, y={y_val}, 残差={residuals[outlier_mask][i]:.2f}")
+ # 剔除异常点
+ clean_x = x[~outlier_mask]
+ clean_y = y[~outlier_mask]
+ clean_slope, clean_intercept, clean_r_2 = calc_slope_line.linear_regression(clean_x, clean_y)
+ print(f"清洗数据后整体拟合参数r_2 = {r_2}")
+
+ # 第一次数据清洗后的数据再分类
+ x_bot_clean = []
+ y_bot_clean = []
+ x_top_clean = []
+ y_top_clean = []
+ for i in range(len(clean_x)):
+ if clean_x[i] * clean_slope + clean_intercept - clean_y[i] > 0:
+ x_bot_clean.append(clean_x[i])
+ y_bot_clean.append(clean_y[i])
+ else:
+ x_top_clean.append(clean_x[i])
+ y_top_clean.append(clean_y[i])
+ x_bot_clean = np.array(x_bot_clean)
+ y_bot_clean = np.array(y_bot_clean)
+ x_top_clean = np.array(x_top_clean)
+ y_top_clean = np.array(y_top_clean)
+
+ # 第二次数据清洗,消除误分类点
+ clean_slope_bot, clean_intercept_bot, clean_r2_bot = calc_slope_line.linear_regression(x_bot_clean, y_bot_clean)
+ clean_slope_top, clean_intercept_top, clean_r2_top = calc_slope_line.linear_regression(x_top_clean, y_top_clean)
+ print(f"清洗数据后上下沿拟合参数clean_r2_bot = {clean_r2_bot},clean_r2_top = {clean_r2_top}")
+ # 绘制拟合线
+ y_bot_pred = clean_slope_bot * x_bot_clean + clean_intercept_bot
+ y_top_pred = clean_slope_top * x_top_clean + clean_intercept_top
+ # 计算残差
+ residuals_bot = y_bot_clean - y_bot_pred
+ residuals_top = y_top_clean - y_top_pred
+ # 计算残差的标准差 (MSE的平方根)
+ residual_std_bot = np.sqrt(np.sum(residuals_bot ** 2) / (len(x_bot_clean) - 2))
+ residual_std_top = np.sqrt(np.sum(residuals_top ** 2) / (len(x_top_clean) - 2))
+ # 计算标准化残差 (Z-score)
+ standardized_residuals_bot = residuals_bot / residual_std_bot
+ standardized_residuals_top = residuals_top / residual_std_top
+ # 设置阈值 (常用 2.5-3.0 个标准差)
+ threshold = 1.0
+ # 标记异常点
+ outlier_mask_bot = np.abs(standardized_residuals_bot) > threshold
+ outlier_mask_top = np.abs(standardized_residuals_top) > threshold
+ outliers_x_bot = x_bot_clean[outlier_mask_bot]
+ outliers_y_bot = y_bot_clean[outlier_mask_bot]
+ outliers_x_top = x_top_clean[outlier_mask_top]
+ outliers_y_top = y_top_clean[outlier_mask_top]
+ print(f"第二次数据清洗下沿发现 {np.sum(outlier_mask_bot)} 个异常点:")
+ # for i, (x_val, y_val) in enumerate(zip(outliers_x_bot, outliers_y_bot)):
+ # print(f"点 {i + 1}: x={x_val}, y={y_val}, 残差={residuals_bot[outlier_mask_bot][i]:.2f}")
+ print(f"第二次数据清洗上沿发现 {np.sum(outlier_mask_top)} 个异常点:")
+ # for i, (x_val, y_val) in enumerate(zip(outliers_x_top, outliers_y_top)):
+ # print(f"点 {i + 1}: x={x_val}, y={y_val}, 残差={residuals_top[outlier_mask_top][i]:.2f}")
+ # 剔除异常点
+ x_bot_clean = x_bot_clean[~outlier_mask_bot]
+ y_bot_clean = y_bot_clean[~outlier_mask_bot]
+ x_top_clean = x_top_clean[~outlier_mask_top]
+ y_top_clean = y_top_clean[~outlier_mask_top]
+
+ # 判断数据的有效性
+ clean_slope_bot, clean_intercept_bot, clean_r2_bot = calc_slope_line.linear_regression(x_bot_clean, y_bot_clean)
+ clean_slope_top, clean_intercept_top, clean_r2_top = calc_slope_line.linear_regression(x_top_clean, y_top_clean)
+ print(f"清洗数据后上下沿拟合参数clean_r2_bot = {clean_r2_bot},clean_r2_top = {clean_r2_top}")
+ if ((1-clean_r2_bot) > 1e-3) or ((1-clean_r2_top) > 1e-3):
+ print("无效数据")
+ return 0, None, None, None, None
+ return 1, x_bot_clean, y_bot_clean, x_top_clean, y_top_clean
diff --git a/Visual measurement-straight/py/measure_lib.py b/Visual measurement-straight/py/measure_lib.py
new file mode 100644
index 0000000..7a9c754
--- /dev/null
+++ b/Visual measurement-straight/py/measure_lib.py
@@ -0,0 +1,45 @@
+import math
+import numpy as np
+import matplotlib.pyplot as plt
+import calc_way
+import get_data
+import calc_slope_line
+import cv2
+import model
+import os
+import time
+
+model = model.Model()
+alpha = model.alpha
+beta = model.beta
+
+
+def vs_measurement(txt_name, position=900):
+ # 加载数据
+ state, x_bot, y_bot, x_top, y_top = get_data.get_data(txt_name)
+ if state == 0:
+ return 0, None, None
+ # 拟合上下沿
+ slope_bot, intercept_bot, r2_bot = calc_slope_line.linear_regression(x_bot, y_bot)
+ slope_top, intercept_top, r2_top = calc_slope_line.linear_regression(x_top, y_top)
+ Xw_bot = []
+ Yw_bot = []
+ for i in range(len(x_bot)):
+ Xw, Yw = calc_way.calc_distance(x_bot[i], y_bot[i], alpha, beta)
+ Xw_bot.append(Xw)
+ Yw_bot.append(Yw)
+ slope_Xw, intercept_Xw, r2_Xw = calc_slope_line.linear_regression(Xw_bot, Yw_bot)
+ # 计算路沿与车身夹角
+ angle = math.atan(slope_Xw)
+ # 位置修正
+ position = position + model.x
+ # 确认目标点位置并计算高度
+ Yw_pos = slope_Xw * position + intercept_Xw
+ x_pos, y_pos = calc_way.calc_distance2(position, Yw_pos, alpha, beta)
+ k_pos = calc_slope_line.get_k(alpha, beta, x_pos, y_pos)
+ b_pos = calc_slope_line.get_b(x_pos, y_pos, k_pos)
+ x_pos_top, y_pos_top = calc_slope_line.find_intersection((k_pos, -1, b_pos), (slope_top, -1, intercept_top))
+ Zw_pos = calc_way.calc_height(x_pos, y_pos, x_pos_top, y_pos_top, alpha, beta)
+ distance_pos = -(Yw_pos + model.y) * math.cos(angle)
+ return 1, Zw_pos, distance_pos
+
diff --git a/Visual measurement-straight/py/model.py b/Visual measurement-straight/py/model.py
new file mode 100644
index 0000000..a7dd362
--- /dev/null
+++ b/Visual measurement-straight/py/model.py
@@ -0,0 +1,25 @@
+import numpy as np
+from scipy.optimize import minimize
+
+#保存相机内参、外参等参数
+class Model:
+ def __init__(self):
+ # 内参
+ self.K = np.array([[801.8319, 0, 647.8920],
+ [0, 801.7619, 532],
+ [0, 0, 1]])
+ self.f = 3.6
+ self.H = 1019.0000170167332
+ self.dx = self.f / self.K[0, 0]
+ self.dy = self.f / self.K[1, 1]
+ self.u0 = self.K[0, 2]
+ self.v0 = self.K[1, 2]
+ # 外参
+ self.alpha = 0.7072338025822084
+ self.beta = 0.9077237961986776
+ # 位置修正
+ self.y = -70
+ self.x = 22
+ # 数据截断线
+ self.limit_slope = 0.3259949467095897
+ self.limit_intercept = 452.86565535382374