一、项目简介

1.主要内容

          本项目主要是基于Open CV进行植物图像进行分类识别。展示部分采用了网页的形式(Vue+Element+.net Core),由用户上传图片,服务器返回该图片的分类结果。Web服务(.net Core)和c++图像处理模块的交互采用了TCP的形式,即利用.net的TCP客户端和Qt的Tcp服务器端进行交互。最后是Qt的服务器端调用了c++的图像处理类,返回分类识别结果。有一点需要注意,进行图像处理前,先进行图像训练。

2.开发环境

         前端展示:Vue+element

         Web服务:.net Core

         TCP客户端:.net framework

它的版本号,我也没晓得用的哪个版本的,只要支持TcpClient就可以的。

         TCP服务端:Qt

Qt的版本号是5.12.0,记得要安装时要选择MSVC 32和64。

         图像处理模块:Open CV+ msvc

Open CV的版本是2.4.13.6,msvc就用刚刚装的那个

         其他:

前端模块开发面板用的是vue ui(可视化界面),如图2-1所示。项目安装的插件如图2-2所示和依赖如图2-3所示。编辑器是VSCode(最新版的就行)。配置过程不懂得,可以直接看视频去哪了呢?_哔哩哔哩_bilibili前几节内容。

                                               (图2-1 Vue 可视化界面)

                                              (图2-2  已安装的插件)

                                                               (图2-3 已安装的依赖)

Web服务和Tcp客户端是写在一块的,用的编辑器是VSCode,项目用dotnet去创建就好了,详细的可以看相关的创建过程。

配置不懂的可以看C# .NET Core实现快速Web API开发_哔哩哔哩_bilibili前半段视频。

Qt TCP客户端界面和mscv图像处理类用的编译器是VS 2019,配置过程详见https://www.jianshu.com/p/1db7fbe407f8

opencv的环境配置的详见OpenCV:VS2010配置OpenCV2.4.10并显示图像_opencv 2.4.10 打开图片-CSDN博客

3.效果演示

演示包括前端页面(如图3-1)、上传图片(如图3-2)、返回结果(如图3-3)、TCP服务端UI(如图3-4)

                                                                           (图3-1 前端页面)

                                                             (图3-2 上传图片)

                                               (图3-3 服务器返回的结果)

                                                                              (图3-4 TCP服务器端UI展示)

二、项目讲解

1.前端模块

//Vue 文件上传代码
<el-upload
      class="upload-demo"
      drag
      action="http://localhost:5000/api/values"
      :before-upload="beforeAvatarUpload"
      :on-success="successUpload"
      multiple>
      <i class="el-icon-upload"></i>
      <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
      <div class="el-upload__tip" slot="tip">只能上传jpg文件,且不超过500kb</div>
</el-upload>

该段源码为Vue的文件上传代码,其中上传前回调beforeAvatarUpload()方法,成功后的回调使用了successUpload()方法。

beforeAvatarUpload (file) {
      const isJPG = file.type === 'image/jpeg'
      const isLt2M = file.size / 1024 / 1024 / 4 < 1
      if (!isJPG) {
        this.$message.error('上传图片只能是 JPG 格式!')
      }
      if (!isLt2M) {
        this.$message.error('上传图片大小不能超过 2MB!')
      }
      return isJPG && isLt2M
    },
successUpload (response, file, fileList) {
      this.$message({
        message: response.end,
        type: 'success'
      })
}

该段源码包含两个方法,分别是上传前回调,主要是验证图片类型和大小;成功后的回调,显示服务器返回的内容。

2.Web服务源码

public void ConfigureServices(IServiceCollection services)
        {
            services.AddCors(option=>option.AddPolicy("cors", policy => policy.AllowAnyHeader().AllowAnyMethod().AllowCredentials().WithOrigins(new []{"http://localhost:8080"})));
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            app.UseCors("cors");
            app.UseMvc();
        }

上面的源码是配置跨域的源码

public async Task<IActionResult> Post(List<IFormFile> file)
        {
            long size = file.Sum(f => f.Length);
            string fileName = "";
            foreach (var formFile in file)
            {
                if (formFile.Length > 0)
                {
                    fileName = formFile.FileName.Substring(formFile.FileName.LastIndexOf("\\") + 1);
                    var filePath = "F:/publicImages/" + fileName;

                    using (var stream = new FileStream(filePath, FileMode.Create))
                    {
                        await formFile.CopyToAsync(stream);
                    }
                }
            }
            String endStr = Connect("127.0.0.1", fileName);
            return Ok(new { fcount = file.Count, fsize = size, end=endStr});
        }

上面的源码时Web接口的源码,主要功能是接受客户端传输的文件,并保存在本地的F:/publicImages文件夹下,然后调用TCP客户端并返回结果。

