声明:本程序代码以国外Jason Antic的DeOldify程序C#代码为基础,做了适应VB.NET程序和语言的变动,免费共享给大家使用。本代码供具有一定VB.NET基础的爱好者学习研究使用,如果想学习基础图像操作知识,请见我的视频课程《VB.NET】二维图形绘制》 等系列课程。

前期建立的界面,在点击 OpenInput 按钮获取到输入黑白图像 InputBitmap 后,再点击 StartButton1 就开始上色之旅了。

1.在前面搭建启动界面的 MainForm.Load 窗体启动事件的处理程序中,我们可以看到它首先调用了 DeOldify.Initialize() 。所以我们接着在应用程序项目的资源管理器里再添加一个 DeOldify 类,这个类里提供的都是 Shared 成员,所以在 vb.net 里把 DeOldify 声明为一个 Class 或 Module 代码块都无所谓,也不用提供 New 构建方法。由于里面有个 Initialize 初始化方法,我们按照 C# 语法也把它声明为一个 Class 代码块,并在类上方添加上后期代码所需要使用的命名空间。

Imports System.IO
Imports System.Numerics
Imports System.Reflection
Imports System.Runtime.InteropServicesFriend NotInheritable Class DeOldifyEnd 

 2.在该类里添加 Initialize 方法成员,用于提前准备上色所需的模型参数,从嵌入资源加载模型参数到字典集合 :

 Public Shared Sub Initialize()Dim br As New BinaryReader(Assembly.GetExecutingAssembly().GetManifestResourceStream("ColorizeOldPhotos.Stable.model"))Parameters = New Dictionary(Of String, Tensor)()'以下代码逐一向Parameters字典集合添加大量模型参数'由于参数太多,为了观看代码方便,这里暂时省略'我把这些参数放在了最后一节单独提供,大家建立代码时去复制回来粘贴到这里
End Sub

3.在界面窗口的 StartButton1.Click 事件的  StartHandler 处理程序里,我们可以看到,它的执行入口是 DeOldify.Colorize(InputBitmap),所以我们先把这个方法成员代码添加上。该方法用于处理黑白图像上色,参数 bw 为输入的黑白图像 Bitmap 类对象,返回值为上色后的彩色 Bitmap 对象。

Public Shared Function Colorize(bw As Bitmap) As Bitmap__Progress = 0.0FDim x As Tensor = Image2Tensor(Resize(bw))Dim x1 As Tensor = Functional.ReLU_(BatchNorm2d(Conv2d(x, "layers.0.0", Parameters, 2, 3), "layers.0.1", Parameters))Dim x2 As Tensor = BasicBlock(BasicBlock(BasicBlock(Functional.MaxPool2d(x1, 3, 3, 2, 2, 1, 1, 1, 1),"layers.0.4.0", Parameters),"layers.0.4.1", Parameters),"layers.0.4.2", Parameters)Dim x3 As Tensor = BasicBlock(BasicBlock(BasicBlock(BasicBlock(x2, "layers.0.5.0", Parameters, True),"layers.0.5.1", Parameters),"layers.0.5.2", Parameters),"layers.0.5.3", Parameters)Dim x4 As Tensor = BasicBlock(BasicBlock(BasicBlock(BasicBlock(BasicBlock(x3, "layers.0.6.0", Parameters, True),"layers.0.6.1", Parameters),"layers.0.6.2", Parameters),"layers.0.6.3", Parameters),"layers.0.6.4", Parameters)For i As Integer = 5 To 19x4 = BasicBlock(x4, $"layers.0.6.{i}", Parameters)NextDim x5 As Tensor = BasicBlock(BasicBlock(BasicBlock(x4, "layers.0.7.0", Parameters, True),"layers.0.7.1", Parameters),"layers.0.7.2", Parameters)Dim y As Tensor = Functional.ReLU_(BatchNorm2d(x5, "layers.1", Parameters))y = MiddleBlock(y, "layers.3", Parameters)y = UnetBlockWide(y, x4, "layers.4", Parameters)y = UnetBlockWide(y, x3, "layers.5", Parameters, True)y = UnetBlockWide(y, x2, "layers.6", Parameters)y = UnetBlockWide(y, x1, "layers.7", Parameters)y = PixelShuffle(y, "layers.8", Parameters)y = Functional.RestrictedCat2d(y, x)y = ResBlock(y, "layers.10", Parameters)y = Conv2d(y, "layers.11.0", Parameters, padding:=1)y = Functional.Sigmoid_(y)__Progress = 0.0FReturn Mux(bw, Tensor2Image(y))End Function

4.从上面 Colorize 方法实施中可以看到首先它调用了 Resize 这个方法,将输入图像调整为 256x256 或 保持宽高比的比例缩放图像,所以我们接着添加这个方法如下:

 Public Shared Function Resize(bmp As Bitmap) As BitmapIf bmp.Width > bmp.Height ThenReturn New Bitmap(bmp, CInt(256.0F / bmp.Height * bmp.Width), 256)ElseReturn New Bitmap(bmp, 256, CInt(256.0F / bmp.Width * bmp.Height))End If
End Function

5.接着我们添加 Colorize 中使用的辅助方法 ReLU_ ,由于用到的辅助方法还有很多,为了不干扰查阅 DeOldify 类的上色主要成员代码,我们把这些辅助方法都放到另一个代码文件里,所以我们接着在项目的资源管理器里再添加一个类或模块代码文件,名称为 Functional,这里我们沿用 DeOldlify 的定义层习惯,也使用 Class 代码块。接着在这个类代码块里添加上 ReLU_ 方法的代码,在类代码外部上方同时添加上以后所需的命名空间导入语句:

Imports System.Numerics
Imports System.Runtime.InteropServicesFriend NotInheritable Class Functional
Public Shared Function ReLU_(x As Tensor) As TensorDim vectorSize As Integer = Vector(Of Single).CountDim vectorCount As Integer = x.Numel \ vectorSizeDim zeroVec As New Vector(Of Single)(0.0F)For i As Integer = 0 To vectorCount - 1Dim offset As Integer = i * vectorSizeDim vec As Vector(Of Single) = x.GetVector(offset)x.SetVector(offset, Vector.Max(vec, zeroVec))NextFor i As Integer = vectorCount * vectorSize To x.Numel - 1If x(i) < 0 Then x(i) = 0.0FNextReturn xEnd Function
End Class

至此我们程序项目所需要的代码文件都已经添加齐全了,从下一节开始,我们将把 DeOldlify 和 Functional 两个代码文件里的代码填写完整。