一、项目简介
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(最新版的就行)。配置过程不懂得,可以直接看https://www.bilibili.com/video/BV1EE411B7SU?p=18前几节内容。
(图2-1 Vue 可视化界面)
(图2-2 已安装的插件)
(图2-3 已安装的依赖)
Web服务和Tcp客户端是写在一块的,用的编辑器是VSCode,项目用dotnet去创建就好了,详细的可以看相关的创建过程。
配置不懂的可以看https://www.bilibili.com/video/BV11E411n74a?from=search&seid=16803251335123231391前半段视频。
Qt TCP客户端界面和mscv图像处理类用的编译器是VS 2019,配置过程详见https://www.jianshu.com/p/1db7fbe407f8
opencv的环境配置的详见https://blog.csdn.net/xinjiang666/article/details/80785210
3.效果演示
演示包括前端页面(如图3-1)、上传图片(如图3-2)、返回结果(如图3-3)、TCP服务端UI(如图3-4)
(图3-1 前端页面)
(图3-2 上传图片)
(图3-3 服务器返回的结果)
(图3-4 TCP服务器端UI展示)
二、项目讲解
1.前端模块
//Vue 文件上传代码
<el-uploadclass="upload-demo"dragaction="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 < 1if (!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();//构造BOWvoid compute_bow_image();//训练分类器void trainSvm();
上面的源码时图像进行训练的过程,详细讲解可以看https://blog.csdn.net/yincheng_917/article/details/82684337
string Categorizer::category_By_svm_file(string picturePath)
{//初始化SVMstor_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,并获取分最高的那个作为结果返回。