65.9K
CodeProject 正在变化。 阅读更多。
Home

从深度图创建 3D 图像

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.94/5 (49投票s)

2011年9月20日

CPOL

5分钟阅读

viewsIcon

171344

downloadIcon

8078

介绍一个用于从深度图生成 3D 图像(立体图和红蓝图)的类。

引言

当我们谈论 3D 图像/视频时,我们必须知道一个简单的 3D 图像是由同一物体在不同角度拍摄的两张图像组成的,一张用于一只眼睛。然后大脑可以利用这种差异为自己创建一张深度图。这将使其对外部世界有一个概念,了解物体是近还是远。

但在本文中,我们没有两张图像。我们只有一张。但我们也有深度图。深度图是一种灰度图像,用于描述图像中物体的距离。例如,您可以在下面看到一张图像及其相关的深度图

DoubleTes.jpg

图像版权归 http://www.dofpro.com/ 所有。

使用这个类,您可以从这两张图像生成一张 3D 立体图像。

示例

源代码中有两个示例。一个在 C# 中,另一个在 VB.NET 中。您可以在编码之前测试该库。DOF Pro 网站上还有大量精美的图像/深度图库。您可以使用它们进行测试。

但请不要忘记版权。您还应该在此处阅读“使用条款”:http://www.dofpro.com/terms.htm

使用代码

使用这个类很简单。您只需几行代码就可以生成一张 3D 图像

  • 首先,您需要创建该类的一个新实例
  • C#
    _3DImageGenerator.c_3DGenerator gen = new _3DImageGenerator.c_3DGenerator();
    VB.NET
    Dim gen as New _3DImageGenerator.c_3DGenerator()
  • 生成一张立体图像并检查此过程是否已成功完成
  • C#
    bool success = gen.GenerateStereoscopic((Bitmap)i_image, (Bitmap)i_depth);
    VB.NET
    Dim success As Boolean = gen.GenerateStereoscopic(i_image, i_depth)
  • 使用图像或保存它
  • C#
    pn_output.BackgroundImage = gen.Stereoscopic_SideBySide;
    gen.SaveStereoscopic("c:\\example.jps", 80);
    VB.NET
    pn_output.BackgroundImage = gen.Stereoscopic_SideBySide
    gen.SaveStereoscopic("c:\example.jps", 80)

请注意,该类中有两种类型的函数用于创建

  1. 立体图像(左右眼各一张图像):http://en.wikipedia.org/wiki/Stereoscopy
  2. 红蓝图:一种立体图像,经过合并后可以使用简单的红蓝眼镜作为打印品或显示器上的数字图像观看:http://en.wikipedia.org/wiki/Anaglyph_image

您可以在下一节中阅读有关属性和函数的更多信息。

类属性和方法

该类具有一些属性和方法,可用于更改 3D 图像的生成过程

  • Anaglyph:一个位图对象。红蓝图生成过程的输出。您必须先调用 GenerateAnaglyph,否则此属性将保持 null
  • Stereoscopic_RightChannel:一个位图对象。立体图像生成过程的输出之一。您必须先调用 GenerateStereoscopic,否则此属性将保持 null
  • Stereoscopic_LeftChannel:一个位图对象。立体图像生成过程的输出之一。您必须先调用 GenerateStereoscopic,否则此属性将保持 null
  • Stereoscopic_SideBySide:一个位图对象。包含立体图像生成过程的左右图像。您必须先调用 GenerateStereoscopic,否则此属性将保持 null
  • MaxPixelDisplacement:获取或设置像素在位移中的最大移动量。
  • Smoothing:在位移后的空隙处用边缘像素填充,或将其留黑。
  • SwapRightLeft:交换左右声道。
  • InverseDepthMap:可用于反转输入深度图的像素。当您不确定深度图是如何生成的时非常有用。(有些相机使用“越近越黑”的规则生成深度图,您需要反转它们。)
  • SaveStereoscopic:将立体图像保存为 jps 文件。jps 文件是一个简单的 JPG 文件,包含左右声道。可以使用 nVidia 3D Vision Photo Viewer 等程序打开。
  • SaveAnaglyph:将红蓝图输出保存为文件。
  • GenerateStereoscopic:通过给定图像和深度图创建/更新立体输出。在使用输出之前必须调用。返回值是布尔值。
  • GenerateStereoscopicAsyncGenerateStereoscopic 的异步版本。调用此方法后,您必须等待 StereoscopicComplete 事件。
  • GenerateAnaglyph:通过给定图像和深度图创建/更新红蓝输出。在使用任何输出之前必须调用。返回值是布尔值。
  • GenerateAnaglyphAsyncGenerateAnaglyph 的异步版本。调用此方法后,您必须等待 AnaglyphComplete 事件。

