研究光度立体法阶段性小结和优化(可20ms获取4个2500*2000灰度图的Normal Map)。...

本文探讨了光度立体法的算法实现,包括所需硬件设置、Halcon库中的photometric_stereo算子及其实现原理。作者通过分析代码并进行优化,将处理时间显著缩短,同时分享了一个简易的测试DEMO。文章还提到了反射率图和正常向量图的获取,并讨论了如何从Halcon算子中获取梯度信息。

  这个东西是我接触的第一个非2D方面的算法,到目前为止其实也没有完全搞定,不过可能短时间内也无法突破。先把能搞定的搞定吧。

  这个东西也有一大堆参考资料,不过呢,搜来搜去其实也就那些同样的东西,个人觉得就属这个文章最经典,既有说明,也有图片,还有代码:

      Photometric Stereo    Chaman Singh Verma and Mon-Ju Wu

       https://pages.cs.wisc.edu/~csverma/CS766_09/Stereo/stereo.html

  另外,github上也应该有一些参考的资料吧,我主要参考的是  https://github.com/chaochaojnu这个中国小哥的博客。

  目前为止,我只实现了提取Albedo、Normal Map和Normal Vectors三个结果。

  从硬件上讲,这算法应该需要一个固定位置的相机(应该是要和目标垂直吧),以及至少3个以上的平行光源,一般实际上可能需要至少4个以上的光源吧,然后每个光源单独打光,单独拍一张图片,共得到N个不同的图片,然后根据这个N个图片,合成一个结果图以及得到额外的梯度和高度信息。 

  在Halcon中,有对应的photometric_stereo算子实现该功能,该算子除了要提供N个图片,还需要提供 Slants和Tilts两个参数,你去看他们的英语翻译,其实都是倾斜角,个人理解Tilts就是光源在XY平面投影时和X轴的夹角,而Slants就是光源和XY平面的夹角。 

  在我刚刚提供的两个链接里,他们都不是直接提供 Slants和Tilts,而是直接利用标准物体在对应光源下拍照,得到几幅标准图像,然后由标

