从深度图创建 3D 图像






4.94/5 (49投票s)
介绍一个用于从深度图生成 3D 图像(立体图和红蓝图)的类。
引言
当我们谈论 3D 图像/视频时,我们必须知道一个简单的 3D 图像是由同一物体在不同角度拍摄的两张图像组成的,一张用于一只眼睛。然后大脑可以利用这种差异为自己创建一张深度图。这将使其对外部世界有一个概念,了解物体是近还是远。
但在本文中,我们没有两张图像。我们只有一张。但我们也有深度图。深度图是一种灰度图像,用于描述图像中物体的距离。例如,您可以在下面看到一张图像及其相关的深度图
图像版权归 http://www.dofpro.com/ 所有。
使用这个类,您可以从这两张图像生成一张 3D 立体图像。
示例
源代码中有两个示例。一个在 C# 中,另一个在 VB.NET 中。您可以在编码之前测试该库。DOF Pro 网站上还有大量精美的图像/深度图库。您可以使用它们进行测试。
- http://www.dofpro.com/cgigallery.htm
- http://www.dofpro.com/complexgallery.htm
- http://www.dofpro.com/cagallery.htm
但请不要忘记版权。您还应该在此处阅读“使用条款”:http://www.dofpro.com/terms.htm
使用代码
使用这个类很简单。您只需几行代码就可以生成一张 3D 图像
- 首先,您需要创建该类的一个新实例
_3DImageGenerator.c_3DGenerator gen = new _3DImageGenerator.c_3DGenerator();
Dim gen as New _3DImageGenerator.c_3DGenerator()
bool success = gen.GenerateStereoscopic((Bitmap)i_image, (Bitmap)i_depth);
Dim success As Boolean = gen.GenerateStereoscopic(i_image, i_depth)
pn_output.BackgroundImage = gen.Stereoscopic_SideBySide;
gen.SaveStereoscopic("c:\\example.jps", 80);
pn_output.BackgroundImage = gen.Stereoscopic_SideBySide
gen.SaveStereoscopic("c:\example.jps", 80)
请注意,该类中有两种类型的函数用于创建
- 立体图像(左右眼各一张图像):http://en.wikipedia.org/wiki/Stereoscopy
- 红蓝图:一种立体图像,经过合并后可以使用简单的红蓝眼镜作为打印品或显示器上的数字图像观看: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
:通过给定图像和深度图创建/更新立体输出。在使用输出之前必须调用。返回值是布尔值。GenerateStereoscopicAsync
:GenerateStereoscopic
的异步版本。调用此方法后,您必须等待StereoscopicComplete
事件。GenerateAnaglyph
:通过给定图像和深度图创建/更新红蓝输出。在使用任何输出之前必须调用。返回值是布尔值。GenerateAnaglyphAsync
:GenerateAnaglyph
的异步版本。调用此方法后,您必须等待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
关注点
- 此代码中一个有趣的地方是使用了
System.Runtime.InteropServices.Marshal
类来执行 VB.NET 中通常不支持的不安全操作;这将使我们能够直接访问内存中的数据,这比使用GetPixel
或SetPixel
等函数快得多。 - 我最初为一款可以从 Kinect(微软的运动捕捉设备,用于 Xbox 游戏机)获取深度和 RGB 流,然后根据该信息创建 3D 图像的应用程序编写了这个类。您可以从以下地址下载源代码:http://www.kinectdevs.com/forums/filebase.php。