^3O($B9=M=A=9E}S0RhX&s5!|MbWF0k7SQ*aAQyAe
zRD@XiuOc
zr1GKOzJM6+jzrH5h}|!#_CQZhC=yiJKxb!1XLmF-;a0702Rc~s%fV1k+(EqeOy?Y;2srjNYu}I^M>C^zLVabVQ$y_!@G56
zO+||Pr>|(vK^1NMhNav^GwwBCxT|D$Rp!v=?&`Tb%2zg*PjQGVvd(zz6YqLxkL8Bh
zB%3NoHH{Sioh_Zg)1R}Y(3-D&4qEdh0*M{**Rwl&e%F98FcSGY0Fj&|(wdV*G8h;>
z3$QZTi((w)V)*A060kDg0jxPbjpQPcqT#*j`wSZs0h~=3*X!#vMo-6ZYE2l)0*y#k
zL*AkGJgX)qJ%m11+RBpgo2@KMGFj74$Mi%zVbNpWmLW@FbxC7iOfs2eNv3fRV0H-?
z^LBh)>9YgQhCxX&9SAeLqyzXPgwgG}D;?_7M!Mlb^!ah_51#$RAC+9is_wse(rtx*+
zqZNIfm|XgG;;+xYPPqA0L9#K`o7^|els>1#KWa)W?1oM0w+Y|_P{LjaaFW{(Bv;sv
zN)DjlAOtI5o|@E2s5^>;eue_=2JbofGNFI~wa>{HtQ76hA^SP`T14NTlP~J>_fNh&
z1o9;uhm|S30l|0koF-HS1@}<3=RD{C{$ChwJ`qj6mTF4wnP!SLjPe(1A3RlCq_3-I
zKm$e9*3}&hb*i>wD9TcB?UGr$Ro1SNwToxuQeYRw=-)Wnhequ-SG$PG5@7_4+A5jI
z!eaL3hTRLj^mT^z3$0L&E+C^fAegh#H2sKjJ)!cSQ0~WULBcldO1iFj;#!cw0Ej!>?+v~?7-!}z49$Gw^w9Zp>$JBbdez)E?Tx4T
axpM8TlJU2GFq7M`)kj_n!Sah#tfB#zUNA43QPNZJI_@)HVdfi`8KiwWzRj0p~oV`j#X
zq~l$;>Z(bhjU(E{Y+-ApK7`~-;e7>&`m
z4|C#jsdB32l5i4Gs(hLOty7DqFa^#6xlK2qcN)@lq?45EC?}=DJY+wNwfOotUEw65
zy>f9YhB-}8Z_86}rZu$s4cu9%$7tdW%xM|M9oqW{xX*!08kTc&Y_DPrs@A}*hWp`n
z@HmfgI27{LpNmS@@nANs>Qh}+f1GX&sdyEQmqR;`k3bzfK_b)Cj5504?de+?@M4#y#MX3i_DAVBqMmq&engSWY4t59{GvUO(*_lC-DX
zzMzM75Rx`9==F1~r0@0kd@j~I;*nJJF{yxc4+MN3nk7IP6PZGqvQKG2N@
z<(mV;k{Ma%q6bg=Q(7=3po9@__E+!-V{>|PymG8EaxBJ9Zjbd(>s!LxlEtMFc4B9A
zSS)Uw+$~yL!`s3`ukBf?+Eh5+Kh_^@jO`N2*Cb5MGp3EAX=B3FHe=c*nzkiOUE!Tc
zvM91WYK?S=q-}<*6UjP(sCx(+V7-|z1%`rK4&i#BmJeYixI#FO=LOL$)z^&oPR38X>bwAQ*(M79KC@K=Wup<)~7ShrLmkDuR&1(^c*5o2b
zAb7JaXyI@sMRcCN3r0apkt4tgLp**x>g*Uj+$k$bn%!kfTCc`B@pN${C;HwX_DUQIK!sE8%J4tKew{t7UD|S+?+f;*w60
ziDPG16Bx40jLEjykZozYdYKro@zsisa=x&KYC=!uXk_8_;Qh=;4UR#+sD*na+;wo*
z=gl29MK`6)FTbzMAXhGftji$lmO<8MkoC*NKrO#=K@2p68bgj{`qc6D3;L`I
ztqwKi#7zIVkbuzzN=9~GI{2Dp8tmW(ZW|>--W9LuL
zGHaL2G8ymW$ucef)tKv+95ds+JY%ls*YYiLX4}B871^I=*-anN6}Afcn6AUx(vOISRL?9S)QSCX7~xkdK?=r
z2OP2#tU9KF&0od7`zVPxaK-`!11}TIdjmf9?xT%Ga1}h}FMc#1|IPfh^Yiab&Ao2Z_UNe%>VJL@2~%kS?@YJ$k{vXBZelRVG9QIeIyA!@#Jeu5N+M>UA;ON
z56@q{oE9Vty*oZ}@0}@o5Pi$r=i(pTz5e>$c>Lb$pLYZa)aUij?IT2oeW-JUXtED?
z2GRbb$v*$dnY*8T(mA4TUVkizUXr;Q-i^F7|K7E^FVCe^y*vIo49K|FgI2)}k76^X
z9=G3>wXCoyQ@AqbG_3DmSN?Hp@6i{Z
z4Wg~n{Q21Yd!wo8a%g1d?1RfuEUEkfslYerrnwGcFq??AxgrzMS>Vcf9g?REQlQ&^YJ>-R_&^mH>W
z#?uEIch)=Tm()IwU(y5^5F!!mNrscuY;ZtQyN|;5-Qy1qcwk2k`iyu3j!lvdbaeUA
zNdSTN)shan$vbR`lDF6C4L*ZTK^U~>lyEPoE^DjL>Zt}7qCY4>EeGHd%JAb5gPw
z)F6y0>oOUUw3wa@Z%yh9;jOdAqR8eM
zYn^DVi@8N><2OdfD4wjcf4CvWPi?*Y>#37sLwlmOL#*l;r6S#9<|r=eDrU*ji`Hn*
zWX%QFWVcwgHc`G#B-dSgRwTFmW&h1e;o!@EtriYB#odP!J9b&Dkp2}|R&&ap(x*E+`o5;Lv(fdZXT*NEi|=sv46WErros%fUO
zQ>^TayKh($mD@$4JiKGHJ<^*5^SZ|lMaxCwli}x*lqtMp(E}#n-3^hG=i1&m0)~^+
zX!qF0NZr`xs6!-cW9~`(GA)v;CcAHu%}L5KzGrMt)DmsEP&`f97VuwFwmZwTKQLmZ
z6+f6Tqj`MS*sjQdGcSa9%u=SuVj9|J8g_{dyKa^UFCIuV92AL_;oYO5=&{+QTL`^C
zwN0IxIv(%8S$b0^7`ms4mmcUaOHt&x2}5}IoVDcqQxi{JT>nAag|?Vu^5o^_#EO<{
z`{R~B9gMsG;1pMENm#dr_av+8Co9FOR)MIvQ)o@~Ynx~jD%K^eEmLrm*$EgBx_P2H
z{Id)(i-vE!TJ&k@mC`9H;ph|^pNabg!`^9P-)vR=hwNm>)JURX+c#C)MP2zQKK9J$
zj!5gQ&KMyg-BHWLfymD2#+W90Iz~^nPBu)H$J=l8h?`y#_WxYmbU-+GNa%11ro&(B
zdLF1TTNe&dZ7z&dMvS7#21lbNbB3O6?wo0UL2Q0Oc=2W7&?|{%w@9oGKOd=%IbA78U0{QZaJ++~#kLb`&HW2?mCrtscmhMFrYKYx1S^hM@_Qx{If1|Xl*Kb@#<
zzrO#*ibUsL;l*yTvs)=M_FQ7^PN8|%O_N}Fd7AhI%(%uN
zP)jw#!n4|h>a~Jxtyr`Ug;&*Ma{migCiRBV&11Ic##_2-$a2(Wf1J2gw;7h-sDD~l
zl{7b^TzDe7X_A~eERb6SVhd9WFB=Db-;9bPNdw<=fnf$6=E6T3^zp+ouDW5
z8on7dY_As}=-&f_4zDE3fSU{W2H`X4DEvl26m(6M>VOZv+Xe<{k55t!vXW*mBAFW0
z7kwkkAF%Q}?+PYsl71a-hAu&$=NX0r5Zauu-SBvz#&P^MR&*P)-p0(|Vr93n(r>Zq
z+Zc5l)8ElqM>XT*7RkjCR20@<3I!inTA_47(Mz34Hc`X}4rKoMX1;&spJe_#ZfR*E
zAS>sT;S5K}TLhu-b)&Whj0#bR!Wbmaz{(n|$>lkQuw*AuxN)NJci4cP=RHOgW)SQZ
z85%eBqHgM=>R=EKG*bt)6c`nv`dRa=!i@?-G85lQVVcq_z>
zV#n&GkravshrPytW_`_p=I+#Yj1ei?q40n(t1pTGC~TXIkz-i%7Xfd?_z_ZKO96~F
z6WApQq5r=!H2jnys{4m(apXd!hMy|^?o^F+UOeO}O|YvmLd}5u0YHzE00bQbqO<@?
zL?{WUB|@!$k|FYlxMZ)$X@}<4KDd!QowM=nYTJ&yf3|X9JSTDiKL0r;Wgx1jAzXL*
zks16f+S4Z}iJW$7VKz>x=W-(JM|}l0cIvCTe+G+$er!+sI(y;K&8r^19RDa%Hesme~-R!z0&_99K!hTXr$EU#eV
z9qF??8kZ!U0-Nor5jM4Yf&ArTKIMF*i=zARY{^vk_4@m4YU)IbOPR25V5ns
zy+!Qhu-MB`;nnjM8V(N4IH|GiLVv(PuP9KVST-%s#>uBiXHd7izLMqo|0x@P20@xG
ow4&5BzBxF=7P}h)_<@}cQ1cwaFmFg|igbU8rJ4Q(0?Oa^9kuB*O#lD@
literal 0
HcmV?d00001
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