Logo 识别系统






4.82/5 (10投票s)
使用 C# .NET 6.0 Windows Form 编写的 Logo 识别系统程序(Tensorflow.net, Tensorflow.keras, Emgu Cv, ScottPlot.WinForms, Newtonsoft.Json)
引言
在本文中,我将介绍我的程序(Logo Recognition System),并通过分步解释代码来向您展示程序的机制。
注意:为了能够理解本文,您应该对深度学习的基础知识有 general 的了解,例如人工神经网络的组成部分,如层或激活函数。您还应该具备高级 C# 知识。
在我们开始之前,我们需要安装所需的库。以下库是使我们的程序正常工作的必需品:
Tensorflow.net
和Tensorflow.Keras
使用 Paket-Manager-Console,输入以下命令:
NuGet\Install-Package TensorFlow.NET -Version 0.100.2
NuGet\Install-Package TensorFlow.Keras -Version 0.10.2
NuGet\Install-Package SciSharp.TensorFlow.Redist -Version 2.10.0
NuGet\Install-Package SciSharp.TensorFlow.Redist-Windows-GPU -Version 2.10.0
- Emgu cv
使用 Paket-Manager-Console,输入以下命令:
NuGet\Install-Package EmguCV -Version 3.1.0.1
NuGet\Install-Package Emgu.CV.runtime.windows -Version 4.6.0.5131
NuGet\Install-Package Emgu.CV.Bitmap -Version 4.6.0.5131
ScottPlot.WinForms
使用 Paket-Manager-Console,输入以下命令:
NuGet\Install-Package ScottPlot.WinForms -Version 4.1.60
- Newtonsoft.Json
使用 Paket-Manager-Console,输入以下命令:
NuGet\Install-Package Newtonsoft.Json -Version 13.0.2
注意:安装完所需的库后,我们还需要将目标生成平台从“任何 CPU”设置为“64 位”,如下所示:
Project->Properties->Build->General->Platform Target-> x64
第一部分:创建我们自己的卷积神经网络模型
由于卷积层专门用于图像识别,而我们主要想对图像进行分类,因此我们将在网络中使用卷积层。为了加速学习过程,我们还将使用“最大池化”层。最后但同样重要的是,为了避免过拟合并防止神经网络过度依赖特定对象的属性,我们将在网络中使用“dropout”层。作为 CNN 的最后一步,我们将使用“Dense”层,它是一个通用的神经网络分类器。在此层中,每个节点都连接到前一层中的每个节点。作为激活函数,我们将在隐藏层中使用 Relu
函数来加速训练,并在输出层中使用 softmax 函数,因为我们将有多个输出。
分步实现网络结构
- 网络输入为 32 x 32 图像,这意味着输入尺寸应为 32 x 32,具有 3 个 RGB 通道。
- 2 个卷积层,32 个滤波器,滤波器尺寸为 3x3。
注意 1:为避免减小层输出尺寸,可以将填充属性设置为“same”。 - 最大池化层,尺寸为 2x2。
- dropout 层,速率为 0.25。
注意 2:作为参数,我们可以给此层一个介于 0 和 1 之间的值,例如 0.25 表示当网络训练时,网络中某个层中预定数量(25%)的神经元将被关闭(“dropout”),并且在下一步计算中不再被处理。 - 我们重复第 2、3、4 步两次,每次都将卷积层中的滤波器数量加倍。
- 256 个神经元的 Dense 层。
- 128 个神经元的 Dense 层。
- 作为输出,我们还将使用具有 softmax 作为激活函数的 Dense 层。
public class CNN
{
ILayer Conv2D_1_;
ILayer Conv2D_2_;
ILayer MaxPooling2D_1_;
ILayer Dropout_1_;
ILayer Conv2D_3_;
ILayer Conv2D_4_;
ILayer MaxPooling2D_2_;
ILayer Dropout_2_;
ILayer Conv2D_5_;
ILayer Conv2D_6_;
ILayer MaxPooling2D_3_;
ILayer Dropout_3_;
ILayer Flatten;
ILayer Dense_1_;
ILayer Dense_2_;
ILayer Dense_3_;
int outputCount;
public CNN(int outputCount) {
var layers = new LayersApi();
Conv2D_1_ = layers.Conv2D(32, (3, 3), activation: "relu", padding: "same");
Conv2D_2_ = layers.Conv2D(32, (3, 3), activation: "relu", padding: "same");
Conv2D_3_ = layers.Conv2D(64, (3, 3), activation: "relu", padding: "same");
Conv2D_4_ = layers.Conv2D(64, (3, 3), activation: "relu", padding: "same");
Conv2D_5_ = layers.Conv2D(128, (3, 3), activation: "relu", padding: "same");
Conv2D_6_ = layers.Conv2D(128, (3, 3), activation: "relu", padding: "same");
MaxPooling2D_1_ = layers.MaxPooling2D(pool_size: (2, 2));
MaxPooling2D_2_ = layers.MaxPooling2D(pool_size: (2, 2));
MaxPooling2D_3_ = layers.MaxPooling2D(pool_size: (2, 2));
Dropout_1_ = layers.Dropout(0.25f);
Dropout_2_ = layers.Dropout(0.25f);
Dropout_3_ = layers.Dropout(0.25f);
Flatten = layers.Flatten();
Dense_1_ = layers.Dense(256, activation: "relu");
Dense_2_ = layers.Dense(128, activation: "relu");
Dense_3_ = layers.Dense(outputCount, activation: "softmax");
}
public Model Build(string name)
{
var inputs = keras.Input(shape: (32, 32, 3), name: "img_"+name);
var x = Conv2D_1_.Apply(inputs);
x = Conv2D_2_.Apply(x);
x = MaxPooling2D_1_.Apply(x);
x = Dropout_1_.Apply(x);
x = Conv2D_3_.Apply(x);
x = Conv2D_4_.Apply(x);
x = MaxPooling2D_2_.Apply(x);
x = Dropout_2_.Apply(x);
x = Conv2D_5_.Apply(x);
x = Conv2D_6_.Apply(x);
x = MaxPooling2D_3_.Apply(x);
x = Dropout_3_.Apply(x);
x = Flatten.Apply(x);
x = Dense_1_.Apply(x);
x = Dense_2_.Apply(x);
return keras.Model(inputs, Dense_3_.Apply(x), name: name);
}
}
第二部分:程序
要完全理解如何使用该程序,我们需要 general 地了解它的工作原理。该程序包含三个部分:第一部分是我们准备自己的数据集;第二部分是训练过程,我们在此训练自己的模型;最后一部分是我们用真实的物体测试我们的模型。
-
准备我们自己的数据集
为此,我开发了一个截图工具,通过它可以执行两项操作:
-
第一项是手动选择一个对象,在此过程中,提取的对象将被导出为一个新输出,并且会在“Ideal Set”组合框中添加一个新的标签。或者,提取的对象可以设置为“Ideal Set”组合框中的现有标签,具体步骤如下(按顺序):
// Doses the Selected Object Already exists in the Data Set DialogResult res = CustomMsgbox.InputBox.ShowDialog ("represent the Image a new Dataset ?", "Question", CustomMsgbox.InputBox.Icon.Question, CustomMsgbox.InputBox.Buttons.YesNo); // if the Selected Object New or doesn´t exists in the Data Set, // we have to enter the new Ideal set Value if (res == System.Windows.Forms.DialogResult.Yes) { res = CustomMsgbox.InputBox.ShowDialog ("Please Enter the Ideal set value !!", "Ideal set", CustomMsgbox.InputBox.Icon.Question, CustomMsgbox.InputBox.Buttons.OkCancel, CustomMsgbox.InputBox.Type.TextBox, new string[] { "Ideal set new Value" }); if (res == System.Windows.Forms.DialogResult.OK) { Bitmap image = ImageHelper.CutImage(rect, new Bitmap (this.docu_pb.Image)); // Filter Selected Object Colors on the basis of Program Settings switch (Central_Static_Value.Settings.Filter) { case "Colored": image = ImageHelper.BGRFiler(image); break; case "Gray": image = ImageHelper.GrayFilter(image); break; case "BlackWeight": image = ImageHelper.BlackWhiteFilter(image); break; default: image = ImageHelper.BGRFiler(image); break; } // get Ideal Set Value from the Message Box string InputBox_ResultValue = CustomMsgbox.InputBox.ResultValue; // save the Ideal set Value as new Output int match = this.AssignIdentity(InputBox_ResultValue); //give the Image a Name to show in to Train Images dataGridView, //Note that this Name does not play any role //in the Actual training or Predict string imageName = "image" + Central_Static_Value.Train_Model. to_Train_Images_dataGridView.Rows.Count.ToString(); // add the Image in the to Train Images dataGridView Central_Static_Value.Train_Model. to_Train_Images_dataGridView.Rows.Add (imageName, InputBox_ResultValue, ImageHelper.ResizeImage(32, 32, image)); // Create a New Dataset Instance TrainingSet trainingSet = new TrainingSet(imageName, match, ImageHelper.ResizeImage(32, 32, image), true); // add the Dataset in the TrainingSetList Central_Static_Value.Train_Model.TrainingSetList.Add(trainingSet); // refresh the Ideal set Combobox refrech_ideal_set_comboBox(); } } // if the Selected Object already exists in the Data Set, // we have to choose one of the Available Ideal Set Values else { res = CustomMsgbox.InputBox.ShowDialog ("Please choose the Ideal set value !!", "Ideal set", CustomMsgbox.InputBox.Icon.Question, CustomMsgbox.InputBox.Buttons.OkCancel, CustomMsgbox.InputBox.Type.ComboBox, Central_Static_Value.Train_Model.neuron2identity.Select (x => x.Value).ToArray()); if (res == DialogResult.OK) { // get Ideal Set Value from the Message Box string result = CustomMsgbox.InputBox.ResultValue; //Cut the selected Area of the given Image Bitmap image = ImageHelper.CutImage (rect, new Bitmap(this.docu_pb.Image)); //give the Image a Name to show in to Train Images dataGridView, //Note that this Name does not play any role in //the Actual training or Predict string imageName = "image" + Central_Static_Value.Train_Model. to_Train_Images_dataGridView.Rows.Count.ToString(); // save the Ideal set Value as new Output int match = AssignIdentity(result); // Filter Selected Object Colors on the basis of Program Settings switch (Central_Static_Value.Settings.Filter) { case "Colored": image = ImageHelper.BGRFiler(image); break; case "Gray": image = ImageHelper.GrayFilter(image); break; case "BlackWeight": image = ImageHelper.BlackWhiteFilter(image); break; default: image = ImageHelper.BGRFiler(image); break; } // add the Image in the to Train Images dataGridView Central_Static_Value.Train_Model.to_Train_Images_ dataGridView.Rows.Add (imageName, result, ImageHelper.ResizeImage (32, 32, image)); // Create a New Dataset Instance TrainingSet trainingSet = new TrainingSet(imageName, match, ImageHelper.ResizeImage(32, 32, image), false); // add the Dataset in the TrainingSetList Central_Static_Value.Train_Model.TrainingSetList.Add(trainingSet); } }
-
在使用鼠标选择新对象后,程序会询问我们选择的对象是否已存在于数据集中。
-
如果所选对象是新的或不存在于数据集中,我们必须输入新的“Ideal Set”值。否则,我们必须选择一个可用的“Ideal Set”值。
- 输入“Ideal Set”值后,我们需要将其保存到一个
Dictionary
中,其中Key
代表等效的当前输出计数,Value
代表实际的“Ideal Set”值。否则,我们需要通过加一来刷新输出计数变量。当然,如果“Ideal Set”是新的,则刷新TrainingsetList
,添加新的Dataset
和代码。 -
第二项是自动选择对象,这将使用选择性搜索算法完成。在此过程中,提取的对象可以简单地设置为“Snip Image Form”中的“Ideal Set”组合框中的现有标签。因此,在获得其中一个对象后,我们使用选择性搜索自动选择的对象,从
ideal_set_cb
获取“Ideal Set”值,并将其保存为新的输出。代码
//get the Image Name ,to show it into Train Images dataGridView, //Note that this Name does not play any role //in the Actual training or Predict string imageName = Image_name_tb.Text; // get Ideal Set Value from the ideal_set_cb string selecteditem = ideal_set_cb.SelectedItem.ToString(); // save the Ideal set Value as new Output int match = AssignIdentity(selecteditem); // add the Image in the to Train Images dataGridView Central_Static_Value.Train_Model.to_Train_Images_dataGridView.Rows.Add (imageName, selecteditem, ImageHelper.ResizeImage (32, 32, new Bitmap(sniped_img_pictureBox.Image))); // Create a New Dataset Instance TrainingSet trainingSet = new TrainingSet (imageName, match, ImageHelper.ResizeImage (32, 32, new Bitmap(sniped_img_pictureBox.Image)), false); // add the Dataset in the TrainingSetList Central_Static_Value.Train_Model.TrainingSetList.Add(trainingSet);
-
-
-
第二部分“训练过程”
准备好
Dataset
后,就可以开始训练过程,如下一步骤:// Convert Trainigset Images List into Int 4 Dimensional ND Array NDArray x_train = ImageHelper.ImagesToNDArray(this.TrainingSetList, 32, 32); // Convert Trainigset Idealsets or Matches in Int 2 Dimensional ND Array NDArray y_train = ImageHelper.IdelaValuesToNDArray (this.TrainingSetList, this.outputCount); //Convert the Input Matrix Type Into float x_train = x_train.astype(np.float32); x_train /= 255; Model = new CNN(outputCount).Build("LogoDetector"); var opt = new Tensorflow.Keras.Optimizers.Adam(learning_rate: 0.001f); // Model.summary(); Model.compile(optimizer: opt, loss: keras.losses.CategoricalCrossentropy(), metrics: new[] { "acc" }); for (int i = 0; i < (int)Epoches_numericUpDown.Value; i++) { Model.fit(x_train, y_train); }
-
我们已经知道我们的网络接受 (32x32) 作为输入尺寸。因此,首先,在准备数据集过程中,我们必须将图像调整为 (32x32)。然后,在训练过程中,我们必须将图像转换为
Int ND Array
。当转换图像时,我们得到一个 1x32x32x3 的数组。第一个 1 代表图像索引,最后的 3 表示每个像素有 3 种颜色。现在,假设我们有不止一张图像需要训练,那么我们需要一个像 (Variable-Images-Count x 32 x 32 x 3) 这样的ND Array
,以及代码。/// <summary> /// Convert List of Images into four dimensional ND Array where the /// first dímension represent the Image Index and the Second the /// Image width and the third the Imgae Height and the Last /// the Image Colors /// </summary> /// <param name="bitmaps">to be Converted Image List</param> /// <param name="width">Images Width</param> /// <param name="height">Images Height</param> /// <returns>4 Dimensional ND Array</returns> public static NDArray ImagesToNDArray(List<TrainingSet> bitmaps, int width, int height) { int[,,,] imagespixels = new int[bitmaps.Count, width, height, 3]; for (int bi = 0; bi < bitmaps.Count; bi++) { Bitmap bitmap = bitmaps[bi].Image; int[,,] pixels = getPixels(bitmap); for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { for (int z = 0; z < 3; z++) { imagespixels[bi, x, y, z] = pixels[x, y, z]; } } } } return np.array(imagespixels); }
-
将图像转换为
ND Array
后,我们还需要将我们的“Ideal Dataset Values”或“Matches Values”转换为2d Nd Array
,其中第一个维度代表等效图像索引,第二个维度代表匹配值或理想输出,以及代码。/// <summary> ///the Idealset or the right Predict represented in 1 ///for identical and 0 for not identical ///will here converted into two dimensional Array, ///where the first dimension represent the Image Index ///and the second the output or the Idealset ///</summary> /// <param name="images">the Ideal Sets of the Images List</param> /// <param name="outputCount">Count of the CNN Outputs</param> /// <returns>2 Dimensional ND Array</returns> public static NDArray IdelaValuesToNDArray (List<TrainingSet> images,int outputCount) { int[,] idealvalues = new int[images.Count, outputCount]; for (int i = 0; i < images.Count; i++) { for (int j = 0; j < outputCount; j++) { if (j == images[i].Match) idealvalues[i, j] = 1; else idealvalues[i, j] = 0; } } return np.array(idealvalues); }
-
为了提高我们的训练结果,我们将我们的数据集或 4D 维
ND Array
除以255
,结果将是一个值在 0 到 1 之间的矩阵。 -
为了加速我们的学习过程并节省内存,我们将使用 adam 优化器。
-
之后,就可以开始学习过程了,以及最终的代码。
-
-
第三部分,“测试过程”
-
该程序首先需要一个选择性搜索算法。该算法将为我们选择图像中的所有对象,并为下一步做准备,即预测这是什么对象。
-
预测过程:在此步骤中,程序将告诉我们图像中的对象是什么。为此,程序会将图像转换为三维 ND Array,第一个维度代表宽度,第二个维度代表高度,最后一个维度代表 RGB 或颜色。之后,我们将把该数组交给我们的 CNN 进行处理,最后就知道它是什么对象了。
-
注意
如果我们尝试预测的数据集范围之外的图像,则有可能生成错误的预测。在此,我想指出,模型必须在通过选择性搜索算法设置的所有图像上进行训练,因为只有这样,程序才能返回正确的预测。
在下面的视频中,我将向您展示如何使用该程序。
历史
- 2023 年 2 月 19 日:初始版本
- 2023 年 3 月 28 日:文章更新