static String Connect(String server, String message)
        {
            try
            {
                // Create a TcpClient.
                // Note, for this client to work you need to have a TcpServer
                // connected to the same address as specified by the server, port
                // combination.
                Int32 port = 8001;
                TcpClient client = new TcpClient(server, port);

                // Translate the passed message into ASCII and store it as a Byte array.
                Byte[] data = System.Text.Encoding.UTF8.GetBytes(message);

                // Get a client stream for reading and writing.
                //  Stream stream = client.GetStream();

                NetworkStream stream = client.GetStream();

                // Send the message to the connected TcpServer.
                stream.Write(data, 0, data.Length);
                Console.WriteLine("byte: {0}",ByteArrayToHexString(data));
                Console.WriteLine("Sent: {0}", message);

                // Receive the TcpServer.response.

                // Buffer to store the response bytes.
                data = new Byte[256];

                // String to store the response ASCII representation.
                String responseData = String.Empty;

                // Read the first batch of the TcpServer response bytes.
                Int32 bytes = stream.Read(data, 0, data.Length);
                responseData = System.Text.Encoding.UTF8.GetString(data,0,bytes);
                Console.WriteLine("Received: {0}", responseData);

                // Close everything.
                stream.Close();
                client.Close();
                return responseData;
            }
            catch (ArgumentNullException e)
            {
                Console.WriteLine("ArgumentNullException: {0}", e);
            }
            catch (SocketException e)
            {
                Console.WriteLine("SocketException: {0}", e);
            }
            return "程序错误";
        }

上段源码时TCP客户端源码,主要功能时连接TCP服务端,等待接受数据,然后返回收到的结果。

3.Qt的TCP服务端demo

由于该demo的源码过多,不进行一一展示了,只进行部分展示,

//设置布局
    setLayout(mainLayout);

    //tcp服务端初始化
    server = new QTcpServer;
    serverSocket = new QTcpSocket;
    if (!server->listen(QHostAddress::AnyIPv4, quint16(serverPort)))
    {
        qDebug() << serverPort << "端口被占用!请更换端口";
        return;
    }
    connect(server, SIGNAL(newConnection()), this, SLOT(serverNewconnected()));
    connect(serverSendButton, SIGNAL(clicked(bool)), this, SLOT(serverSendData()));

    //tcp客户端初始化
    clientSocket = new QTcpSocket;
    connect(clientConnectButton, SIGNAL(clicked()), this, SLOT(clientNewConnecting()));
    connect(clientDisConnectionButton, SIGNAL(clicked()), this, SLOT(clientDisConnecting()));
    connect(clientSendButton, SIGNAL(clicked(bool)), this, SLOT(clientSendData()));
    connect(clientSocket, SIGNAL(error(QAbstractSocket::SocketError)),
        this, SLOT(clientReadError(QAbstractSocket::SocketError)));

    //图像处理类初始化
    categorizer = new Categorizer;

上面的源码主要是设置界面的layout,然后进行服务端和客户端的初始化,还有图像处理类的初始化。

4.图像处理类

	//初始化
	Categorizer();
	//特征聚类
	void bulid_vacab();
	//构造BOW
	void compute_bow_image();
	//训练分类器
	void trainSvm();

上面的源码时图像进行训练的过程,详细讲解可以看opencv中的SVM图像分类(一)_c# opencv+svm实现图像分类代码+训练图片-CSDN博客

string Categorizer::category_By_svm_file(string picturePath)
{
    //初始化SVM
    stor_svms = new CvSVM[categories_size];
    for (int i = 0; i < categories_size; i++)
    {
        string svm_filename = string(DATA_FOLDER) + category_name[i] + string("SVM.xml");
        stor_svms[i].load(svm_filename.c_str());
    }
    //获取并初始化字典
    string vocab_filename = string(DATA_FOLDER) + string("vocab.yml");
    FileStorage fs;
    fs.open(vocab_filename, FileStorage::READ);
    fs["vocabulary"] >> vocab;
    bowDescriptorExtractor->setVocabulary(vocab);
    fs.release();

    //初始化图像
    Mat input_pic = imread(picturePath), gray_pic;
    cvtColor(input_pic, gray_pic, CV_BGR2GRAY);
    Mat test_pic = gray_pic;
    // 提取BOW描述子
    vector<KeyPoint>kp;
    Mat test;
    featureDecter->detect(test_pic, kp);
    cout << kp[0].size;
    bowDescriptorExtractor->compute(test_pic, kp, test);
    cout << test.cols;
    float best_score = -999;
    string prediction_category = "";
    for (int j = 0; j < categories_size; j++)
    {
        float scoreValue = stor_svms[j].predict(test, true);
        float classValue = stor_svms[j].predict(test, false);
        float curConfjdence;
        int sjgn;
        sjgn = (scoreValue < 0.0f) == (classValue < 0.0f) ? 1 : -1;
        curConfjdence = sjgn * stor_svms[j].predict(test, true);
        cout << "测试图:" << picturePath << "在" << category_name[j] << "的成绩为:" << curConfjdence << "\n";
        if (curConfjdence > best_score)
        {
            best_score = curConfjdence;
            prediction_category = category_name[j];
        }
    }
    cout << "测试图:" << picturePath << "最相似的是:" << prediction_category << "\n";
    Mat mat = imread(string(DATA_FOLDER) + prediction_category + "/1.jpg");
//    imshow(picturePath + "系统匹配图", mat);
    return prediction_category;
}

上面的源码是对测试图像进行测试的函数。首先是载入之前训练好的SVM数据并初始化、然后载入之前训练好的字典数据并初始化,然后提取该图片的BOW描述子,最后对每一类图像进行predive,并获取分最高的那个作为结果返回。

最后,所有源码和训练图像、测试图像、训练数据可见https://gitee.com/clove682/Plant_image_classification_and_recognition_project_based_on_open_CV.git

Logo

前往低代码交流专区

更多推荐