深度图,亮度图,点云图获取以及深度图转点云图

前言

        本文中图像采集选取硬件为3D轮廓传感器,也称3D线激光或3D相机,至于其硬件采图原理(三角测量法)这里就不做详细说明,想了解的小伙伴可以去网上搜索相关资料,这里我们着重讲解基于halcon算子,获取深度图,亮度图,以及如何将深度图转换成点云图,并进行显示。

1) 连接外部3D相机后,我们一般需要设置帧/行触发方式,以及图像行高,保证产品扫描的完整性;然后采集过程中,不论使用轮询采集或者图像回调的方式,在图像数据返回的事件中,取得图像的源数据(指针,数组,实例对象,图像信息包括图像格式,行高等),然后通过halcon算子“GenImage1”即可完整数据的转换,最终可输出halcon数据对象hobject表示的图像,下面所示代码即对此做出具体演示

图像轮询线程

 /// <summary>
 /// 轮询取图流程
 /// </summary>
 private void ReceiveThreadProcess()
 {
     int nRet = (int)Mv3dLpSDK.MV3D_LP_OK;

     STC_DataSet pDataSet = IntPtr.Zero;
     UInt32 nTimeOut = 50;

     while (m_bGrabbing)
     {
         Thread.Sleep(1);
         MV3D_LP_IMAGE_DATA pstImage = new MV3D_LP_IMAGE_DATA();
         nRet = Mv3dLpSDK.MV3D_LP_GetImage(m_DevHandle, pstImage, nTimeOut);
         if (0 == nRet)
         {
             try
             {
               
                 nRet = DisplayImage(pstImage);//存储原始数据,方便后续获取其他图像
                 GrabFlagEvent?.Invoke((int)Mv3dLpSDK.MV3D_LP_OK == nRet);//原始输出存储完成               
                 
                 if ((int)Mv3dLpSDK.MV3D_LP_OK != nRet)
                 {
                     throw new ArgumentException(nRet.ToString());
                 }
             }
             catch
             {
                 Console.WriteLine("ERROR  !\r\n");
             }
         }
         else
         {
             continue;
         }
     }
 }

 图像数据缓存:

 /// <summary>
 /// 图像显示
 /// </summary>
 /// <param name="pstImage"></param>
 /// <returns></returns>
 private int DisplayImage(MV3D_LP_IMAGE_DATA pstImage)
 {
     int nRet = (int)Mv3dLpSDK.MV3D_LP_OK;
     Monitor.Enter(Lock);
     m_stImageInfo.nWidth = pstImage.nWidth;
     m_stImageInfo.nHeight = pstImage.nHeight;
     m_stImageInfo.nDataLen = pstImage.nDataLen;
     m_stImageInfo.nIntensityDataLen = pstImage.nIntensityDataLen;
     m_stImageInfo.enImageType = pstImage.enImageType;
     m_stImageInfo.nFrameNum = pstImage.nFrameNum;
     m_stImageInfo.fXScale = pstImage.fXScale;
     m_stImageInfo.fYScale = pstImage.fYScale;
     m_stImageInfo.fZScale = pstImage.fZScale;
     m_stImageInfo.nXOffset = pstImage.nXOffset;
     m_stImageInfo.nYOffset = pstImage.nYOffset;
     m_stImageInfo.nZOffset = pstImage.nZOffset;
     if (m_nMaxImageSize < pstImage.nDataLen)
     {
         m_pcDataBuf = new byte[pstImage.nDataLen];
         m_nMaxImageSize = pstImage.nDataLen;

     }
     if (m_nMaxImageSize2 < pstImage.nIntensityDataLen)
     {
         m_pcDataBuf2 = new byte[pstImage.nIntensityDataLen];
         m_nMaxImageSize2 = pstImage.nIntensityDataLen;

     }
     m_stImageInfo.pData = Marshal.UnsafeAddrOfPinnedArrayElement(m_pcDataBuf, 0);
     m_stImageInfo.pIntensityData = Marshal.UnsafeAddrOfPinnedArrayElement(m_pcDataBuf2, 0);
     Marshal.Copy(pstImage.pData, m_pcDataBuf, 0, (int)pstImage.nDataLen);
     Marshal.Copy(pstImage.pIntensityData, m_pcDataBuf2, 0, (int)pstImage.nIntensityDataLen);
     Monitor.Exit(Lock);
     //不在此处显示
     //nRet = Mv3dLpSDK.MV3D_LP_DisplayImage(pstImage, m_hWnd, Mv3dLpSDK.DisplayType_Auto, 0, 0);

     return nRet;
 }