<think>我们正在讨论的是如何通过光度立体(PhotometricStereo)将一组黑白图像(在不同的光照方向下拍摄)渲染为彩色的线贴图(NormalMap)。光度立体是一种从多张不同光照条件下的图像中恢复表面线的技术。###步骤概述:1.**图像获取**:使用固定相机,从多个不同的已知光源方向拍摄物体(至少3个方向,通常需要更多以减少噪声解决歧义)。2.**光源方向与强度**:需要知道每个光源的方向向量强度(如果光源强度不同)。3.**反射模型**:通常假设物体表面是朗伯体(Lambertian),即反射光线是均匀散射的,因此图像中的像素亮度与表面线光源方向的点积成正比。4.**求解线**:对于每个像素,我们可以建立一个方程组,通过最小二乘等方来求解该像素处的表面线。5.**线图表示**线向量的三个分量(Nx,Ny,Nz)通常被映射到RGB颜色空间(分别对应R,G,B),以生成彩色的线贴图。###详细方:####1.图像光源信息设我们有m张图像(m>=3),每张图像对应一个光源方向。光源方向向量可以用一个三维向量$\mathbf{l_i}=(l_{i,x},l_{i,y},l_{i,z})^T$表示($i=1,2,...,m$)。同时,光源强度通常需要被标定,但有时可以假设为单位强度或通过其他方式归一化。####2.朗伯反射模型对于朗伯表面,每个像素的亮度$I_i$(第$i$张图像上的该像素)与表面线$\mathbf{n}=(n_x,n_y,n_z)^T$光源方向$\mathbf{l_i}$的点积成正比:$$I_i=\rho\cdot\mathbf{l_i}^T\mathbf{n}$$其中,$\rho$是表面的反射率(albedo)。注意,这里我们假设光源强度已经被归一化,或者包含在$\mathbf{l_i}$中。####3.建立方程组对于同一个像素,我们有m个方程:$$\begin{bmatrix}I_1\\I_2\\\vdots\\I_m\end{bmatrix}=\rho\cdot\begin{bmatrix}l_{1,x}&l_{1,y}&l_{1,z}\\l_{2,x}&l_{2,y}&l_{2,z}\\\vdots&\vdots&\vdots\\l_{m,x}&l_{m,y}&l_{m,z}\end{bmatrix}\begin{bmatrix}n_x\\n_y\\n_z\end{bmatrix}$$写成矩阵形式:$\mathbf{I}=\rho\mathbf{L}\mathbf{n}$####4.求解线由于$\rho$$\mathbf{n}$都是未知的,我们需要从上述方程组中同时求解。注意到$\mathbf{n}$是单位向量,因此我们可以先忽略$\rho$,通过最小二乘求解一个未归一化的向量,然后再归一化。具体步骤:-将方程组改写为:$\mathbf{L}\mathbf{n}=\frac{1}{\rho}\mathbf{I}$-由于$\frac{1}{\rho}$是一个标量,我们可以先求解$\mathbf{n}$的方向。利用最小二乘求解超定方程组:$\mathbf{n}=(\mathbf{L}^T\mathbf{L})^{-1}\mathbf{L}^T\mathbf{I}$注意:这里我们暂时将$\frac{1}{\rho}$合并到了$\mathbf{n}$中,所以此时得到的$\mathbf{n}$并不是单位向量。-然后,归一化向量:$\mathbf{n}\leftarrow\frac{\mathbf{n}}{\|\mathbf{n}\|}$-反射率$\rho$可以通过$\rho=\|\mathbf{n}\|$来估计(在归一化之前,注意最小二乘解出来的向量范数实际对应了$\sqrt{\frac{1}{\rho^2}\cdot\text{常数}}$?这里需要调整:实际上,在归一化之前我们得到的向量记为$\mathbf{n}_0$,那么有$\mathbf{n}_0=\frac{\mathbf{n}}{\rho}$,所以$\|\mathbf{n}_0\|=\frac{1}{\rho}$,因此$\rho=\frac{1}{\|\mathbf{n}_0\|}$。但通常我们可以先计算单位向量,然后反射率可以通过亮度与点积的比值来估计(具体见后面)。另一种常见做是同时求解反射率线。因为每个像素的反射率是标量,我们可以将方程写为:$$\mathbf{L}\mathbf{n}=\mathbf{I}/\rho$$但是$\rho$是未知的。我们可以先假设反射率$\rho$在像素间是变化的,但每个像素独立求解。然后注意到向量$\mathbf{n}$的模长为1,因此我们可以这样求解:-由于$I_i=\rho\cdot(\mathbf{l_i}\cdot\mathbf{n})$,因此$\rho\mathbf{n}=(\mathbf{L}^T\mathbf{L})^{-1}\mathbf{L}^T\mathbf{I}$。设$\mathbf{g}=(\mathbf{L}^T\mathbf{L})^{-1}\mathbf{L}^T\mathbf{I}$,则$\rho=\|\mathbf{g}\|$,而$\mathbf{n}=\mathbf{g}/\|\mathbf{g}\|$。因此,对每个像素,计算$\mathbf{g}$,然后归一化得到单位向量,同时得到反射率图(albedomap)$\rho$。####5.生成彩色线贴图得到线后,每个像素都有一个三维向量$\mathbf{n}=(n_x,n_y,n_z)$。由于线分量取值范围在$[-1,1]$,而图像通常用0到255表示,我们需要将其映射:-通常映射:$R=(n_x+1)*127.5$,$G=(n_y+1)*127.5$,$B=(n_z+1)*127.5$-但注意:在标准线贴图表示中,z分量通常是正的(因为线指向观察方向外),因此蓝色(代表z分量)通常比较亮。###注意:-光度立体需要精确的光源方向。如果光源方向未知,可能需要校准步骤。-非朗伯表面需要更复杂的模型。-阴影高光区域会带来问题,可能需要鲁棒的方处理。###简单代码示例假设我们有三张图像三个光源方向(单位向量):```pythonimportnumpyasnpimportcv2#假设有三张图像,放在一个列表中,每个图像是灰度图(0-1)imgs=[img1,img2,img3]#这三张图需要在相同视角不同光照下拍摄#光源方向(单位向量),每行对应一个光源light_dirs=np.array([[0,0,1],#第一张图的光源方向,从正前方打光(沿z轴)[1,0,0],#第二张图的光源方向,从x正方向[0,1,0]#第三张图的光源方向,从y正方向])#将图像堆叠成一个矩阵,每个像素对应一个列向量,每行对应一个光源height,width=imgs[0].shapeimg_matrix=np.array(imgs).reshape(len(imgs),-1)#3x(height*width)#求解最小二乘问题:Ln=I(每个光源对应一个方程,每个像素独立求解)#注意:这里我们假设反射率ρ被包含在向量中,后面再归一化#公式:n=(L^TL)^{-1}L^TIL=light_dirsLt=L.TLtL=np.dot(Lt,L)invLtL=np.linalg.inv(LtL)result=np.dot(np.dot(invLtL,Lt),img_matrix)#3x(height*width)#现在result的每一列是3维向量,即每个像素的未归一化向量(包含ρ)乘以反射率ρ?#但按照前面的推导:我们解出的result实际上是ρ*n(也就是我们之前定义的g)#因此,对每个像素,归一化并得到反射率normal_map=np.zeros((height,width,3),dtype=np.float32)albedo=np.zeros((height,width),dtype=np.float32)foriinrange(img_matrix.shape[1]):g=result[:,i]rho=np.linalg.norm(g)ifrho>1e-8:n=g/rhoelse:n=np.array([0,0,1])#避免除以0,设为线向上(z轴)rho=0#分解到二维坐标(对应像素位置)y=i//widthx=i%width#保存线分量(归一化后的单位向量)normal_map[y,x]=nalbedo[y,x]=rho#线映射到颜色#将线分量从[-1,1]映射到[0,1]normal_color=(normal_map+1)/2#转换为0-255的整数normal_color_uint8=(normal_color*255).astype(np.uint8)#显示或保存cv2.imwrite('normal_map.png',cv2.cvtColor(normal_color_uint8,cv2.COLOR_RGB2BGR))```注意:上面的代码仅适用于三个光源且没有噪声的情况。实际中,通常需要更多光源(比如32个或更多)以减少噪声影响。此外,光源方向需要精确校准。###相关问题§§相关问题§§1.光度立体需要多少张图像才能准确重建线贴图?2.如何处理非朗伯表面带来的高光问题?3.如何在实际中获取精确的光源方向?</think> 基于您的问题,我将详细介绍使用光度立体(Photometric Stereo)将黑白图像转换为彩色线贴图(Normal Map)的实现方。核心原理是通过不同光照方向下的图像亮度差异反推物体表面线方向[^1]。 ### 一、实现步骤 1. **数据采集**(需固定相机位置): - 拍摄至少3张(建议4-6张)同视角的灰度图像($I_1$-$I_m$) - 记录每张图像对应的光源方向向量($\mathbf{L}_i = [l_x, l_y, l_z]^T$) - 示例光照矩阵: $$ \mathbf{L} = \begin{bmatrix} l_{x1} & l_{y1} & l_{z1} \\ \vdots & \vdots & \vdots \\ l_{xm} & l_{ym} & l_{zm} \end{bmatrix} $$ 2. **算法线向量**: - 对每个像素点$(u,v)$构造亮度向量: $$ \mathbf{I}_{uv} = [I_1, I_2, \dots, I_m]^T $$ - 通过最小二乘算法线: $$ \mathbf{n}_{uv} = (\mathbf{L}^T\mathbf{L})^{-1}\mathbf{L}^T\mathbf{I}_{uv} $$ 3. **生成彩色线贴图**: - 将线分量$\mathbf{n}_{uv}=[n_x,n_y,n_z]^T$映射到RGB空间: - $R = (n_x +1)/2 \times 255$ - $G = (n_y +1)/2 \times 255$ - $B = (n_z +1)/2 \times 255$ - 生成规则: - 线平行观察方向时呈蓝色($[0,0,1]$→RGB(127,127,255)) - 线向左倾斜时红色增强($[-1,0,0]$→RGB(0,127,127)) ### 二、Python实现代码 ```python import numpy as np import cv2 def photometric_stereo(images, light_dirs): """ images: 灰度图像列表 [h, w] * m light_dirs: 光源方向矩阵 [m, 3] """ h, w = images[0].shape normals = np.zeros((h, w, 3)) for y in range(h): for x in range(w): I = np.array([img[y,x] for img in images]) n = np.linalg.lstsq(light_dirs, I, rcond=None)[0] normals[y,x] = n / np.linalg.norm(n) # 单位化 return normals def normal_to_color(normal_map): """将线贴图转为RGB图像""" return (normal_map[:,:,[2,1,0]] * 127.5 + 127.5).astype(np.uint8) # 示例调用 if __name__ == "__main__": # 读取灰度图像(示例) images = [cv2.imread(f'light_{i}.png', 0) for i in range(4)] # 光源方向矩阵(需校准) L = np.array([ [0.1, -0.3, 0.9], # 光源1方向 [-0.4, 0.2, 0.8], # 光源2 [0.7, 0.5, 0.4], # 光源3 [-0.2, -0.6, 0.7] # 光源4 ]) normal_map = photometric_stereo(images, L) color_normal = normal_to_color(normal_map) cv2.imwrite('normal_map.png', color_normal) ``` ### 三、关键注意事项 1. **光照校准**: - 光源方向需精确测量(可用镜面球辅助标定) - 建议使用点光源并保持恒定强度[^1] 2. **噪声处理**: - 增加光照方向数量(4-6个)提升鲁棒性 - 添加正则化项:$ \min_{\mathbf{n}} \|\mathbf{Ln} - \mathbf{I}\|^2 + \lambda \|\mathbf{n}\|^2 $ 3. **表面约束**: - 朗伯模型假设:$I = \rho(\mathbf{n} \cdot \mathbf{l})$ - 处理阴影区域(检测亮度<阈值时设为$(0,0,1)$) ### 四、效果优化技术 - **多光谱**:使用RGB相机可一次性获得三通道光度信息 - **深度学习增强**:用UNet网络精化线贴图[^1] - **线融合**:结合SfM/MVS重建结果优化线精度
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值