网站接入服务 公司wordpress前台注册登录弹窗代码
网站接入服务 公司,wordpress前台注册登录弹窗代码,龙江人社使用方法,做公司网站详细步骤单目相机阴影3D高度估测系统一、实际应用场景描述在工业生产线上#xff0c;经常需要对散装物料#xff08;如粮食、矿石、塑料颗粒等#xff09;的堆积高度进行实时监测#xff0c;以便进行库存管理、质量控制或工艺控制。传统方案通常依赖#xff1a;- 激光测距仪#…单目相机阴影3D高度估测系统一、实际应用场景描述在工业生产线上经常需要对散装物料如粮食、矿石、塑料颗粒等的堆积高度进行实时监测以便进行库存管理、质量控制或工艺控制。传统方案通常依赖- 激光测距仪单点测量无法获取整个料堆的轮廓- 超声波传感器受物料表面特性影响大精度有限- 双目/结构光3D相机成本昂贵对环境光敏感维护复杂本系统通过单目工业相机从固定角度拍摄料堆表面利用朗伯体反射模型和阴影几何关系通过分析物料表面的亮度变化来估算相对高度分布实现低成本、大面积的3D高度检测。二、引入痛点1. 单目深度歧义单张2D图像无法唯一确定3D结构2. 光照敏感性环境光变化会严重影响阴影特征提取3. 材质依赖性不同反射特性的物料需要不同的光照模型4. 遮挡问题料堆边缘或内部凹陷造成阴影误判5. 相机姿态要求相机必须严格固定倾斜会导致高度计算偏差三、核心逻辑讲解高度估测原理基于光照-阴影-高度的几何关系1. 朗伯余弦定律表面亮度 I I_0 \cdot \rho \cdot \cos(\theta)- I 观测亮度- I_0 光源强度- \rho 表面反射率反照率- \theta 光线入射角2. 高度-阴影关系对于固定光源方向表面高度变化导致光照角度变化h(x,y) f(\cos^{-1}(I(x,y) / (I_0 \cdot \rho)))3. 简化模型假设均匀反射率和平行光源亮度直接反映相对高度h_{rel}(x,y) \propto \arccos\left(\frac{I(x,y)}{I_{max}}\right)实现步骤1. 相机标定建立像素坐标到世界坐标的映射2. 光照建模估计光源方向和强度分布3. 阴影检测识别亮度梯度显著的阴影边界4. 高度求解基于亮度-高度映射关系求解相对高度场5. 后处理滤波平滑和异常值剔除四、代码模块化实现项目结构monocular_shadow_height/├── main.py # 主程序入口├── camera_calibrator.py # 相机标定模块├── light_modeler.py # 光照模型估计模块├── shadow_detector.py # 阴影检测模块├── height_estimator.py # 高度估测核心算法├── surface_reconstructor.py # 表面重建模块├── utils.py # 工具函数├── light_params.json # 光照参数配置├── calibration_params.json # 相机标定参数└── README.md # 项目文档1. utils.py - 工具函数模块工具函数模块包含通用辅助函数和数学工具import jsonimport numpy as npfrom typing import Tuple, List, Optional, Dictfrom dataclasses import dataclassimport cv2from scipy import ndimagefrom scipy.interpolate import griddatadataclassclass CameraIntrinsics:相机内参数据结构fx: float # 焦距x (像素)fy: float # 焦距y (像素)cx: float # 主点x (像素)cy: float # 主点y (像素)k1: float 0.0 # 径向畸变系数1k2: float 0.0 # 径向畸变系数2p1: float 0.0 # 切向畸变系数1p2: float 0.0 # 切向畸变系数2dataclassclass LightDirection:光源方向数据结构azimuth: float # 方位角 (弧度)elevation: float # 仰角 (弧度)intensity: float # 光源强度type: str parallel # 光源类型: parallel/diffuse/pointdef save_json(data: dict, filepath: str) - None:保存字典数据到JSON文件with open(filepath, w, encodingutf-8) as f:json.dump(data, f, indent4, ensure_asciiFalse)print(f数据已保存至 {filepath})def load_json(filepath: str) - dict:从JSON文件加载数据try:with open(filepath, r, encodingutf-8) as f:return json.load(f)except FileNotFoundError:print(f警告未找到文件 {filepath}返回空字典)return {}def pixel_to_world(u: float, v: float, z: float,intrinsics: CameraIntrinsics) - Tuple[float, float]:像素坐标转世界坐标假设地面为z0平面参数:u, v: 像素坐标z: 物体高度相对于地面intrinsics: 相机内参返回:(X, Y): 世界坐标米# 反向投影到相机坐标系x_cam (u - intrinsics.cx) * z / intrinsics.fxy_cam (v - intrinsics.cy) * z / intrinsics.fy# 假设相机高度为h_cam向下俯视角度为θ# 这里简化处理假设相机在(0, 0, h_cam)地面为z0# 实际应用中需要根据相机安装位置和姿态进行变换return x_cam, y_camdef world_to_pixel(X: float, Y: float, Z: float,intrinsics: CameraIntrinsics) - Tuple[float, float]:世界坐标转像素坐标u intrinsics.fx * X / Z intrinsics.cxv intrinsics.fy * Y / Z intrinsics.cyreturn u, vdef normalize_vector(v: np.ndarray) - np.ndarray:向量归一化norm np.linalg.norm(v)return v / norm if norm 0 else vdef compute_surface_normal(height_map: np.ndarray,pixel_size: float) - np.ndarray:从高度图计算表面法向量参数:height_map: 高度矩阵 (m)pixel_size: 像素尺寸 (m/pixel)返回:法向量矩阵 (3, H, W)每个像素对应一个3D法向量# 计算梯度dz_dx, dz_dy np.gradient(height_map, pixel_size)# 法向量 (-dz/dx, -dz/dy, 1) 归一化normals np.zeros((3, height_map.shape[0], height_map.shape[1]), dtypenp.float32)normals[0] -dz_dxnormals[1] -dz_dynormals[2] 1.0# 归一化norms np.sqrt(normals[0]**2 normals[1]**2 normals[2]**2)for i in range(3):normals[i] / normsreturn normalsdef compute_gradients(image: np.ndarray, method: str sobel) - Tuple[np.ndarray, np.ndarray]:计算图像梯度参数:image: 输入灰度图像method: 梯度计算方法 (sobel, scharr, central_diff)返回:(grad_x, grad_y): x和y方向的梯度if method sobel:grad_x cv2.Sobel(image, cv2.CV_64F, 1, 0, ksize3)grad_y cv2.Sobel(image, cv2.CV_64F, 0, 1, ksize3)elif method scharr:grad_x cv2.Scharr(image, cv2.CV_64F, 1, 0)grad_y cv2.Scharr(image, cv2.CV_64F, 0, 1)elif method central_diff:grad_x ndimage.sobel(image, axis1, modereflect)grad_y ndimage.sobel(image, axis0, modereflect)else:raise ValueError(f不支持的梯度计算方法: {method})return grad_x, grad_ydef interpolate_missing_values(height_map: np.ndarray,method: str linear) - np.ndarray:插值填补高度图中的缺失值NaN参数:height_map: 含NaN的高度矩阵method: 插值方法 (linear, nearest, cubic)返回:填补后的高度矩阵# 找到有效点valid_mask ~np.isnan(height_map)if not np.any(valid_mask):return height_map# 创建网格h, w height_map.shapex, y np.meshgrid(np.arange(w), np.arange(h))# 有效点的坐标和值valid_points np.column_stack([x[valid_mask], y[valid_mask]])valid_values height_map[valid_mask]# 插值filled_map griddata(valid_points, valid_values, (x, y), methodmethod)# 保留原有的有效值插值可能有微小误差filled_map[valid_mask] height_map[valid_mask]return filled_mapdef apply_bilateral_filter(image: np.ndarray, d: int 9,sigma_color: float 75,sigma_space: float 75) - np.ndarray:应用双边滤波保边去噪参数:image: 输入图像d: 邻域直径sigma_color: 颜色空间标准差sigma_space: 坐标空间标准差返回:滤波后的图像return cv2.bilateralFilter(image.astype(np.float32), d, sigma_color, sigma_space)def compute_shadow_confidence(shadow_mask: np.ndarray,gradient_magnitude: np.ndarray) - np.ndarray:计算阴影区域的置信度图参数:shadow_mask: 阴影二值掩码gradient_magnitude: 梯度幅值图返回:置信度图 (0-1)# 阴影边界处的梯度较大置信度高shadow_gradients gradient_magnitude * shadow_mask# 归一化到0-1if np.max(shadow_gradients) 0:confidence shadow_gradients / np.max(shadow_gradients)else:confidence np.zeros_like(shadow_mask, dtypenp.float32)# 膨胀阴影区域降低内部点的置信度kernel np.ones((5, 5), np.uint8)dilated_shadow cv2.dilate(shadow_mask.astype(np.uint8), kernel, iterations2)inner_shadow dilated_shadow - shadow_maskconfidence[inner_shadow 0] * 0.5return confidencedef estimate_albedo(image: np.ndarray, light_dir: LightDirection,normals: np.ndarray) - np.ndarray:估计表面反照率反射率基于朗伯余弦定律: I ρ * L · n因此: ρ I / (L · n)参数:image: 输入图像亮度light_dir: 光源方向normals: 表面法向量返回:反照率图 (0-1)# 构建光源方向向量L np.array([np.cos(light_dir.elevation) * np.sin(light_dir.azimuth),np.cos(light_dir.elevation) * np.cos(light_dir.azimuth),np.sin(light_dir.elevation)])L normalize_vector(L)# 计算光照方向与法向量的点积cos_theta normals[0] * L[0] normals[1] * L[1] normals[2] * L[2]# 避免除零cos_theta np.clip(cos_theta, 0.01, 1.0)# 估计反照率albedo image / cos_theta# 限制在合理范围albedo np.clip(albedo, 0, 1)return albedodef create_height_colormap(height_map: np.ndarray,vmin: Optional[float] None,vmax: Optional[float] None) - np.ndarray:创建高度图的伪彩色可视化参数:height_map: 高度矩阵 (m)vmin, vmax: 颜色映射的最小最大值返回:BGR格式的彩色图像if vmin is None:vmin np.nanmin(height_map)if vmax is None:vmax np.nanmax(height_map)# 归一化高度值normalized np.clip((height_map - vmin) / (vmax - vmin), 0, 1)# 应用Jet色图height_colored cv2.applyColorMap((normalized * 255).astype(np.uint8),cv2.COLORMAP_JET)return height_colored2. camera_calibrator.py - 相机标定模块相机标定模块负责相机内参标定和姿态估计import cv2import numpy as npfrom typing import Tuple, Optional, Listfrom dataclasses import dataclassfrom utils import CameraIntrinsics, save_json, load_jsonclass CameraCalibrator:def __init__(self):初始化相机标定器self.intrinsics Noneself.dist_coeffs Noneself.extrinsics None # 相机外参姿态self.is_calibrated Falsedef calibrate_from_checkerboard(self, images: List[np.ndarray],checkerboard_size: Tuple[int, int],square_size_m: float) - bool:使用棋盘格标定相机内参参数:images: 棋盘格图像列表checkerboard_size: 棋盘格内角点数量 (cols, rows)square_size_m: 每个方格的实际尺寸 (米)返回:标定是否成功objp np.zeros((checkerboard_size[0] * checkerboard_size[1], 3), np.float32)objp[:, :2] np.mgrid[0:checkerboard_size[0],0:checkerboard_size[1]].T.reshape(-1, 2)objp * square_size_mobjpoints [] # 3D点imgpoints [] # 2D点for img in images:gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)# 查找棋盘格角点ret, corners cv2.findChessboardCorners(gray, checkerboard_size, None)if ret:# 亚像素级精确化criteria (cv2.TERM_CRITERIA_EPS cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)corners_refined cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)objpoints.append(objp)imgpoints.append(corners_refined)if len(objpoints) 3:print(错误有效标定图像不足)return False# 相机标定ret, mtx, dist, rvecs, tvecs cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)if ret:self.intrinsics CameraIntrinsics(fxmtx[0, 0],fymtx[1, 1],cxmtx[0, 2],cymtx[1, 2],k1dist[0, 0],k2dist[0, 1],p1dist[0, 2],p2dist[0, 3])self.dist_coeffs distself.is_calibrated Trueprint(相机内参标定成功:)print(f fx{self.intrinsics.fx:.2f}, fy{self.intrinsics.fy:.2f})print(f cx{self.intrinsics.cx:.2f}, cy{self.intrinsics.cy:.2f})print(f 畸变系数: k1{self.intrinsics.k1:.6f}, k2{self.intrinsics.k2:.6f})return Truereturn Falsedef estimate_pose_from_ground_plane(self, ground_points: List[Tuple[float, float, float]],image_points: List[Tuple[float, float]]) - bool:估计相机相对于地面的姿态假设地面为z0平面通过已知的地面点对应关系估计相机高度和俯仰角参数:ground_points: 地面3D点列表 (X, Y, 0)image_points: 对应的图像2D点列表 (u, v)返回:估计是否成功if not self.is_calibrated or len(ground_points) 4:print(错误需要先标定相机内参且至少需要4个地面点)return False# 转换为numpy数组ground_pts np.array(ground_points, dtypenp.float32)img_pts np.array(image_points, dtypenp.float32)# 使用solvePnP估计姿态# 地面法向量为(0, 0, 1)指向相机camera_center np.array([[0], [0], [1]], dtypenp.float32)ret, rvec, tvec cv2.solvePnP(ground_pts, img_pts,np.array([[self.intrinsics.fx, 0, self.intrinsics.cx],[0, self.intrinsics.fy, self.intrinsics.cy],[0, 0, 1]], dtypenp.float32),self.dist_coeffs,camera_center, None,cv2.SOLVEPNP_IPPE_SQUARE)if ret:self.extrinsics {rotation_vector: rvec,translation_vector: tvec,camera_height: np.linalg.norm(tvec)}print(f相机姿态估计成功:)print(f 相机高度: {self.extrinsics[camera_height]:.3f}m)return Truereturn Falsedef manual_setup(self, fx: float, fy: float, cx: float, cy: float,camera_height: float, pitch_angle: float) - None:手动设置相机参数当无法进行完整标定时参数:fx, fy: 焦距cx, cy: 主点camera_height: 相机离地高度 (m)pitch_angle: 相机俯仰角 (弧度向下为正)self.intrinsics CameraIntrinsics(fxfx, fyfy, cxcx, cycy)self.extrinsics {camera_height: camera_height,pitch_angle: pitch_angle}self.is_calibrated Trueprint(手动相机参数设置完成:)print(f 焦距: fx{fx:.2f}, fy{fy:.2f})print(f 主点: cx{cx:.2f}, cy{cy:.2f})print(f 相机高度: {camera_height:.3f}m)print(f 俯仰角: {np.degrees(pitch_angle):.2f}°)def undistort_image(self, image: np.ndarray) - np.ndarray:去除图像畸变if not self.is_calibrated:return imageh, w image.shape[:2]new_camera_matrix, roi cv2.getOptimalNewCameraMatrix(np.array([[self.intrinsics.fx, 0, self.intrinsics.cx],[0, self.intrinsics.fy, self.intrinsics.cy],[0, 0, 1]], dtypenp.float32),self.dist_coeffs, (w, h), 1, (w, h))undistorted cv2.undistort(image,np.array([[self.intrinsics.fx, 0, self.intrinsics.cx],[0, self.intrinsics.fy, self.intrinsics.cy],[0, 0, 1]], dtypenp.float32),self.dist_coeffs, None, new_camera_matrix)return undistorteddef pixel_to_ground_plane(self, u: float, v: float) - Tuple[float, float]:将像素坐标映射到地面平面坐标参数:u, v: 像素坐标返回:(X, Y): 地面平面坐标米if not self.is_calibrated or self.extrinsics is None:raise ValueError(相机未标定或未设置姿态)# 简化的透视投影模型# 假设相机位于(0, 0, h)向下俯视角度为θh self.extrinsics[camera_height]theta self.extrinsics.get(pitch_angle, 0)# 像素坐标相对于主点的偏移dx (u - self.intrinsics.cx) / self.intrinsics.fxdy (v - self.intrinsics.cy) / self.intrinsics.fy# 考虑俯仰角的投影X dx * h / (np.cos(theta) - dy * np.sin(theta))Y dy * h / (np.cos(theta) - dy * np.sin(theta))return X, Ydef get_intrinsics(self) - Optional[CameraIntrinsics]:获取相机内参return self.intrinsicsdef get_extrinsics(self) - Optional[dict]:获取相机外参return self.extrinsicsdef save_calibration(self, filepath: str calibration_params.json) - None:保存标定参数到文件if not self.is_calibrated:print(警告相机未标定无法保存参数)returnparams {intrinsics: {fx: self.intrinsics.fx,fy: self.intrinsics.fy,cx: self.intrinsics.cx,cy: self.intrinsics.cy,k1: self.intrinsics.k1,k2: self.intrinsics.k2,p1: self.intrinsics.p1,p2: self.intrinsics.p2},extrinsics: self.extrinsics,is_calibrated: self.is_calibrated}save_json(params, filepath)def load_calibration(self, filepath: str calibration_params.json) - bool:从文件加载标定参数params load_json(filepath)if not params:return Falseintr params[intrinsics]self.intrinsics CameraIntrinsics(fxintr[fx], fyintr[fy],cxintr[cx], cyintr[cy],k1intr.get(k1, 0.0), k2intr.get(k2, 0.0),p1intr.get(p1, 0.0), p2intr.get(p2, 0.0))self.extrinsics params.get(extrinsics, None)self.is_calibrated params.get(is_calibrated, False)print(f标定参数已从 {filepath} 加载)return True3. light_modeler.py - 光照模型估计模块光照模型估计模块估计光源方向和强度分布import cv2import numpy as npfrom typing import Tuple, Optional, Listfrom dataclasses import dataclassfrom utils import LightDirection, normalize_vector, compute_gradientsfrom scipy.optimize import minimize, differential_evolutionfrom scipy.ndimage import gaussian_filterdataclassclass PhotometricStereoResult:光度立体视觉结果light_directions: List[np.ndarray] # 每个光源方向如果是多光源light_intensities: List[float] # 每个光源强度albedo: np.ndarray # 反照率图normals: np.ndarray # 表面法向量场class LightModeler:def __init__(self, image_width: int, image_height: int):初始化光照模型估计器参数:image_width: 图像宽度image_height: 图像高度self.image_width image_widthself.image_height image_heightself.light_direction Noneself.light_intensity Noneself.albedo Nonedef estimate_single_light_direction(self, image: np.ndarray,assumed_albedo: Optional[np.ndarray] None,initial_guess: Tuple[float, float] (45, 45)) - LightDirection:估计单一平行光源的方向基于图像亮度梯度和假设的朗伯反射模型参数:image: 输入灰度图像assumed_albedo: 假设的反照率图可选initial_guess: 光源方向的初始猜测 (方位角°, 仰角°)返回:估计的光源方向# 计算图像梯度grad_x, grad_y compute_gradients(image, methodsobel)gradient_magnitude np.sqrt(grad_x**2 grad_y**2)# 估计光源方向使得亮度变化与梯度方向一致# 对于朗伯表面: ∇I -ρ * L · ∇n / |n|# 简化光源方向应与亮度下降最快的方向相反# 计算亮度下降方向阴影边界shadow_boundaries gradient_magnitude np.percentile(gradient_magnitude, 90)# 计算平均梯度方向valid_grad_x grad_x[shadow_boundaries]valid_grad_y grad_y[shadow_boundaries]if len(valid_grad_x) 0:mean_grad_x np.mean(valid_grad_x)mean_grad_y np.mean(valid_grad_y)# 光源方向大致与梯度方向相反# 梯度方向是亮度增加的方向光源在相反方向grad_angle np.arctan2(mean_grad_y, mean_grad_x)light_azimuth grad_angle np.pi # 相反方向light_elevation np.radians(initial_guess[1]) # 使用初始猜测的仰角else:# 使用初始猜测light_azimuth np.radians(initial_guess[0])light_elevation np.radians(initial_guess[1])# 优化光源方向optimized self._optimize_light_direction(image, light_azimuth, light_elevation)light_azimuth, light_elevation optimized# 估计光源强度light_intensity self._estimate_light_intensity(image, light_azimuth, light_elevation, assumed_albedo)self.light_direction LightDirection(azimuthlight_azimuth,elevati利用AI解决实际问题如果你觉得这个工具好用欢迎关注长安牧笛