通过源数据生成深度图hobject 

  /// <summary>
  /// 获取深度图(.tiff) 
  /// </summary>
  /// <returns></returns>
  public HObject GetDepthImage()
  {
      HObject ho_Image = null;
      Monitor.Enter(Lock);
      GCHandle hBuf = GCHandle.Alloc(m_pcDataBuf, GCHandleType.Pinned);
      IntPtr ptr = hBuf.AddrOfPinnedObject();
      HOperatorSet.GenImage1(out ho_Image, "int2", m_stImageInfo.nWidth, m_stImageInfo.nHeight, ptr.ToInt64());
      Monitor.Exit(Lock);
      if (hBuf.IsAllocated)
      {
          hBuf.Free();
      }
      //if (null != ho_Image)
      //{
      //    // 保存深度图 | Save Depth
      //    string strtiffName = "./Halcon_Image_";
      //    strtiffName += m_stImageInfo.nFrameNum;
      //    strtiffName += ".tiff";
      //    HOperatorSet.WriteImage(ho_Image, "tiff", 0, strtiffName);
      //}
      return ho_Image;
  }

下图为显示的深度图:

2)通过相似的方法我们可以获取亮度图

/// <summary>
/// 获取亮度图(.bmp) 
/// </summary>
/// <returns></returns>
public HObject GetIntensityImage()
{

    HObject ho_Image = null;
    Monitor.Enter(Lock);
    GCHandle hBuf = GCHandle.Alloc(m_pcDataBuf2, GCHandleType.Pinned);
    IntPtr ptr = hBuf.AddrOfPinnedObject();
    HOperatorSet.GenImage1(out ho_Image, "byte", m_stImageInfo.nWidth, m_stImageInfo.nHeight, ptr.ToInt64());
    Monitor.Exit(Lock);

    if (hBuf.IsAllocated)
    {
        hBuf.Free();
    }
    //if (null != ho_Image)
    //{
    //    // 保存深度图 | Save Depth
    //    string strtiffName = "./Halcon_Image_";
    //    strtiffName += m_stImageInfo.nFrameNum;
    //    strtiffName += ".bmp";
    //    HOperatorSet.WriteImage(ho_Image, "bmp", 0, strtiffName);
    //}
    return ho_Image;
}

