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

Logo 识别系统

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.82/5 (10投票s)

2023年2月19日

CPOL

7分钟阅读

viewsIcon

30625

downloadIcon

584

使用 C# .NET 6.0 Windows Form 编写的 Logo 识别系统程序(Tensorflow.net, Tensorflow.keras, Emgu Cv, ScottPlot.WinForms, Newtonsoft.Json)

引言

在本文中,我将介绍我的程序(Logo Recognition System),并通过分步解释代码来向您展示程序的机制。

注意:为了能够理解本文,您应该对深度学习的基础知识有 general 的了解,例如人工神经网络的组成部分,如层或激活函数。您还应该具备高级 C# 知识。

在我们开始之前,我们需要安装所需的库。以下库是使我们的程序正常工作的必需品:

  • Tensorflow.netTensorflow.Keras

    使用 Paket-Manager-Console,输入以下命令:

    1. NuGet\Install-Package TensorFlow.NET -Version 0.100.2
    2. NuGet\Install-Package TensorFlow.Keras -Version 0.10.2
    3. NuGet\Install-Package SciSharp.TensorFlow.Redist -Version 2.10.0
    4. NuGet\Install-Package SciSharp.TensorFlow.Redist-Windows-GPU -Version 2.10.0
  • Emgu cv

    使用 Paket-Manager-Console,输入以下命令:

    1. NuGet\Install-Package EmguCV -Version 3.1.0.1
    2. NuGet\Install-Package Emgu.CV.runtime.windows -Version 4.6.0.5131
    3. NuGet\Install-Package Emgu.CV.Bitmap -Version 4.6.0.5131
  • ScottPlot.WinForms

    使用 Paket-Manager-Console,输入以下命令:

    1. NuGet\Install-Package ScottPlot.WinForms -Version 4.1.60
  • Newtonsoft.Json

    使用 Paket-Manager-Console,输入以下命令:

    1. NuGet\Install-Package Newtonsoft.Json -Version 13.0.2

注意:安装完所需的库后,我们还需要将目标生成平台从“任何 CPU”设置为“64 位”,如下所示:

Project->Properties->Build->General->Platform Target-> x64

第一部分:创建我们自己的卷积神经网络模型

由于卷积层专门用于图像识别,而我们主要想对图像进行分类,因此我们将在网络中使用卷积层。为了加速学习过程,我们还将使用“最大池化”层。最后但同样重要的是,为了避免过拟合并防止神经网络过度依赖特定对象的属性,我们将在网络中使用“dropout”层。作为 CNN 的最后一步,我们将使用“Dense”层,它是一个通用的神经网络分类器。在此层中,每个节点都连接到前一层中的每个节点。作为激活函数,我们将在隐藏层中使用 Relu 函数来加速训练,并在输出层中使用 softmax 函数,因为我们将有多个输出。

分步实现网络结构

  1. 网络输入为 32 x 32 图像,这意味着输入尺寸应为 32 x 32,具有 3 个 RGB 通道。
  2. 2 个卷积层,32 个滤波器,滤波器尺寸为 3x3。
    注意 1:为避免减小层输出尺寸,可以将填充属性设置为“same”。
  3. 最大池化层,尺寸为 2x2。
  4. dropout 层,速率为 0.25。
    注意 2:作为参数,我们可以给此层一个介于 0 和 1 之间的值,例如 0.25 表示当网络训练时,网络中某个层中预定数量(25%)的神经元将被关闭(“dropout”),并且在下一步计算中不再被处理。
  5. 我们重复第 2、3、4 步两次,每次都将卷积层中的滤波器数量加倍。
  6. 256 个神经元的 Dense 层。
  7. 128 个神经元的 Dense 层。
  8. 作为输出,我们还将使用具有 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 地了解它的工作原理。该程序包含三个部分:第一部分是我们准备自己的数据集;第二部分是训练过程,我们在此训练自己的模型;最后一部分是我们用真实的物体测试我们的模型。

  1. 准备我们自己的数据集

    为此,我开发了一个截图工具,通过它可以执行两项操作:

    • 第一项是手动选择一个对象,在此过程中,提取的对象将被导出为一个新输出,并且会在“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);
          }
      }
      1. 在使用鼠标选择新对象后,程序会询问我们选择的对象是否已存在于数据集中。

      2. 如果所选对象是新的或不存在于数据集中,我们必须输入新的“Ideal Set”值。否则,我们必须选择一个可用的“Ideal Set”值。

      3. 输入“Ideal Set”值后,我们需要将其保存到一个 Dictionary 中,其中 Key 代表等效的当前输出计数,Value 代表实际的“Ideal Set”值。否则,我们需要通过加一来刷新输出计数变量。当然,如果“Ideal Set”是新的,则刷新 TrainingsetList,添加新的 Dataset 和代码。
      4. 第二项是自动选择对象,这将使用选择性搜索算法完成。在此过程中,提取的对象可以简单地设置为“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);
  2. 第二部分“训练过程”

    准备好 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);
     }
    1. 我们已经知道我们的网络接受 (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);
      }
    2. 将图像转换为 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);
      }
    3. 为了提高我们的训练结果,我们将我们的数据集或 4D 维 ND Array 除以 255,结果将是一个值在 0 到 1 之间的矩阵。

    4. 为了加速我们的学习过程并节省内存,我们将使用 adam 优化器。

    5. 之后,就可以开始学习过程了,以及最终的代码。

  3. 第三部分,“测试过程”
    • 该程序首先需要一个选择性搜索算法。该算法将为我们选择图像中的所有对象,并为下一步做准备,即预测这是什么对象。

    • 预测过程:在此步骤中,程序将告诉我们图像中的对象是什么。为此,程序会将图像转换为三维 ND Array,第一个维度代表宽度,第二个维度代表高度,最后一个维度代表 RGB 或颜色。之后,我们将把该数组交给我们的 CNN 进行处理,最后就知道它是什么对象了。

注意

如果我们尝试预测的数据集范围之外的图像,则有可能生成错误的预测。在此,我想指出,模型必须在通过选择性搜索算法设置的所有图像上进行训练,因为只有这样,程序才能返回正确的预测。

在下面的视频中,我将向您展示如何使用该程序。

历史

  • 2023 年 2 月 19 日:初始版本
  • 2023 年 3 月 28 日:文章更新
© . All rights reserved.