本项目主要是基于Open CV进行植物图像进行分类识别。展示部分采用了网页的形式(Vue+Element+.net Core),由用户上传图片,服务器返回该图片的分类结果。Web服务(.net Core)和c++图像处理模块的交互采用了TCP的形式,即利用.net的TCP客户端和Qt的Tcp服务器端进行交互。最后是Qt的服务器端调用了c++的图像处理类,返回分类识别结果。有一点需要注意,进行图像处理前,先进行图像训练。
前端展示: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-1)、上传图片(如图3-2)、返回结果(如图3-3)、TCP服务端UI(如图3-4)
(图3-1 前端页面)
(图3-2 上传图片)
(图3-3 服务器返回的结果)
(图3-4 TCP服务器端UI展示)
//Vue 文件上传代码
将文件拖到此处,或点击上传
只能上传jpg文件,且不超过500kb
该段源码为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'
})
}
该段源码包含两个方法,分别是上传前回调,主要是验证图片类型和大小;成功后的回调,显示服务器返回的内容。
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 Post(List 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服务端,等待接受数据,然后返回收到的结果。
由于该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,然后进行服务端和客户端的初始化,还有图像处理类的初始化。
//初始化
Categorizer();
//特征聚类
void bulid_vacab();
//构造BOW
void compute_bow_image();
//训练分类器
void trainSvm();
上面的源码时图像进行训练的过程,详细讲解可以看https://blog.csdn.net/yincheng_917/article/details/82684337
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描述子
vectorkp;
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,并获取分最高的那个作为结果返回。