3)点云图的获取可以直接通过客户端SDK中相应的接口来完成,这里不做说明,我们着重说明下如何通过halcon算子将源数据生成点云图,获取点云对象(HTuple)

  /// <summary>
  /// 获取点云对象(object handle)   
  /// </summary>
  /// <returns></returns>
  public HTuple GetObjectModel3D()
  {
      MV3D_LP_IMAGE_DATA stImageInfoTmp = new MV3D_LP_IMAGE_DATA();
      Monitor.Enter(Lock);
      GCHandle hBuf = GCHandle.Alloc(m_pcDataBuf, GCHandleType.Pinned);
      IntPtr ptr = hBuf.AddrOfPinnedObject();
      stImageInfoTmp.enImageType = m_stImageInfo.enImageType;
      stImageInfoTmp.nFrameNum = m_stImageInfo.nFrameNum;
      stImageInfoTmp.nWidth = m_stImageInfo.nWidth;
      stImageInfoTmp.nHeight = m_stImageInfo.nHeight;
      stImageInfoTmp.nDataLen = m_stImageInfo.nDataLen;
      stImageInfoTmp.pData = ptr;
      stImageInfoTmp.fXScale = m_stImageInfo.fXScale;
      stImageInfoTmp.fYScale = m_stImageInfo.fYScale;
      stImageInfoTmp.fZScale = m_stImageInfo.fZScale;
      stImageInfoTmp.nXOffset = m_stImageInfo.nXOffset;
      stImageInfoTmp.nYOffset = m_stImageInfo.nYOffset;
      stImageInfoTmp.nZOffset = m_stImageInfo.nZOffset;
      MV3D_LP_IMAGE_DATA plyData = new MV3D_LP_IMAGE_DATA();
      //先通过接口将深度图转点云图
      Mv3dLpSDK.MV3D_LP_MapDepthToPointCloud(stImageInfoTmp, plyData);

      //再将点云数据转换成halcon 可用的对象指针
      HTuple hObjectModel3D = null;
      HTuple hv_X = new HTuple();
      HTuple hv_Y = new HTuple();
      HTuple hv_Z = new HTuple();
      float[] ConvertData = new float[plyData.nDataLen / 4];
      Marshal.Copy(plyData.pData, ConvertData, 0, (int)plyData.nDataLen / 4);
      for (int j = 0; j < plyData.nWidth * plyData.nHeight; j++)
      {
          hv_X[j] = ConvertData[j * 3];
          hv_Y[j] = ConvertData[j * 3 + 1];
          hv_Z[j] = ConvertData[j * 3 + 2];
      }
      HOperatorSet.GenObjectModel3dFromPoints(hv_X, hv_Y, hv_Z, out hObjectModel3D);

      Monitor.Exit(Lock);

      if (hBuf.IsAllocated)
      {
          hBuf.Free();
      }
      //if (null != hObjectModel3D)
      //{
      //    // 保存点云图 | Save PointCloud
      //    string strPlyName = "./Halcon_Image_";
      //    strPlyName += plyData.nFrameNum;
      //    strPlyName += ".ply";
      //    HOperatorSet.WriteObjectModel3d(hObjectModel3D, "ply", strPlyName, "invert_normals", "false");

      //}
      return hObjectModel3D;
  }

代码中设计到的XYZ缩放以及偏移,均可通过硬件技术资料获取,有关像素坐标到物理坐标的计算公式不同的硬件有些许的差异,大家不用担心;在这个转换工程中会使用一个关键的halcon算子“GenObjectModel3dFromPoints”

 gen_object_model_3d_from_points( : : X, Y, Z : ObjectModel3D)

  函数说明: 创建表示点云的三维对象模型。这些点由参数x、y和z坐标描述。

  函数参数:
    X:输入三维点云中点的x坐标;
    Y:输入 三维点云中点的y坐标;
    Z:输入 三维点云中点的y坐标;
    ObjectModel3D:输出 3D对象模型句柄;

4)上面显示如何将相机源数据转换成点云对象模型句柄Htuple,其实我们可以只获取相机的深度图,然后通过方法将深度图转换成点云图并进行操作和显示,如下将说明转换的具体方法