工作原理

大脑如何估算物体的距离?!靠差异。我们如何给大脑提供这种差异?!通过从两个角度拍摄两张图像。两张不同角度的图像有什么区别?!当然是物体的位置。在此代码中,我们尝试根据物体与我们的距离(近或远)来移动它们。此操作在图像处理中称为位移,我们根据深度图动态进行,您可以称之为动态图像位移。最后,如果我们启用了“平滑”功能,我们将尝试用行边缘创建的像素来填充剩余的空间。您可以在下一节中看到部分代码。源代码也有文档记录,即使您不是 VB.NET 程序员,也可以轻松阅读。

一点代码

这是类中的 GenerateStereoscopic 方法。用 VB.NET 编写,但每一行都有注释,因此您可以轻松理解。

它将让您对上一部分的工作原理有一个初步了解

'' Checking if image and depthmap have same size
If Image.Width <> DepthMap.Width OrElse Image.Height <> DepthMap.Height Then
    Throw New ArgumentException("Size of Image and DepthMap are not same.")
'' Check if image and depthmap are 24bitRGB or not
If Image.PixelFormat <> PixelFormat.Format24bppRgb OrElse _
            Image.PixelFormat <> PixelFormat.Format24bppRgb Then
    Throw New ArgumentException("Image and/or DepthMap are/is not 24bitRGB")
