12. RANSAC点云多平面拟合分割
本文看了博客RANSAC点云多平面拟合分割-CSDN博客的文章,该博客将多平面拟合分割讲的很详细了,这里只是作为笔记用。
1.RANSAC算法原理
三维平面拟合(最小二乘法)
具体实现见下面代码:
def SVD(points):# 二维,三维均适用# 二维直线,三维平面pts = points.copy()# 奇异值分解c = np.mean(pts, axis=0)A = pts - c # shift the pointsA = A.T #3*nu, s, vh = np.linalg.svd(A, full_matrices=False, compute_uv=True) # A=u*s*vhnormal = u[:,-1]# 法向量归一化nlen = np.sqrt(np.dot(normal,normal))normal = normal / nlen# normal 是主方向的方向向量 与PCA最小特征值对应的特征向量是垂直关系# u 每一列是一个方向# s 是对应的特征值# c >>> 点的中心# normal >>> 拟合的方向向量return u,s,c,normalclass plane_model(object):def __init__(self):平面模型参数(平面上任意一点 cx, cy, cx) + (平面法向量 nx, ny, nz)self.parameters = Nonedef calc_inliers(self,points,dst_threshold):# 根据点到平面的距离计算内点和外点c = self.parameters[0:3]n = self.parameters[3:6]dst = abs(np.dot(points-c,n))ind = dst<dst_thresholdreturn inddef estimate_parameters(self,pts):# 最小二乘法估算平面模型# 只有三个点时,可以直接计算num = pts.shape[0]if num == 3:c = np.mean(pts,axis=0)l1 = pts[1]-pts[0]l2 = pts[2]-pts[0]n = np.cross(l1,l2)scale = [n[i]**2 for i in range(n.shape[0])]#print(scale)n = n/np.sqrt(np.sum(scale))else:_,_,c,n = SVD(pts)params = np.hstack((c.reshape(1,-1),n.reshape(1,-1)))[0,:]self.parameters = paramsreturn paramsdef set_parameters(self,parameters):self.parameters = parameters
RANSAC算法
参考:RANSAC介绍(Matlab版直线拟合+平面拟合)_sylvester的博客-CSDN博客_matlab ransac
RANSAC是一种算法,一种思想,不仅仅可以用于拟合平面,实际上还有很多用处。
这里的算法如下算法
具体实现见下面代码:
# 注意这里并没有根据内点比例和模型可靠的概率动态调整最大迭代次数
def ransac_planefit(points, ransac_n, max_dst, max_trials=1000, stop_inliers_ratio=1.0, initial_inliers=None):# RANSAC 平面拟合pts = points.copy()num = pts.shape[0]cc = np.mean(pts,axis=0)iter_max = max_trialsbest_inliers_ratio = 0 #符合拟合模型的数据的比例best_plane_params = Nonebest_inliers = Nonebest_remains = Nonefor i in range(iter_max):sample_index = random.sample(range(num),ransac_n)sample_points = pts[sample_index,:]plane = plane_model()plane_params = plane.estimate_parameters(sample_points)# 计算内点 外点index = plane.calc_inliers(points,max_dst)inliers_ratio = pts[index].shape[0]/numif inliers_ratio > best_inliers_ratio:best_inliers_ratio = inliers_ratiobest_plane_params = plane_paramsbset_inliers = pts[index]bset_remains = pts[index==False]if best_inliers_ratio > stop_inliers_ratio:# 检查是否达到最大的比例print("iter: %d\n" % i)print("best_inliers_ratio: %f\n" % best_inliers_ratio)breakreturn best_plane_params,bset_inliers,bset_remains
多平面拟合
针对一个三维点云,进行多平面拟合的基本思想是:首先使用RANSAC平面拟合算法从点云中提取出一个平面,对于剩下的外点,继续循环RANSAC平面拟合算法提取平面,直到提取出所有的平面,循环时注意循环停止条件的设置
具体实现见下面代码:
def ransac_plane_detection(points, ransac_n, max_dst, max_trials=1000, stop_inliers_ratio=1.0, initial_inliers=None, out_layer_inliers_threshold=230, out_layer_remains_threshold=230):# out_layer_inliers_threshold --> 每一次RANSAC提取出来的平面最少含有的内点数量# out_layer_remains_threshold --> 每一次RANSAC提取平面后剩余外点的最少数量,少于这个值,则停止循环inliers_num = out_layer_inliers_threshold + 1remains_num = out_layer_remains_threshold + 1plane_set = []plane_inliers_set = []plane_inliers_num_set = []data_remains = np.copy(points)i = 0while inliers_num>out_layer_inliers_threshold and remains_num>out_layer_remains_threshold:# robustly fit line only using inlier data with RANSAC algorithmbest_plane_params,pts_inliers,pts_outliers = ransac_planefit(data_remains, ransac_n, max_dst, max_trials=max_trials, stop_inliers_ratio=stop_inliers_ratio)inliers_num = pts_inliers.shape[0]remains_num = pts_outliers.shape[0]if inliers_num>out_layer_inliers_threshold:plane_set.append(best_plane_params)plane_inliers_set.append(pts_inliers)plane_inliers_num_set.append(inliers_num)i = i+1print('------------> %d <--------------' % i)print(best_plane_params)data_remains = pts_outliers# sortingplane_set = [x for _, x in sorted(zip(plane_inliers_num_set,plane_set), key=lambda s: s[0], reverse=True)]plane_inliers_set = [x for _, x in sorted(zip(plane_inliers_num_set,plane_inliers_set), key=lambda s: s[0], reverse=True)]return plane_set, plane_inliers_set, data_remains
案例
点云
这里给出一个立方体的三维点云,如下图所示,资源可以从下面下载:
链接: https://pan.baidu.com/s/1G2g6sYp46-FWnDX20uphvg?pwd=dzbn
提取码: dzbn
拟合结果
6个平面
红色边框是6个平面的参数,即 (cx,cy,cz,nx,ny,nz)
绘图显示
不同颜色代表不同的平面
2.三维点云的全部代码
包含一些读取txt和绘制三维点的代码,如下:
import os
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3Dimport math
import randomdef strlist2num(dl):#将字符串列表转化为浮点型列表data = []for i in range(len(dl)):if dl[i]=='nan'or dl[i]=='NaN':raise ValueError('data is nan')data.append(float(dl[i]))return np.array(data)def read_txt(path,row_skip=0,split_char=',',num_range=None, verbose=False):"""read txt file into a np.ndarray.Input:------path: file pathrow_skip: skip the first rows to read datasplit_char: spliting characternum_range: data range of each numberOutput:------data: data read. data is np.array([]) when reading error happeneddata is np.array([]) when nan or NaN appearsdata is np.array([]) when any number is out of range"""try:f = open(path,'r',encoding='utf-8')line_list = f.readlines()read_lines_num = len(line_list)for i in range(read_lines_num):line_list[i] = line_list[i].rstrip()i = row_skip # 从第三行开始读取data = []while i <= read_lines_num-1:data_str = line_list[i].split(split_char)data.append(strlist2num(data_str))i = i + 1f.close()except:if verbose:print("type data of [{}] is wrong...".format(path))data = np.array([])f.close()data = np.array(data)if num_range is not None:if np.any(data<num_range[0]) or np.any(data>num_range[1]):data = np.array([])if verbose:print("data of [{}] is out of range...".format(path))return datadef SVD(points):# 二维,三维均适用# 二维直线,三维平面pts = points.copy()# 奇异值分解c = np.mean(pts, axis=0)A = pts - c # shift the pointsA = A.T #3*nu, s, vh = np.linalg.svd(A, full_matrices=False, compute_uv=True) # A=u*s*vhnormal = u[:,-1]# 法向量归一化nlen = np.sqrt(np.dot(normal,normal))normal = normal / nlen# normal 是主方向的方向向量 与PCA最小特征值对应的特征向量是垂直关系# u 每一列是一个方向# s 是对应的特征值# c >>> 点的中心# normal >>> 拟合的方向向量return u,s,c,normalclass plane_model(object):def __init__(self):self.parameters = Nonedef calc_inliers(self,points,dst_threshold):c = self.parameters[0:3]n = self.parameters[3:6]dst = abs(np.dot(points-c,n))ind = dst<dst_thresholdreturn inddef estimate_parameters(self,pts):num = pts.shape[0]if num == 3:c = np.mean(pts,axis=0)l1 = pts[1]-pts[0]l2 = pts[2]-pts[0]n = np.cross(l1,l2)scale = [n[i]**2 for i in range(n.shape[0])]#print(scale)n = n/np.sqrt(np.sum(scale))else:_,_,c,n = SVD(pts)params = np.hstack((c.reshape(1,-1),n.reshape(1,-1)))[0,:]self.parameters = paramsreturn paramsdef set_parameters(self,parameters):self.parameters = parametersdef ransac_planefit(points, ransac_n, max_dst, max_trials=1000, stop_inliers_ratio=1.0, initial_inliers=None):# RANSAC 平面拟合pts = points.copy()num = pts.shape[0]cc = np.mean(pts,axis=0)iter_max = max_trialsbest_inliers_ratio = 0 #符合拟合模型的数据的比例best_plane_params = Nonebest_inliers = Nonebest_remains = Nonefor i in range(iter_max):sample_index = random.sample(range(num),ransac_n)sample_points = pts[sample_index,:]plane = plane_model()plane_params = plane.estimate_parameters(sample_points)# 计算内点 外点index = plane.calc_inliers(points,max_dst)inliers_ratio = pts[index].shape[0]/numif inliers_ratio > best_inliers_ratio:best_inliers_ratio = inliers_ratiobest_plane_params = plane_paramsbset_inliers = pts[index]bset_remains = pts[index==False]if best_inliers_ratio > stop_inliers_ratio:# 检查是否达到最大的比例print("iter: %d\n" % i)print("best_inliers_ratio: %f\n" % best_inliers_ratio)breakreturn best_plane_params,bset_inliers,bset_remainsdef ransac_plane_detection(points, ransac_n, max_dst, max_trials=1000, stop_inliers_ratio=1.0, initial_inliers=None, out_layer_inliers_threshold=230, out_layer_remains_threshold=230):inliers_num = out_layer_inliers_threshold + 1remains_num = out_layer_remains_threshold + 1plane_set = []plane_inliers_set = []plane_inliers_num_set = []data_remains = np.copy(points)i = 0while inliers_num>out_layer_inliers_threshold and remains_num>out_layer_remains_threshold:# robustly fit line only using inlier data with RANSAC algorithmbest_plane_params,pts_inliers,pts_outliers = ransac_planefit(data_remains, ransac_n, max_dst, max_trials=max_trials, stop_inliers_ratio=stop_inliers_ratio)inliers_num = pts_inliers.shape[0]remains_num = pts_outliers.shape[0]if inliers_num>out_layer_inliers_threshold:plane_set.append(best_plane_params)plane_inliers_set.append(pts_inliers)plane_inliers_num_set.append(inliers_num)i = i+1print('------------> %d <--------------' % i)print(best_plane_params)data_remains = pts_outliers# sortingplane_set = [x for _, x in sorted(zip(plane_inliers_num_set,plane_set), key=lambda s: s[0], reverse=True)]plane_inliers_set = [x for _, x in sorted(zip(plane_inliers_num_set,plane_inliers_set), key=lambda s: s[0], reverse=True)]return plane_set, plane_inliers_set, data_remainsdef show_3dpoints(pointcluster,s=None,colors=None,quiver=None,q_length=10,tri_face_index=None):# pointcluster should be a list of numpy ndarray# This functions would show a list of pint cloud in different colorsn = len(pointcluster)if colors is None:colors = ['r','g','b','c','m','y','k','tomato','gold']if n < 10:colors = np.array(colors[0:n])else: colors = np.random.rand(n,3)fig = plt.figure()ax = fig.add_subplot(projection='3d')if s is None:s = np.ones(n)*2for i in range(n):ax.scatter(pointcluster[i][:,0],pointcluster[i][:,1],pointcluster[i][:,2],s=s[i],c=[colors[i]],alpha=0.6)if not (quiver is None):c1 = [random.random() for _ in range(len(quiver))]c2 = [random.random() for _ in range(len(quiver))]c3 = [random.random() for _ in range(len(quiver))]c = []for i in range(len(quiver)):c.append((c1[i],c2[i],c3[i]))cp = []for i in range(len(quiver)):cp.append(c[i])cp.append(c[i])c = c + cpax.quiver(quiver[:,0],quiver[:,1],quiver[:,2],quiver[:,3],quiver[:,4],quiver[:,5],length=q_length,arrow_length_ratio=.2,pivot='tail',normalize=False,color=c)if not (tri_face_index is None):for i in range(len(tri_face_index)):for j in range(tri_face_index[i].shape[0]):index = tri_face_index[i][j].tolist()index = index + [index[0]]ax.plot(*zip(*pointcluster[i][index]))ax.set_xlabel('x')ax.set_ylabel('y')ax.set_zlabel('z')#ax.set_ylim([-20,60])plt.show()return 0if __name__ == "__main__":path = "./files/multiplane_detection/cubic15.txt"pcd = read_txt(path, row_skip=1, split_char=' ')pcd = pcd[:,:3]plane_set, plane_inliers_set, data_remains = ransac_plane_detection(pcd, 3, 5, max_trials=1000, stop_inliers_ratio=1.0, initial_inliers=None,out_layer_inliers_threshold=230, out_layer_remains_threshold=230)plane_set = np.array(plane_set)print("================= 平面参数 ====================")print(plane_set)# 绘图show_3dpoints(plane_inliers_set)print("over!!!")
3.二维点的数据拟合直线的全部代码
代码如下:
import os
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3Dimport math
import random# 1.获取二维数据
def ply_txt(files):outs = []with open(files, 'r') as file:for line in file:ss = line.strip().split(" ")outs.append([float(ss[2]),float(ss[0])])return outs#2. svd求法向量
def SVD(points):# 二维,三维均适用# 二维直线,三维平面pts = points.copy()# 奇异值分解c = np.mean(pts, axis=0)A = pts - c # shift the pointsA = A.T # 3*nu, s, vh = np.linalg.svd(A, full_matrices=False, compute_uv=True) # A=u*s*vhnormal = u[:, -1]# 法向量归一化nlen = np.sqrt(np.dot(normal, normal))normal = normal / nlen# normal 是主方向的方向向量 与PCA最小特征值对应的特征向量是垂直关系# u 每一列是一个方向# s 是对应的特征值# c >>> 点的中心# normal >>> 拟合的方向向量return u, s, c, normalclass plane_model(object):def __init__(self):self.parameters = Nonedef calc_inliers(self, points, dst_threshold):c = self.parameters[0:2]n = self.parameters[2:4]dst = abs(np.dot(points - c, n))ind = dst < dst_thresholdreturn inddef estimate_parameters(self, pts):num = pts.shape[0]# print("sssssssssss: ",pts)if num == 3:c = np.mean(pts, axis=0)l1 = pts[1] - pts[0]l2 = pts[2] - pts[0]n = np.cross(l1, l2)scale = [n[i] ** 2 for i in range(n.shape[0])]# print(scale)n = n / np.sqrt(np.sum(scale))else:_, _, c, n = SVD(pts)params = np.hstack((c.reshape(1, -1), n.reshape(1, -1)))[0, :]self.parameters = paramsreturn paramsdef set_parameters(self, parameters):self.parameters = parametersdef ransac_planefit(points, ransac_n, max_dst, max_trials=1000, stop_inliers_ratio=1.0, initial_inliers=None):# RANSAC 平面拟合pts = points.copy()num = pts.shape[0]cc = np.mean(pts, axis=0)iter_max = max_trialsbest_inliers_ratio = 0 # 符合拟合模型的数据的比例best_plane_params = Nonebest_inliers = Nonebest_remains = Nonefor i in range(iter_max):sample_index = random.sample(range(num), ransac_n)sample_points = pts[sample_index, :]plane = plane_model()plane_params = plane.estimate_parameters(sample_points)# 计算内点 外点index = plane.calc_inliers(points, max_dst)inliers_ratio = pts[index].shape[0] / numif inliers_ratio > best_inliers_ratio:best_inliers_ratio = inliers_ratiobest_plane_params = plane_paramsbset_inliers = pts[index]bset_remains = pts[index == False]if best_inliers_ratio > stop_inliers_ratio:# 检查是否达到最大的比例print("iter: %d\n" % i)print("best_inliers_ratio: %f\n" % best_inliers_ratio)breakreturn best_plane_params, bset_inliers, bset_remainsdef ransac_plane_detection(points, ransac_n, max_dst, max_trials=1000, stop_inliers_ratio=1.0, initial_inliers=None,out_layer_inliers_threshold=230, out_layer_remains_threshold=230):inliers_num = out_layer_inliers_threshold + 1remains_num = out_layer_remains_threshold + 1plane_set = []plane_inliers_set = []plane_inliers_num_set = []data_remains = np.copy(points)i = 0while inliers_num > out_layer_inliers_threshold and remains_num > out_layer_remains_threshold:# robustly fit line only using inlier data with RANSAC algorithmbest_plane_params, pts_inliers, pts_outliers = ransac_planefit(data_remains, ransac_n, max_dst,max_trials=max_trials,stop_inliers_ratio=stop_inliers_ratio)inliers_num = pts_inliers.shape[0]remains_num = pts_outliers.shape[0]if inliers_num > out_layer_inliers_threshold:plane_set.append(best_plane_params)plane_inliers_set.append(pts_inliers)plane_inliers_num_set.append(inliers_num)i = i + 1print('------------> %d <--------------' % i)print(best_plane_params)data_remains = pts_outliers# sortingplane_set = [x for _, x in sorted(zip(plane_inliers_num_set, plane_set), key=lambda s: s[0], reverse=True)]plane_inliers_set = [x for _, x in sorted(zip(plane_inliers_num_set, plane_inliers_set), key=lambda s: s[0], reverse=True)]return plane_set, plane_inliers_set, data_remainsif __name__ == "__main__":# 1.获取水平距离的值files = "1.txt"points = ply_txt(files)# u, s, c, normal = SVD(points)# print("1.u: ", u)# print("2.s: ", s)# print("3.c: ", c)# print("4.normal: ", normal)plane_set, plane_inliers_set, data_remains = ransac_plane_detection(points, 2, 0.3, max_trials=1000,stop_inliers_ratio=1.0, initial_inliers=None,out_layer_inliers_threshold=15,out_layer_remains_threshold=15)plane_set = np.array(plane_set)# 画散点图colors = ["blue","red","green", "yellow"]x = []y = []for i in range(len(plane_inliers_set)):print(plane_inliers_set[i].shape)for item in plane_inliers_set[i]:plt.scatter(item[0], item[1], label=i, color=colors[i], linewidth=3.0)plt.show()print(len(plane_inliers_set))
运行数据如下(注意:该数据是三维点云数据,在读取数据时将第二列数据去掉了,只保留了第一列和第三列数据,这样就成了二维数据):
1317.8586 3.2500 161.5373
1316.9133 3.2500 161.3521
1315.9966 3.2500 161.1666
1315.4659 3.2500 160.9775
1314.1918 3.2500 160.7953
1313.3609 3.2500 160.6090
1312.5728 3.2500 160.4223
1312.2994 3.2500 160.2309
1311.1111 3.2500 160.0479
1310.6090 3.2500 159.8585
1310.3498 3.2500 159.6669
1309.9192 3.2500 159.4769
1309.5314 3.2500 159.2865
1309.3009 3.2500 159.0947
1309.1418 3.2500 158.9021
1308.8541 3.2500 158.7108
1308.2948 3.2500 158.5220
1307.9785 3.2500 158.3309
1308.0053 3.2500 158.1366
1307.9463 3.2500 157.9432
1307.8730 3.2500 157.7499
1307.7569 3.2500 157.5569
1307.4549 3.2500 157.3657
1307.3959 3.2500 157.1723
1307.3655 3.2500 156.9785
1307.3208 3.2500 156.7849
1307.0188 3.2500 156.5937
1306.6024 3.2500 156.4036
1306.2575 3.2500 156.2128
1306.0413 3.2500 156.0208
1305.9252 3.2500 155.8279
1305.9091 3.2500 155.6340
1306.1360 3.2500 155.4379
1306.1056 3.2500 155.2442
1306.0323 3.2500 155.0508
1305.8161 3.2500 154.8588
1305.6427 3.2500 154.6664
1305.5409 3.2500 154.4734
1305.5391 3.2500 154.2794
1305.4515 3.2500 154.0862
1305.2781 3.2500 153.8938
1305.4050 3.2500 153.6986
1305.3174 3.2500 153.5054
1305.2299 3.2500 153.3122
1304.9850 3.2500 153.1205
1304.7259 3.2500 152.9289
1304.5383 3.2500 152.7366
1304.5651 3.2500 152.5423
1304.7205 3.2500 152.3469
1304.9188 3.2500 152.1510
1304.7169 3.2500 151.9589
1304.4006 3.2500 151.7678
1304.2559 3.2500 151.5752
1304.1969 3.2500 151.3817
1304.1951 3.2500 151.1877
1304.2790 3.2500 150.9929
1304.2058 3.2500 150.7996
1304.0467 3.2500 150.6071
1303.9448 3.2500 150.4140
1303.8144 3.2500 150.2212
1303.8841 3.2500 150.0266
1304.0395 3.2500 149.8311
1304.1664 3.2500 149.6359
1304.3075 3.2500 149.4406
1304.1485 3.2500 149.2481
1304.1753 3.2500 149.0538
1304.1020 3.2500 148.8605
1304.0001 3.2500 148.6674
1303.9268 3.2500 148.4741
1304.0823 3.2500 148.2786
1304.1663 3.2500 148.0839
1303.9643 3.2500 147.8917
1303.8910 3.2500 147.6984
1303.8178 3.2500 147.5051
1303.5443 3.2500 147.3136
1303.4711 3.2500 147.1203
1303.1834 3.2500 146.9289
1303.1101 3.2500 146.7356
1302.9082 3.2500 146.5435
1302.7634 3.2500 146.3508
1302.7902 3.2500 146.1566
1303.5889 3.2500 145.9551
1303.8873 3.2500 145.7583
1304.0142 3.2500 145.5632
1303.9981 3.2500 145.3693
1303.7961 3.2500 145.1772
1303.6800 3.2500 144.9842
1303.2922 3.2500 144.7938
1303.1189 3.2500 144.6014
1303.0742 3.2500 144.4078
1303.3869 3.2500 144.2109
1303.5852 3.2500 144.0151
1303.4690 3.2500 143.8221
1303.1670 3.2500 143.6309
1302.9365 3.2500 143.4391
1302.8346 3.2500 143.2460
1302.9472 3.2500 143.0510
1302.7453 3.2500 142.8588
1302.5862 3.2500 142.6663
1302.4701 3.2500 142.4734
1302.5111 3.2500 142.2790
1303.0669 3.2500 142.0798
1303.4653 3.2500 141.8821
1303.5493 3.2500 141.6873
1303.4045 3.2500 141.4946
1303.5028 3.2500 141.2997
1303.5439 3.2500 141.1053
1303.3848 3.2500 140.9128
1303.1829 3.2500 140.7207
1303.0810 3.2500 140.5276
1303.0363 3.2500 140.3340
1303.0203 3.2500 140.1402
1302.8612 3.2500 139.9476
1302.7879 3.2500 139.7543
1302.9720 3.2500 139.5586
1302.9416 3.2500 139.3649
1302.7539 3.2500 139.1726
1302.7521 3.2500 138.9786
1303.5080 3.2500 138.7776
1303.5491 3.2500 138.5832
1303.4043 3.2500 138.3905
1303.1595 3.2500 138.1988
1302.6288 3.2500 138.0097
1301.9551 3.2500 137.8219
1302.0962 3.2500 137.6266
1302.3089 3.2500 137.4306
1302.8217 3.2500 137.2319
1302.9628 3.2500 137.0366
1303.0611 3.2500 136.8416
1303.0879 3.2500 136.6474
1303.2433 3.2500 136.4519
1303.3559 3.2500 136.2569
1303.2111 3.2500 136.0642
1302.9377 3.2500 135.8727
1302.8359 3.2500 135.6797
1302.3337 3.2500 135.4903
1302.0746 3.2500 135.2987
1301.7869 3.2500 135.1074
1301.7279 3.2500 134.9139
1301.7261 3.2500 134.7199
1302.0102 3.2500 134.5233
1302.1657 3.2500 134.3278
1302.2354 3.2500 134.1332
1302.7911 3.2500 133.9340
1302.7321 3.2500 133.7406
1302.8018 3.2500 133.5459
1302.7142 3.2500 133.3527
1302.6409 3.2500 133.1594
1302.5819 3.2500 132.9659
1302.6373 3.2500 132.7714
运行结果如下:
显然,该数据拟合成了3条直线,3条直线的点分别是蓝色、绿色和红色。
3.1 将均值改为中值对比
将以上代码中的SVD函数中的c = np.mean(pts, axis=0)修改为c = np.median(pts, axis=0),即将均值修改为中值,得到的结果如下:
显然,两个图有差异,是因为中值不受异常点的影响,而均值会受到异常点的影响,所以,在这里(二维和三维数据),建议大家都用中值替代均值。
最后再给一个基于open3d的实现,以供参考
Multiple_Planes_Detection/utils.py at 7c660104ab75ca6ab1871bb9ee374f925d21b181 · yuecideng/Multiple_Planes_Detection (github.com)
原文链接:https://blog.csdn.net/Subtlechange/article/details/123004329
相关文章:
12. RANSAC点云多平面拟合分割
本文看了博客RANSAC点云多平面拟合分割-CSDN博客的文章,该博客将多平面拟合分割讲的很详细了,这里只是作为笔记用。 1.RANSAC算法原理 三维平面拟合(最小二乘法) 具体实现见下面代码: def SVD(points):# 二维&#…...
Prompt
提示工程指南 | Prompt Engineering GuideGoogle 官方提示工程 (Prompt Engineering)白皮书 | 宝玉的分享https://www.geeksforgeeks.org/what-is-prompt-engineering-the-ai-revolution/Prompt Engineering for Reasoning ModelsPrompt Engineering with Reasoning Models 1.p…...
[特殊字符] SpringCloud项目中使用OpenFeign进行微服务远程调用详解(含连接池与日志配置)
📚 目录 为什么要用OpenFeign? 在cart-service中整合OpenFeign 2.1 引入依赖 2.2 启用OpenFeign 2.3 编写Feign客户端 2.4 调用Feign接口 开启连接池,优化Feign性能 3.1 引入OkHttp 3.2 配置启用OkHttp连接池 3.3 验证连接池生效 Feign最佳…...
迈瑞医疗:国际业务增长21.28% 发展中国家成重要增长引擎
4月28日,迈瑞医疗(300760.SZ)发布2024年年度报告。报告期内,公司实现营业收入367.26亿元,同比增长5.14%;实现归母净利润116.68亿元,同比增长0.74%;经营活动产生的现金流量净额124.25…...
「地平线」创始人余凯:自动驾驶尚未成熟,人形机器人更无从谈起
温馨提示:运营团队2025年最新原创报告(共210页) —— 正文: 近日,国内智驾芯片上市公司[地平线]创始人兼CEO余凯(先生)接受腾讯新闻访谈时,提到如今大火的人形机器人,核…...
R 语言科研绘图第 43 期 --- 桑基图-冲击
在发表科研论文的过程中,科研绘图是必不可少的,一张好看的图形会是文章很大的加分项。 为了便于使用,本系列文章介绍的所有绘图都已收录到了 sciRplot 项目中,获取方式: R 语言科研绘图模板 --- sciRplothttps://mp.weixin.qq.c…...
SpringBoot应用原生或docker镜像容器集成Skywalking
相关组件及版本 序号 组件 版本 备注 1 skywalking-oap-server 8.9.1 2 elasticsearch 7.17.6 3 skywalking-ui 8.9.1 4 apache-skywalking-java-agent 8.15.0 一、JAR包方式部署服务 下载apache-skywalking-java-agent jar文件 使用如下名称执行 java \ -j…...
模板--进阶
1. 非类型模板参数 模板解决了两类问题 1控制数据类型 2控制某种设计逻辑 类域可以直接访问什么? 1static修饰的静态成员变量 2typedef定义的内嵌类型 3 内部类 下面我们就要引出一个模板的知识点----------再用类模板的时候从类模板里面去取东西的时候 要…...
PostgreSQL无法查看表中数据问题排查
在 PostgreSQL 中,恢复数据库后,使用 DBeaver 等工具可以看到数据库和表名,但无法查询到表中数据,可能是以下原因之一。以下是可能的原因和解决方法: 1. 恢复未完全成功 原因:数据库恢复过程中可能未完全成…...
实验研究 | 千眼狼高速摄像机驱动精密制造创新
研究背景 小模数齿轮作为精密制造领域的核心组件,以“结构紧凑、重量轻、精度高”等优势,广泛应用于航空航天、工业机器人、精密仪器、医疗设备、新能源和微电机等领域。传统抛光技术效率低、成本高、表面质量不足,电解等离子体抛光技术&am…...
AWS虚拟专用网络全解析:从基础到高级实践
导语 AWS虚拟专用网络是连接企业本地数据中心与AWS云环境的关键桥梁。本文将深入探讨AWS VPN的核心概念、配置方法、最佳实践以及常见问题解决方案,助您构建安全、可靠的混合云网络架构。 一、AWS VPN概述 1. 定义 AWS VPN是一种网络服务,允许用户通过加密隧道将本地网络…...
工业园区工厂企业数字IP广播应急呼叫对讲系统:数字IP广播极大提升工厂企业管理效率与应急响应效能
工业园区工厂企业数字IP广播应急呼叫对讲系统:数字IP广播极大提升工厂企业管理效率与应急响应效能 北京海特伟业科技有限公司任洪卓发布于2025年4月28日 在数字化转型浪潮下,IP应急广播呼叫对讲广播系统凭借其网络化、智能化、融合化、多元化等优势,已…...
我的HTTP和HTTPS
注释:本文章架构跟随小林coding,在此基础上加深个人理解 小林coding:https://xiaolincoding.com/network/2_http/http_interview.html HTTP基本概念 HTTP是什么? http的中文名是超文本传输协议,超文本就是html&…...
python_股票月数据趋势判断
目录 前置 代码 视频&月数据 前置 1 A股月数据趋势大致判断,做一个粗略的筛选 2 逻辑: 1)取最近一次历史最高点 2)以1)中最高点为分界点,只看右侧数据,取最近一次最低点 3…...
计算机考研精炼 计网
第 19 章 计算机网络体系结构 19.1 基本概念 19.1.1 计算机网络概述 1.计算机网络的定义、组成与功能 计算机网络是一个将分散的、具有独立功能的计算机系统,通过通信设备与线路连接起来,由功能完善的软件实现资源共享和信息传递的系统。 …...
大数据应用开发和项目实战-Matplotlib
Matplotlib的介绍 Matplotlib 是 Python 的绘图库,它能让使用者很轻松地将数据图形化,并且提供多样化的输出格式。 Matplotlib 可以用来绘制各种静态,动态,交互式的图表。比如说散点图、柱状图等等。 Matplotlib的安装与…...
SQLMesh CLI 实战教程: 构建和维护数据转换管道的快速指南
在数据工程领域,构建和维护数据转换管道是一项复杂而关键的任务。SQLMesh 是一个强大的工具,可以帮助你简化这一过程。本文将带你快速了解如何使用 SQLMesh CLI 来构建和维护数据转换管道。本文的目标是在 30 分钟或更短的时间内,让你熟悉 SQ…...
行政区划代码
本文数据来源:中华人民共和国民政部 在平常数据研究和开发中,经常会用到行政区划代码。而行政区划代码的使用,因年份不同可能也不尽完全相同。故整理了1980年到2024年所有的行政区划代码。 这样不仅仅适用于研究区划代码的变更研究…...
Spring生命周期
在Spring框架中,Bean的生命周期指的是从Bean的创建到销毁的整个过程。这个过程包括了多个阶段,每个阶段都有特定的操作。下面是一个典型的Spring Bean生命周期的概述: 一. 实例化(Instantiation) 当Spring容器启动时&…...
前端连接websocket服务报错 Unexpected response code: 301
前端连接websocket服务报错 Unexpected response code: 301 引直连测试301重定向修改nginx配置 引 websocket后端服务正常,监听正常,可以通过简单的前端代码进行连接,但是通过nginx反向代理后报错Error during WebSocket handshake: Unexpec…...
C语言(5)—操作符详解
文章目录 一、操作符分类概览二、二进制与进制转换2.1 进制基本概念 三、原码、反码与补码3.1 基本概念 四、位操作符详解 一、操作符分类概览 C语言中的操作符可以分为以下几大类: 算术操作符:, -, *, /, % 移位操作符:<<, >>…...
ubuntu新增磁盘挂载
1. 确认新磁盘 使用 lsblk 命令查看系统中所有的块设备,找出新增的磁盘。 rootzzwl:~# lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS sda 8:0 0 60G 0 disk ├─sda1 8:1 0 …...
【计网】计算机网络的类别与性能
一、计算机网络的分类 计算机网络可以根据不同的标准进行分类。下面是两种常见的分类方法: 1.按照网络的作用范围进行分类 广域网 (WAN): 广域网覆盖的范围非常广泛,通常跨越城市、国家或甚至是全球。WAN主要用于连接地理位置相隔较远的计算机和网络设…...
Field访问对象int字段,对象访问int字段,通过openjdk17 C++源码看对象字段访问原理
在Java反射机制中,访问对象的int类型字段值(如field.getInt(object))的底层实现涉及JVM对内存偏移量的计算与直接内存访问。本文通过分析OpenJDK 17源码,揭示这一过程的核心实现逻辑。 一、字段偏移量计算 1. Java层初始化偏移量…...
香橙派打包qt文件报错“xcb 插件无法加载”与“QObject::moveToThread”线程错误的解决方案
PyQt 报错总结:打包文件过程,“xcb 插件无法加载”与“QObject::moveToThread”线程错误的解决方案全解析 在使用 PyQt5 搭建图形界面时,打包文件的过程中出现的问题,真难绷,搞了半天。 Qt 平台插件 xcb 无法加载QOb…...
Flutter 学习之旅 之 flutter 作为 module ,在 Android 的界面中嵌入Flutter界面功能的简单整理
Flutter 学习之旅 之 flutter 作为 module ,在 Android 的界面中嵌入Flutter界面功能的简单整理 目录 Flutter 学习之旅 之 flutter 作为 module ,在 Android 的界面中嵌入Flutter界面功能的简单整理 一、简单介绍 二、在同一个布局中同时显示 Androi…...
在android 系统上qnn sdk转换,运行模型示例
前面讲了如何配置qnn sdk的环境,这一篇总结下qnn 实际转换一个onnx 模型,并运行的实现步骤。 设备: 1. ubuntu22.04 的Linux 服务器。 2. 一台android手机。 一、下载模型 from optimum.onnxruntime import ORTModelForSequenceClassifi…...
docker安装Canal1.1.5,MySQL5.7踩坑
docker安装Canal 前情提示安装MySQLdocker安装MySQL1. 拉取镜像2. 创建容器映射宿主机目录3. docker run 配置mysql重启mysql新建canal用户 安装Canal创建docker网络安装Canal1.拉取canal镜像2.创建目录3.运行容器 踩坑问题1 前情提示 关于版本问题,官方也没有详细…...
SpringBoot 接口国际化i18n 多语言返回 中英文切换 全球化 语言切换
介绍 Spring Boot通过MessageSource接口来实现国际化,它可以加载不同的消息资源文件,通常是.properties格式。通过定义不同的语言文件(例如:messages_en.properties、messages_zh.properties等),可以根据用…...
C++ 嵌套类 (详解 一站式讲解)
目录 嵌套类 嵌套类的定义 嵌套类结构的访问权限 pimpl模式(了解) 嵌套类 嵌套类的定义 首先介绍两个概念: 类作用域(Class Scope) 类作用域是指在类定义内部的范围。在这个作用域内定义的成员(包括…...
EasyCVR视频汇聚平台助力大型生产监控项目摄像机选型与应用
一、方案背景 在300路大型生产监控项目中,由于生产环境复杂多样,涵盖室外厂区、大型车间、室内办公区域等不同场景,单一类型的摄像机难以满足全方位、精细化的监控需求。EasyCVR作为一款功能强大的视频融合管理平台,具备灵活的视…...
解决leensa无法使用的办法:平替教程
Leensa已经完全连不上,并显示超时,至于之前有传言称他们会恢复服务也完全是无稽之谈。 那么,Leensa停止服务后,广大的外贸人,设计人,留学生以及技术人学习,查资料以及开发客户该怎么办呢&#…...
算法思想之哈希表
欢迎拜访:雾里看山-CSDN博客 本篇主题:算法思想之哈希表 发布时间:2025.4.28 隶属专栏:算法 目录 算法介绍哈希表作用什么时候使用哈希表怎么使用哈希表 例题两数之和题目链接题目描述算法思路代码实现 判定是否互为字符重排题目链…...
springboot 视频分段加载在线播放
页面访问视频资源 前端播放加载部分视频,每次选中一个时间点后 往后加载一部分视频,主要用以节省网络传输的数据量 通过ResourceHttpRequestHandler类实现,ResourceHttpRequestHandler支持分片加载,前端请求头中携带Range: bytes 0-10001,就…...
【题解-Acwing】868. 筛质数
题目:868. 筛质数 题目描述 给定一个正整数 n,请你求出 1∼n 中质数的个数。 输入 共一行,包含整数 n。 输出 共一行,包含一个整数,表示 1∼n 中质数的个数。 数据范围 1 ≤ n ≤ 106 时空限制 0.2s / 64MB 输入样例 8输出样例 4代码 #include...
java 和 C#操作数据库对比
Java和C#在操作数据库方面有诸多相似之处,但也存在一些差异,以下从几个方面为你进行对比: 数据库连接 Java 在Java中,使用JDBC(Java Database Connectivity)来连接数据库。连接数据库的步骤通常包括加载…...
用 Python打造交互式图片-音视频管理器:代码分析与实践
在 Python 的 GUI 开发领域,wxPython 是一个功能强大且跨平台的框架,适合快速构建桌面应用程序。本文将深入分析一个基于 wxPython 的交互式图片管理器项目(SelectModelToPlayVideoAndAudio.py),该程序允许用户管理图片…...
Django 缓存框架
动态网站的一个基本权衡是它们是动态的。每当用户请求页面时,Web 服务器进行各种计算,从数据库查询到模板渲染到业务逻辑,以创建您网站访问者看到的页面。从处理开销的角度来看,这比标准的从文件系统中读取文件的服务器安排要昂贵…...
后缀数组~
子串:在字符串s中,取任意i<j,那么在s中截取从i到j的这一段就叫做s的一个子串。 后缀:后缀就是从字符串的某个位置i到字符串末尾的子串,我们定义以s的第i个字符为第一个元素的后缀为suff(i)。 suff(1)就是从第一个字符到最后一个…...
Rust 学习笔记:编程练习(一)
Rust 学习笔记:编程练习(一) Rust 学习笔记:编程练习(一)Convert temperatures between Fahrenheit and CelsiusGenerate the nth Fibonacci numberPrint the lyrics to the Christmas carol “The Twelve …...
【基础知识】常见的计算公式(一)
目录标题 定时器相关公式1. 定时器分频系数计算2. 定时器定时时间计算 波特率相关公式1. 异步串行通信波特率计算 PWM 相关公式1. PWM 占空比计算 不知道你们是不是和我一样,对定时器中的分频系数相关的计算公式,波特率计算公式等,都有点心生畏惧,其核心原因是不清楚不了解,没有…...
【AI生产力工具】Windsurf,一款AI编程工具
Windsurf 是 Codeium 公司推出的一款 AI 编程助手,它是一款集成深度上下文感知、多模型协作和实时代码管理的综合开发环境(IDE)。 Windsurf 作为 AI 编程工具的核心价值在于 “上下文感知 + 多模型协作 + 自动化工作流”,其深度集成的智能体系统(如 Flows 和 Cascade)正…...
【网络原理】TCP提升效率机制(三):延时应答和捎带应答
目录 一. 延时应答 二. 捎带应答 一. 延时应答 延时应答也是基于滑动窗口的一种提升传输效率的方式(减少ACK数量) 接收方收到数据之后,不会立刻返回一个ACK确认报文,而是等一会再返回ACK报文 这样做的好处? 1&…...
Golang|分布式索引架构
当文档数量巨大时,如一亿个文档,倒排索引难以全部放入单机内存。正排索引将热数据和冷数据分别存储在内存和磁盘中。分布式搜索引擎将数据分布在不同服务器上以应对大量数据。 倒排索引的水平切分 水平切分按关键词划分数据,如JAVA和算法在A服…...
【蓝桥杯省赛真题58】Scratch画台扇 蓝桥杯scratch图形化编程 中小学生蓝桥杯省赛真题讲解
目录 scratch画台扇 一、题目要求 编程实现 二、案例分析 1、角色分析 2、背景分析 3、前期准备 三、解题思路 四、程序编写 五、考点分析 六、推荐资料 1、scratch资料 2、python资料 3、C资料 scratch画台扇 第十五届青少年蓝桥杯scratch编程省赛真题解析 一…...
WebRTC SDK是什么?
语音环境每年都在变,OKCC以前代理商的客群都是简单高效外呼为主,今年发现变化很大。很多代理商做的终端客户都是给其他业务系统赋能为主了。主流的还是以API对接为主,但是对接中发现webrtc SDK使用频率很高。 那么什么是WebRTC SDK…...
反向代理、负载均衡与镜像流量:原理剖析、区别对比及 Nginx 配置实践
在现代网络架构中,反向代理、负载均衡和镜像流量是保障系统高效、稳定、安全运行的关键技术。虽然它们都涉及网络流量的管理与分发,但在功能、应用场景和实现方式上有着显著差异。本文将深入探讨三者的区别,并结合 Nginx 这一强大的开源服务器…...
PyCharm 2023升级2024 版本
windows下把老版本卸载之后,需要把环境变量,注册表信息删除。 并且把C:\Users\用户\AppData 文件夹下的 Local\JetBrains和Roaming\JetBrains 都删除,再重新安装 原旧项目升级的方式: 1.2023虚拟机的文件夹是venv 改为.venv…...
for(auto it: vec)和for(auto it: vec)的区别以及使用场景
目录 1.for(auto it: vec) 2.for(auto& it: vec) 3.总结 在 C 里,for(auto it: vec) 和 for(auto& it: vec) 都属于基于范围的 for 循环,可用于遍历容器(像 std::vector、std::list 等)或者数组。不过,它们…...
系统思考提升培训效能
彼得德鲁克曾说:管理者的价值,不在于处理了多少事务,而在于创造了多少不可替代的成果。效率是以正确的方法做事,效能则是做正确的事。在系统思考中我们看到:效率往往是对单点问题的反应,而效能则是对整体系…...