/// <summary>
/// 深度图转点云图
/// </summary>
/// <param name="ho_ImageH">输入深度图</param>
/// <param name="hv_XScale">转点云坐标X缩放</param>
/// <param name="hv_XOffset">转点云坐标X偏移</param>
/// <param name="hv_YScale">转点云坐标Y缩放</param>
/// <param name="hv_YOffset">转点云坐标Y偏移</param>
/// <param name="hv_ZScale">转点云坐标Z缩放</param>
/// <param name="hv_ZOffset">转点云坐标Z偏移</param>
/// <returns></returns>
public static HTuple DepthImg2PointCloud(HObject ho_ImageH,
    double hv_XScale, double hv_XOffset,
     double hv_YScale, double hv_YOffset,
      double hv_ZScale, double hv_ZOffset,
      out HObject ho_ImageX, out HObject ho_ImageY, out HObject ho_ImageZ)
{
    //对象变量
    HObject ho_Domain, ho_ImageSurface;
    HObject ho_ImageSurface1, ho_Region1;
    HObject ho_Region2, ho_RegionDifference, ho_ImageReduced;
    HObject ho_ImageHReal;
    //转化比例    
    HTuple hv_Width = new HTuple();
    HTuple hv_Height = new HTuple(), hv_Min = new HTuple();
    HTuple hv_Max = new HTuple(), hv_Range = new HTuple();
    HTuple hv_ObjectModel3D = new HTuple();
    //初始化      
    HOperatorSet.GenEmptyObj(out ho_Domain);
    HOperatorSet.GenEmptyObj(out ho_ImageSurface);
    HOperatorSet.GenEmptyObj(out ho_ImageSurface1);
    HOperatorSet.GenEmptyObj(out ho_ImageX);
    HOperatorSet.GenEmptyObj(out ho_ImageY);
    HOperatorSet.GenEmptyObj(out ho_Region1);
    HOperatorSet.GenEmptyObj(out ho_Region2);
    HOperatorSet.GenEmptyObj(out ho_RegionDifference);
    HOperatorSet.GenEmptyObj(out ho_ImageReduced);
    HOperatorSet.GenEmptyObj(out ho_ImageHReal);
    HOperatorSet.GenEmptyObj(out ho_ImageZ);

    //* 单位mm          
    //hv_ZScale = 0.001;

    //using (HDevDisposeHelper dh = new HDevDisposeHelper())
    //{
    //    hv_ZOffset = -10.0 / 1000;
    //}
    //hv_XScale.Dispose();
    //hv_XScale = 0.020;
    //hv_XOffset.Dispose();
    //using (HDevDisposeHelper dh = new HDevDisposeHelper())
    //{
    //    hv_XOffset = -7051.0 / 1000;
    //}
    //hv_YScale.Dispose();
    //hv_YScale = -0.020;
    //hv_YOffset.Dispose();
    //hv_YOffset = 0.0;

    //xyz三通道的深度图
    hv_Width.Dispose(); hv_Height.Dispose();
    HOperatorSet.GetImageSize(ho_ImageH, out hv_Width, out hv_Height);
    ho_Domain.Dispose();
    HOperatorSet.GetDomain(ho_ImageH, out ho_Domain);
    //生成xy坐标的图像映射,乘以分辨率就是xy的相对值
    yImage
    ho_ImageSurface.Dispose();
    HOperatorSet.GenImageSurfaceFirstOrder(out ho_ImageSurface, "real", 1.0, 0.0,
        0.0, 0.0, 0.0, hv_Width, hv_Height);
    xImage
    ho_ImageSurface1.Dispose();
    HOperatorSet.GenImageSurfaceFirstOrder(out ho_ImageSurface1, "real", 0.0, 1.0,
        0.0, 0.0, 0.0, hv_Width, hv_Height);
    ho_ImageX.Dispose();
    HOperatorSet.ScaleImage(ho_ImageSurface1, out ho_ImageX, hv_XScale, hv_XOffset);
    ho_ImageY.Dispose();
    HOperatorSet.ScaleImage(ho_ImageSurface, out ho_ImageY, hv_YScale, hv_YOffset);

    //使用算子scale_image将图像灰度值转实际高度,并提取最大最小高度
    ho_Domain.Dispose();
    HOperatorSet.GetDomain(ho_ImageH, out ho_Domain);
    hv_Min.Dispose(); hv_Max.Dispose(); hv_Range.Dispose();
    HOperatorSet.MinMaxGray(ho_Domain, ho_ImageH, 0, out hv_Min, out hv_Max, out hv_Range);
    ho_Region1.Dispose();
    HOperatorSet.Threshold(ho_ImageH, out ho_Region1, hv_Min, hv_Max);
    using (HDevDisposeHelper dh = new HDevDisposeHelper())
    {
        ho_Region2.Dispose();
        HOperatorSet.Threshold(ho_ImageH, out ho_Region2, hv_Min, hv_Min + 2);
    }
    ho_RegionDifference.Dispose();
    HOperatorSet.Difference(ho_Region1, ho_Region2, out ho_RegionDifference);
    ho_ImageReduced.Dispose();
    HOperatorSet.ReduceDomain(ho_ImageH, ho_RegionDifference, out ho_ImageReduced
        );
    ho_ImageHReal.Dispose();
    HOperatorSet.ConvertImageType(ho_ImageReduced, out ho_ImageHReal, "real");
    ho_ImageZ.Dispose();
    HOperatorSet.ScaleImage(ho_ImageHReal, out ho_ImageZ, hv_ZScale, hv_ZOffset);
    //ho_MultiChannelImage.Dispose();
    //HOperatorSet.Compose3(ho_ImageX, ho_ImageY, ho_ImageZ, out ho_MultiChannelImage
    //    );
    hv_ObjectModel3D.Dispose();
    HOperatorSet.XyzToObjectModel3d(ho_ImageX, ho_ImageY, ho_ImageZ, out hv_ObjectModel3D);
    //点云保存
    //HOperatorSet.WriteObjectModel3d(hv_ObjectModel3D, "ply", "transply.ply",
    //    new HTuple(), new HTuple());
    ho_Domain.Dispose();
    ho_ImageSurface.Dispose();
    ho_ImageSurface1.Dispose();
    //ho_ImageX.Dispose();
    //ho_ImageY.Dispose();
    ho_Region1.Dispose();
    ho_Region2.Dispose();
    ho_RegionDifference.Dispose();
    ho_ImageReduced.Dispose();
    ho_ImageHReal.Dispose();
    //ho_ImageZ.Dispose();

    return hv_ObjectModel3D;
}