Try
    '' Locking image and depthmap so other threads
    '' cant access them when we work on them
    SyncLock Image : SyncLock DepthMap
        '' Create CH2 bitmap for saving output
        b_CH2 = New Bitmap(DepthMap.Width, DepthMap.Height)
        '' Create a rect object, Same size as CH2 bitmap.
        '' Need for direct access in memory
        Dim r_CH2 As Rectangle = _
            New Rectangle(0, 0, DepthMap.Width, DepthMap.Height)
    
        '' Create CH1 bitmap for saving output 
        b_CH1 = New Bitmap(DepthMap.Width, DepthMap.Height)
        '' Create a rect object, Same size as CH1 bitmap.
        '' Need for direct access in memory
        Dim r_CH1 As Rectangle = _
            New Rectangle(0, 0, DepthMap.Width, DepthMap.Height)
    
        '' Calculating real width of image (By byte)
        Dim i_width As Integer = DepthMap.Width * 3
        If i_width Mod 4 <> 0 Then
            i_width = 4 * (i_width / 4 + 1)
        End If  

        '' How much we need to move each pixel per depth byte 
        Dim hsrate As Double = i_maxDisplacement / 255
        '' Creating a rect object with same size. For Depth map
        Dim r_depth As Rectangle = _
            New Rectangle(0, 0, DepthMap.Width, DepthMap.Height)
        '' Opening direct access to bitmap data in memory for Depth map
        Dim d_depth As BitmapData = DepthMap.LockBits(r_depth, _
            ImageLockMode.ReadOnly, _
            System.Drawing.Imaging.PixelFormat.Format24bppRgb)

        '' Creating a rect object with same size. For Image
        Dim r_image As Rectangle = New Rectangle(0, 0, Image.Width, Image.Height)
        '' Opening direct access to bitmap data in memory for Image
        Dim d_image As BitmapData = Image.LockBits(r_image, _
            ImageLockMode.ReadOnly, _
            System.Drawing.Imaging.PixelFormat.Format24bppRgb)

        '' Opening direct access to bitmap data in memory for CH2
        Dim d_ch2 As BitmapData = b_CH2.LockBits(r_CH2, ImageLockMode.ReadWrite, _
            System.Drawing.Imaging.PixelFormat.Format24bppRgb)
        '' Opening direct access to bitmap data in memory for CH1
        Dim d_ch1 As BitmapData = b_CH1.LockBits(r_CH1, _
            ImageLockMode.ReadWrite, _
            System.Drawing.Imaging.PixelFormat.Format24bppRgb)

        Dim sfp As Integer
        For y As Integer = 0 To DepthMap.Height - 1
            '' Calculate location of current line's last free pixel
            Dim rLPDest As IntPtr = (y + 1) * i_width - (i_maxDisplacement * 3) - 3

            '' Calculate location of current line's first free pixel 
            Dim rFPDest As IntPtr = y * i_width + (i_maxDisplacement * 3)

            '' Count for each pixel on width of image.
            '' Cut MaxDisplacementfrom from both sides
            For x As Integer = i_maxDisplacement To _
                     DepthMap.Width - 1 - i_maxDisplacement

                ''''''''''''''''''''''''''''''''''' Right 2 Left
                '' Read Depth, Right to Left
                Dim depthrgb As Byte = ReadByte(d_depth.Scan0 + rLPDest + 1)
                If InverseDepthMap Then depthrgb = 255 - depthrgb
                '' Calculate displacement offset, Right to Left 
                sfp = depthrgb * hsrate 
                '' Read a pixel from image, Right to Left 
                Dim imagergb(2) As Byte
                Copy(d_image.Scan0 + rLPDest, imagergb, 0, 3)

                '' Correct CH2 Displacement, Right to Left 
                Copy(imagergb, 0, d_ch1.Scan0 + rLPDest + ((sfp) * 3), 3)
    
                '' Smoothing  
                If b_Smoothing And sfp <> 0 Then
                   '' Calculate color changes between pixels (For better
                   '' smoothing we use 4 pixel and then get an average)
                    Dim ich2Rgrate, ich2Ggrate, ich2Bgrate As Double
                    Dim db(11) As Byte 
                    Copy(d_image.Scan0 + rLPDest - 12, db, 0, 12) 
                    db(11) = CType((CType(db(11), Integer) + _
                             CType(db(8), Integer)) / 2, Byte)
                    db(10) = CType((CType(db(10), Integer) + _
                             CType(db(7), Integer)) / 2, Byte) 
                    db(9) = CType((CType(db(9), Integer) + _
                            CType(db(6), Integer)) / 2, Byte) 
                    db(2) = CType((CType(db(2), Integer) + _
                            CType(db(5), Integer)) / 2, Byte) 
                    db(1) = CType((CType(db(1), Integer) + _
                            CType(db(4), Integer)) / 2, Byte)
                    db(0) = CType((CType(db(0), Integer) + _
                            CType(db(3), Integer)) / 2, Byte)

                    '' Split color changes between pixels that we
                    '' need to write. So we can create a gradient effect 
                    ich2Rgrate = (CType(db(2), Integer) - _
                                  CType(db(11), Integer)) / (sfp + 1) 
                    ich2Ggrate = (CType(db(1), Integer) - _
                                  CType(db(10), Integer)) / (sfp + 1)
                    ich2Bgrate = (CType(db(0), Integer) - _
                                  CType(db(9), Integer)) / (sfp + 1) 

                    '' Apply Smoothing
                    For i As Integer = 0 To sfp - 1
                        '' CH2 Smoothing
                        db(0) = db(9) + (i * ich2Bgrate)
                        db(1) = db(10) + (i * ich2Ggrate)
                        db(2) = db(11) + (i * ich2Rgrate)
                        Copy(db, 0, d_ch1.Scan0 + rLPDest + _
                             (((sfp - 1) - i) * 3), 3)
                    Next
                End If
                '' Go to Last Pixel
                rLPDest -= 3

                ''''''''''''''''''''''''''''''''''' Left 2 Right
                '' Read Depth, Left to Right
                depthrgb = ReadByte(d_depth.Scan0 + rFPDest + 1)
                If InverseDepthMap Then depthrgb = 255 - depthrgb

                '' Calculate displacement offset, Left to Right  
                sfp = depthrgb * hsrate 

                '' Read a pixel from image, Left to Right
                Copy(d_image.Scan0 + rFPDest, imagergb, 0, 3) 

                '' Correct CH1 Displacement, Left to Right 
                Copy(imagergb, 0, d_ch2.Scan0 + rFPDest + (-sfp * 3), 3) 

                '' Smoothing 
                If b_Smoothing And sfp <> 0 Then 

                    '' Calculate color changes between pixels
                    '' (For better smoothing we use
                    '' 4 pixel and then get an average) 
                    Dim ich1Rgrate, ich1Ggrate, ich1Bgrate As Double
                    Dim db(11) As Byte
                    Copy(d_image.Scan0 + rFPDest + 3, db, 0, 12)
                    db(11) = CType((CType(db(11), Integer) + _
                             CType(db(8), Integer)) / 2, Byte)
                    db(10) = CType((CType(db(10), Integer) + _
                             CType(db(7), Integer)) / 2, Byte)
                    db(9) = CType((CType(db(9), Integer) + _
                            CType(db(6), Integer)) / 2, Byte)
                    db(2) = CType((CType(db(2), Integer) + _
                            CType(db(5), Integer)) / 2, Byte)
                    db(1) = CType((CType(db(1), Integer) + _
                            CType(db(4), Integer)) / 2, Byte) 
                    db(0) = CType((CType(db(0), Integer) + _
                            CType(db(3), Integer)) / 2, Byte)

                    '' Split color changes between pixels that
                    '' we need to write. So we can create a gradient effect
                    ich1Rgrate = (CType(db(2), Integer) - _
                                  CType(db(11), Integer)) / (sfp + 1)
                    ich1Ggrate = (CType(db(1), Integer) - _
                                  CType(db(10), Integer)) / (sfp + 1)
                    ich1Bgrate = (CType(db(0), Integer) - _
                                  CType(db(9), Integer)) / (sfp + 1)

                    '' Apply Smoothing
                    For i As Integer = 0 To sfp - 1
                        '' CH1 Smoothing
                        db(0) = db(9) + ((sfp - i) * ich1Bgrate)
                        db(1) = db(10) + ((sfp - i) * ich1Ggrate)
                        db(2) = db(11) + ((sfp - i) * ich1Rgrate)
                        Copy(db, 0, d_ch2.Scan0 + rFPDest + (-i * 3), 3)
                    Next
                End If
                '' Go to Next Pixel
                rFPDest += 3
            Next
        Next

        '' Closing direct access 
        DepthMap.UnlockBits(d_depth)
        Image.UnlockBits(d_image)
        b_CH2.UnlockBits(d_ch2)
        b_CH1.UnlockBits(d_ch1)
    End SyncLock : End SyncLock
    Return True
Catch ex As Exception
        Return False
End Try

关注点

  1. 此代码中一个有趣的地方是使用了 System.Runtime.InteropServices.Marshal 类来执行 VB.NET 中通常不支持的不安全操作;这将使我们能够直接访问内存中的数据,这比使用 GetPixelSetPixel 等函数快得多。
  2. 我最初为一款可以从 Kinect(微软的运动捕捉设备,用于 Xbox 游戏机)获取深度和 RGB 流,然后根据该信息创建 3D 图像的应用程序编写了这个类。您可以从以下地址下载源代码:http://www.kinectdevs.com/forums/filebase.php
© . All rights reserved.