上图代码中都有详细的注解,所以就不做进一步说明,不过最后会使用一个关键的算子“XyzToObjectModel3d”来进行转换

xyz_to_object_model_3d
(X, Y, Z : : : ObjectModel3D)
将3D点从图像转换为3D对象模型。

输入:

X (input_object) 单通道图像→object (real)
用三维点的x坐标和三维点ROI区域。
Y (input_object) 单通道图像→object (real)
图像与三维点的y坐标。
Z (input_object) 单通道图像→object (real)
图像与三维点的z坐标。
输出:

ObjectModel3D (output_control)
3D对象模型的句柄。

5)通过上面的方法转换后,咱们在这将点云显示出来看看   

点云的查看会用到一个重要的算子“visualize_object_model_3d”

visualize_object_model_3d( : : WindowHandle, ObjectModel3D, CamParam, PoseIn, GenParamName, GenParamValue, Title, Label, Information : PoseOut)

WindowHandle:显示窗口句柄

ObjectModel3D:需要展示的3d模型

CamParam:假想的观察这个模型的一个面阵相机的内参

PoseIn:这个模型的3d姿态

GenParamName:参数名

GenParamValue:参数值

Title:展示在窗口左上角的文字

Label:在每个3d模型位置显示的文本

Information:窗口左下角显示的信息

PoseOut:用户调整模型姿态后输出这个3d姿态

6)附件:我们来看看转换后的点云如何查看和操作

点云查看器

该文章主要讲解了图像的来源以及他们之间如何进行转换,并且生成可通过halcon操作的对象,后面的文件我们会一一讲解图像显示控件以及点云的相关操作算法,敬请期待!!!

<think>好的,用户想将深度图换为点云图,使用OpenCV和点云库(比如PCL)来实现。我需要分步骤解释这个过程,确保符合系统指令中的格式要求,比如行内数学公式用$...$,独立公式用$$...$$,并且生成相关问题。同时要引用提供的参考资料。 首先,换的基本原理。深度图中的每个像素值代表距离,需要结合相机内参将其换为三维坐标。相机内参包括焦距$f_x$, $f_y$和光心坐标$(c_x, c_y)$。公式部分需要写成独立公式,用$$...$$。例如,每个点的三维坐标计算式为: $$X = \frac{(u - c_x) \cdot d}{f_x}$$ $$Y = \frac{(v - c_y) \cdot d}{f_y}$$ $$Z = d$$ 这里要确保公式正确,并且变量用斜体。 然后,步骤部分。第一步是读取深度图,可能需要换为浮点型。第二步处理内参矩阵,注意用户可能需要替换示例中的参数。第三步遍历每个像素,计算对应的三维点。这里要提到使用OpenCV的Mat数据结构和双重循环。第四步保存点云,提到PCL库的使用和保存为PLY或PCD格式。 代码示例部分,需要用代码块展示OpenCV的实现,并注释关键步骤。同时,要提醒安装PCL库,并注意可能的编译问题,比如引用中提到的vtkViewsInfovisopencv_calib3d.lib错误,可能是库路径或版本问题。 接下来,生成相关问题。根据用户可能感兴趣的扩展方向,比如不同工具的对比、处理大尺寸深度图的优化、深度图预处理方法、点云后处理,以及Open3D的应用。这些问题需要与主题相关,并帮助用户进一步探索。 最后,检查引用是否在适当的位置添加。比如在提到性能优化时引用提供的资料,或者在处理错误时提到引用中的vtk问题。确保引用标识自然,比如在讨论工具或错误时添加[^1][^2]。 还要注意所有数学表达式是否正确使用LaTeX,行内公式用$...$,独立公式单独成段。例如,内参矩阵的表示应该用独立公式: $$ K = \begin{bmatrix} f_x & 0 & c_x \\ 0 & f_y & c_y \\ 0 & 0 & 1 \end{bmatrix} $$ 检查是否有遗漏的步骤,比如是否需要处理深度图的单位(米或毫米),或者是否需要考虑坐标系的换(比如OpenCV和PCL可能使用不同的坐标系)。如果有,需要提醒用户注意这些细节。 总结整个流程,确保逻辑清晰,步骤明确,代码示例正确,并且相关问题覆盖用户可能的需求。同时,保持回答的中文,避免使用Markdown格式,除了代码块和公式部分。</think>要将深度图换为点云图,需结合相机内参和几何关系,以下是使用OpenCV和PCL的详细步骤: ### 1. 原理分析 深度图中每个像素值$d$表示距离,通过相机内参矩阵$K$可计算三维坐标$(X,Y,Z)$: $$ K = \begin{bmatrix} f_x & 0 & c_x \\ 0 & f_y & c_y \\ 0 & 0 & 1 \end{bmatrix} $$ 每个像素$(u,v)$的三维坐标公式为: $$ \begin{cases} X = \dfrac{(u - c_x) \cdot d}{f_x} \\ Y = \dfrac{(v - c_y) \cdot d}{f_y} \\ Z = d \end{cases} $$ ### 2. 实现步骤 #### (1) 读取深度图 ```python import cv2 depth_image = cv2.imread("depth.png", cv2.IMREAD_ANYDEPTH) # 必须保留深度原始数据 depth_image = depth_image.astype(float) / 1000.0 # 假设深度单位为毫米需米 ``` #### (2) 设置相机参数 ```python fx, fy = 525.0, 525.0 # Kinect v1典型参数 cx, cy = 319.5, 239.5 ``` #### (3) 坐标换核心算法 ```python point_cloud = [] for v in range(depth_image.shape[0]): for u in range(depth_image.shape[1]): d = depth_image[v, u] if d <= 0: continue # 过滤无效点 x = (u - cx) * d / fx y = (v - cy) * d / fy point_cloud.append([x, y, d]) ``` #### (4) 保存点云(PCL实现) ```cpp #include <pcl/io/ply_io.h> pcl::PointCloud<pcl::PointXYZ> cloud; // 填充点云数据 pcl::io::savePLYFile("output.ply", cloud); ``` *注意:编译PCL时需正确配置VTK依赖库路径* ### 3. 优化建议 - **并行计算**:使用OpenCV的并行循环可加速处理[^2] - **降采样**:对深度图进行双边滤波减少噪声 - **GPU加速**:CUDA实现像素级并行计算
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

%Onmyway

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值