综合设计——多源异构数据采集与融合应用综合实践
这个项目属于哪个课程 | 2024数据采集与融合技术实践 |
---|---|
组名 | 从你的全世界爬过 团队logo: |
项目简介 | 项目名称:博物识植 项目logo: 项目介绍:在探索自然奥秘的旅途中,我们常与动植物相伴而行,却无法准确识别它们,更难以深入了解他们的特征。为了更好地理解和欣赏自然界的多样性,提升我们对动植物的认识和保护意识,我们需要一个智能系统。该系统能够根据用户拍摄的动植物照片,智能识别并匹配相应的信息,同时为用户提供丰富的学习资源,帮助人们更深入地了解和学习动植物知识。通过这样的方式,我们不仅能够更准确地识别和欣赏周围的生命,还能够在日常生活中,随时随地增长见识,体验探索自然的乐趣。 项目背景:人类的生活离不开动植物的支持,动植物的多样性是一切地球生物的依赖。在生活中随处可见很多动植物,动植物是人类生活必不可少的一部分。 保护大自然保护动植物就是在保护人类自己。在保护动植物的过程中,首先要解决的是动植物识别的问题。 项目意义:提供了一种我们与自然界互动的方式。其应用场景广泛,渗透到了教育、旅游等多个领域。在学校,它可以是生物课程的辅助工具,通过实践学习生物多样性;在旅游行业,它可以帮助游客更好地了解他们所参观的自然景观,提升旅行体验 |
团队成员学号 | 042201401陈高菲、102202107王勤琛、102202108王露洁、102202115孙佳会、102202123张铭心、102202130林烨、102202138徐婉瑜、102202140郭心怡 |
项目目标 | 本系统旨在实现以下功能: a.图片识别功能:用户上传动植物图片,系统通过图像识别技术自动识别物种,返回准确的物种名称。 b.物种详细信息:识别后,用户将获取该物种的详细信息,包括外形特点、生长环境、分布区域等相关数据。 c.物种图片展示:系统将提供该物种的高质量图片,帮助用户更直观地了解物种特征。 d.名称搜索功能:用户可以手动输入动植物的名称,系统将返回该物种的相关信息,方便快速查询。 e.网站部署上线:通过华为云的弹性计算服务部署网站,确保系统高可用和稳定运行,实现网站上线。 |
其他参考文献 | 1.yanjingang/pigimgclassification: 图像分类 2.基于改进SE-MnasNet骨干网络YOLOv5的动植物树木识别系统_开源 树木识别 |
gitee链接 | 2024学年数据采集与融合技术大作业——博物识植 团队:从你的全世界爬过 |
一、系统总体技术概述
1.1 系统架构概述
系统分为前端、后端、数据库、AI接口、爬虫模块、部署等多个层级。前后端之间通过RESTful API进行通信。具体分为以下几个部分:
-
前端:使用HTML、CSS和JavaScript进行界面设计,实现用户与系统的交互。用户可以上传文本、图片等文件。
-
后端:使用Python语言和Flask框架实现,处理图像识别、查询请求、调用AI接口和爬虫数据存储等业务逻辑。
-
数据库:存储动植物物种的详细信息,包括图像、分布、特点等。存储物种识别的历史记录信息。
-
图像识别与AI接口:利用图像识别模型或调用第三方AI服务(如百度AI、Google Vision等)识别图片并返回结果。
-
爬虫:提前爬取动植物相关网站数据,补充物种数据库。使用Selenium框架进行实时图片爬取。
-
部署平台:使用华为云平台部署系统,保证系统的高可用和稳定性。
1.2 各模块技术实现
1.2.1 图像识别模块
-
目标:用户上传图片,系统通过图像识别技术返回物种名称。
-
技术方案:
使用深度学习模型:基于改进SE-MnasNet骨干网络YOLOv5和卷积神经网络cnn opencv进行图像分类和识别。
基于识别精确度的考虑调用第三方云服务百度智能云的动植物识别API提供快速而准确的图像识别。
-
流程:
用户上传图片,前端将图片通过API发送至后端。
后端调用模型或AI图像识别API分析图片,获取可能的物种标签。
后端将物种名称返回给前端,前端展示识别结果。
1.2.2 物种信息查询功能
-
目标:根据识别后的物种名称或用户输入的名称,返回该物种的详细信息。
-
技术方案:
利用selenium技术和scrapy框架爬取信息网站所有物种信息(如外形特点生长环境、分布区域等)存储在csv表导入数据库并定期更新。
利用查询语句在数据库中进行查找并返回详细信息。
若数据库中没有相关信息,则调用百度智能云的千帆大模型识别物种名称,查询物种相关信息。
-
流程:
后端识别出物种名称时,系统首先查询数据库,若没有该物种的信息,再调用AI接口获取。
1.2.3 相似图片展示
-
目标:
根据用户上传的图片,返回物种的相似图片,帮助用户直观了解物种。
-
技术方案:
运用selenium爬虫技术实时爬取百度识图返回的相似图片
-
流程:
后端接收前端传入的图片后,将图片作为输入文件传入百度识图网站实时爬取相似图片,在系统返回物种详细信息时,将图片URL一并返回。
1.2.4 保存历史记录
-
目标:
将用户的历史搜索记录保存至数据库,方便用户在“我的图鉴”页面查看并跳转至物种详情页,随时查看过去的搜索记录。
-
技术方案:
创建一个数据库表专门用来保存用户的搜索记录,包括用户上传的图片、识别出来的物种名称、物种的详细信息(如描述、分布、图片URL等)
-
流程:
当用户获取识别结果时,后端系统会将物种信息保存至数据库中。在点击我的图鉴中的物种名称时,后端调取数据库信息展示在前端界面。
1.2.5 部署与部署架构
-
目标:将整个系统部署到华为云服务器上,让非本地用户可以访问。
-
技术方案:
使用华为云ECS(Elastic Cloud Server)部署后端服务。
使用华为云OBS 存储图片等静态资源。
使用RDS(Relational Database Service)存储物种信息数据库。
前端可以使用 Nginx 进行负载均衡和反向代理
-
流程:
前后端文件上传部署完成后即可实现非本地用户的访问。
1.3 源码运行步骤
-
gitee仓库下载源码
-
启动文件中的ai.py与database.py文件
-
运行index.html文件
(!注意在本地主机运行代码时请更换代码中的路径名)
二、个人分工部分阐述
2.1 我的工作概述
在项目开始之前,我们大家一起开会讨论选题,确定选题后,就开始进行调研,想看看市面上现存的类似的软件或网站有什么优势和欠缺的地方可以给我们的项目提供参考。
我这次在编程上的工作是和另外两位同学一起进行前端界面的设计,使用HTML、CSS和JavaScript进行界面设计,实现用户与系统的交互,具体就是关于 动物搜索界面 和 详情界面 开发和设计。
在进行前端界面设计时,我跟另外两位伙伴分别负责不同界面的设计,虽然都是按照各自的想法的进行设计的,但是最后整合的时候却发现一点违和感都没有,真的有点意外和惊讶!各有各的特点,但都是符合主题且赏心悦目的,下面可以简单展示一下:
拍拍园地:
植物百科:
动物绘本:
我的图鉴:
植物图鉴:
动物图鉴:
植物百科详情:
动物百科详情:
有了软件工程实践和数据库系统实践的经验之后,我发现前端考验的是审美和技术两方面的能力。
使用HTML和CSS进行静态页面和样式的设计,这是考验审美;使用JavaScript进行与用户和后端的交互,这是考验技术。
这次进行前端设计的伙伴们审美都是在线的,所以后面我就主要介绍js部分的具体实现。
除此之外,我还负责录制最后展示项目的视频,录制视频本身没有什么难度,但是我却觉得挺有意思的,因为我可以通过介绍我们这个项目而产生(一丢丢)成就感和自豪感。
以下是我个人负责的部分的动图展示:
搜索主页:
图片搜索获取详情:
文本搜索获取详情:
2.1.1 搜索界面---实现页面动态内容管理
data: 包含了一个动物对象数组,描述每种动物的 name、description 和 image;
computed: rows(): 通过计算属性,将动物数据按每行 3 个进行分组,便于在页面上均匀显示;
使用 Vue.js 的 el 属性绑定到 HTML DOM(class="animal-page"),使页面与数据实时同步;
通过动态绑定数据 (v-for) 实现动物卡片的展示;
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14"></script>
<script>
new Vue({
el: ".animal-page",
data: {
animals: [
{ name: "羊驼", description: "羊驼的颈部较长,蹄子是肉质的,胃里也有水囊,可以数日不饮水,走路的姿态也与骆驼类同。但其身体较小,背上无肉峰,四肢很细,脚的前端有弯曲而尖锐的蹄;脸细长,耳尖长,眼睛很大,非常清秀;尾巴短,毛细长。", image: "img/alpaca.jpg" },
{ name: "吸蜜蜂鸟", description: "双足短小;喙像一根细针,舌头像一根纤细的线,眼睛像两个闪光的黑点;翅上的羽毛轻薄得好像是透明的一样。主要以采集花蜜为生,偶尔也会捕食躲藏在花中心上的小昆虫。", image: "img/bird.jpg" },
{ name: "耳廓狐", description: "耳廓呈三角形,耳内侧为纯白色,外侧为浅黄色。眼大,眼前部有一块清晰的褐色斑纹。体毛近乎白色,略带浅黄色,毛长而蓬松、柔软,背中线为肉桂褐色,腹部及四肢内侧为白色。", image: "img/fox.jpg" },
{ name: "原麝", description: "原麝的体色主要为深棕色,背部、腹部及臀部有肉桂色的斑点,这些斑点排列成4~6纵行,其中腰臀两侧的斑点最为明显且密集。它们的嘴、面颊为棕灰色,额部毛色较深,耳背和耳尖为棕灰色,耳壳内为白色,下颌也为白色。", image: "img/deer.jpg" },
{ name: "南美洲栗鼠", description: "体形中小而肥胖,头部似兔,尾巴似松鼠,耳朵大而薄,呈钝圆形,眼睛大而亮。前肢短小,有5趾;后肢强壮,有4趾,善于跳跃。性格温顺,活泼好动,喜欢蹦蹦跳跳,好奇心强。", image: "img/mouse.jpg" },
{ name: "海獭", description: "头部较短而宽阔,耳壳小,口鼻部较为短钝,吻端裸出,上唇与脸颊相当发达,覆有浓密的硬须。其躯干肥圆,后部细,形似鼬鼠。大部分时间里不是仰躺着浮在水面上,就是潜入海床觅食。", image: "img/sea otter.jpg" }
]
},
computed: {
rows() {
const chunkSize = 3;
return Array.from({ length: Math.ceil(this.animals.length / chunkSize) }, (_, i) =>
this.animals.slice(i * chunkSize, i * chunkSize + chunkSize)
);
}
}
});
2.1.2 搜索界面---文件上传及预览
使用 FileReader 读取用户上传的图片为 DataURL;
动态生成图片预览 标签,并显示在页面上;
将图片的 DataURL 存储到 localStorage,方便其他页面(如详情页)访问;
// 等待文档加载完毕
document.addEventListener('DOMContentLoaded', function() {
// 获取文件输入元素
var fileInput = document.getElementById('file-upload');
var uploadContent = document.querySelector('.input-file')
// 监听文件输入的变化
fileInput.addEventListener('change', function(event) {
if (this.files && this.files[0]) {
var file = this.files[0];
var reader = new FileReader();
// 读取文件成功后执行的函数
reader.onload = function(e) {
// 创建预览图片元素
var imgPreview = document.createElement('img');
imgPreview.src = e.target.result;
imgPreview.style.maxWidth = '70px'; // 限制预览图的最大宽度
imgPreview.style.marginTop = '10px'; // 预览图与其他内容的间隔
// 将预览图片添加到页面上
// 确保不重复添加预览图片
var existingPreview = uploadContent.querySelector('img');
if (existingPreview) {
uploadContent.replaceChild(imgPreview, existingPreview);
} else {
uploadContent.appendChild(imgPreview);
}
// 显示文件名
var fileNameDisplay = document.createElement('p');
fileNameDisplay.textContent = '选择的文件:' + file.name;
// 确保不重复添加文件名信息
var existingFileName = uploadContent.querySelector('p');
if (existingFileName) {
uploadContent.replaceChild(fileNameDisplay, existingFileName);
} else {
uploadContent.appendChild(fileNameDisplay);
}
// 存储上传的图片DataURL到 localStorage(用于详情页展示)
localStorage.setItem('uploadedImage', e.target.result);
};
// 读取文件为DataURL
reader.readAsDataURL(file);
}
});
2.1.3 搜索界面---动物分类与爬虫信息获取
基于用户上传的动物图片,通过后端接口完成动物分类并获取补充信息;
上传图片并调用 classify_animals 接口,获得动物分类结果;
调用 upload_and_crawl 接口,获取该动物的更多相关信息和相似图片;
数据整合: 接口返回的数据通过合并(combinedData)存储到 localStorage,用于详情页展示;
// 识别动物按钮的点击事件处理
document.getElementById('search-button').addEventListener('click', function () {
var fileInput = document.getElementById('file-upload');
if (fileInput.files && fileInput.files[0]) {
var file = fileInput.files[0];
// 创建 FormData 对象
var formData = new FormData();
formData.append('image', file);
console.log("Uploading image:", file.name);
// 首先调用 classify_animals 接口
fetch('http://localhost:5000/api/classify_animals', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data1 => {
if (data1.success) {
// 然后调用 upload_and_crawl 接口
return fetch('http://localhost:5000/api/upload_and_crawl', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data2 => {
if (data2.success) {
// 合并两个接口返回的数据
var combinedData = {
animal_name: data1.animal_name,
// baike_info: data1.baike_info,
details: data1.details,
similar_images: data2.similar_images
};
console.log("Combined Data:", combinedData);
// 存储到 localStorage
localStorage.setItem('animalDetails', JSON.stringify(combinedData));
// 存储上传的图片DataURL到 localStorage(用于详情页展示)
var reader = new FileReader();
reader.onload = function(e) {
localStorage.setItem('uploadedImage', e.target.result);
console.log("Uploaded Image DataURL stored.");
};
reader.readAsDataURL(file);
// 跳转到 aniamal_details.html 页面
window.location.href = 'animal_details.html';
} else {
document.getElementById('search-results').innerHTML = `<p>Error: ${data2.error}</p>`;
console.error("upload_and_crawl error:", data2.error);
}
});
} else {
document.getElementById('search-results').innerHTML = `<p>Error: ${data1.error}</p>`;
console.error("classify_animals error:", data1.error);
}
})
.catch(error => {
console.error('Error:', error);
document.getElementById('search-results').innerHTML = `<p>Error: ${error.message}</p>`;
});
} else {
alert('请先上传动物图片。');
}
});
});
2.1.4 搜索界面---动物描述搜索功能
获取输入框内容,验证非空;
使用 fetch 调用 submit_description_animal 接口提交描述;
接口返回的描述信息通过弹窗显示在用户界面上;
弹窗设计: openPopup 和 closePopup 两个封装函数,用于控制弹窗显示和关闭;
弹窗通过 innerHTML 动态填充内容;
// 确保弹窗初始状态为隐藏
document.getElementById('popup').style.display = 'none';
document.getElementById('search-icon').addEventListener('click', function() {
var descriptionInput = document.getElementById('description-input');
// 确保输入框有值
if (descriptionInput.value.trim() !== '') {
var description = descriptionInput.value;
var formData = new FormData();
formData.append('description', description);
// 发送POST请求
fetch('http://localhost:5000/api/submit_description_animal', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
// 如果请求成功,显示植物描述
document.getElementById('popup-content').innerHTML = `<p>动物描述: ${data.details}</p>`;
openPopup(); // 使用封装的 openPopup 函数来显示弹窗
} else {
// 如果请求失败,显示错误信息
document.getElementById('popup-content').innerHTML = `<p>Error: ${data.error}</p>`;
openPopup(); // 使用封装的 openPopup 函数来显示弹窗
}
})
.catch(error => {
// 捕获并显示请求错误
console.error('Error:', error);
document.getElementById('popup-content').innerHTML = `<p>Error: ${error.message}</p>`;
openPopup(); // 使用封装的 openPopup 函数来显示弹窗
});
}
});
// 关闭弹窗的函数
function closePopup() {
document.getElementById('popup').style.display = 'none'; // 隐藏弹窗
}
// 打开弹窗的函数
function openPopup() {
document.getElementById('popup').style.display = 'flex'; // 使用 flex 显示弹窗
}
</script>
2.1.5 详情界面---动物详情数据的展示
通过 localStorage.getItem('animalDetails') 获取动物的详细信息;
通过 data.animal_name 动态设置动物名称,如果为空则显示为 "未知动物";
优先使用接口返回的图片 URL (data.image_url),如果接口没有返回图片,则回退显示用户上传的图片(存储于 localStorage 的 uploadedImage),如果两者都没有,则显示默认的提示(alt);
使用 data.details 填充详细描述区域,若没有内容则显示默认提示;
遍历 data.similar_images 数组,动态生成 HTML 列表项来展示每张图片;
当 localStorage 中没有动物详情数据时,显示提示信息,并提供返回按钮;
<script>
document.addEventListener('DOMContentLoaded', function() {
// 从 localStorage 获取动物详情数据
var animalDetails = localStorage.getItem('animalDetails');
if (animalDetails) {
var data = JSON.parse(animalDetails);
// 显示动物名称
document.getElementById('animal-name').textContent = data.animal_name || '未知动物';
// 显示动物图片
var animalImage = document.getElementById('animal-image');
if (data.image_url) {
animalImage.src = data.image_url;
animalImage.alt = data.animal_name || '动物图片';
} else {
// 如果没有返回图片URL,使用上传的图片
var uploadData = localStorage.getItem('uploadedImage');
if (uploadData) {
animalImage.src = uploadData;
animalImage.alt = data.animal_name || '动物图片';
} else {
animalImage.alt = '无图片可显示';
}
}
// 显示详细信息
document.getElementById('detailed-info').innerHTML = data.details || '暂无详细信息。';
// 显示相似图片
if (data.similar_images && data.similar_images.length > 0) {
var similarImagesList = document.getElementById('similar-images-list');
similarImagesList.innerHTML = data.similar_images.map(url =>
`<li><img src="${url}" alt="相似图片" class="similar-image"/></li>`
).join('');
} else {
document.getElementById('similar-images-container').style.display = 'none';
}
} else {
// 如果没有数据,显示提示并提供返回按钮
document.querySelector('.details-container').innerHTML = `
<p>没有找到动物详情信息。</p>
<button onclick="window.location.href='animals_get.html'">返回</button>
`;
}
});
2.1.6 详情界面---保存动物详情
从 localStorage 获取动物详情数据;
提取关键字段(如 animal_name、image_url、details 等);
相似图片列表通过 Array.join(',') 拼接为字符串,以便后端处理;
确保传递的是图片 URL(如果存在),避免传递 base64 编码的图片;
构造 JSON 格式的请求数据,使用 fetch 发送 POST 请求至保存接口 (http://localhost:5001/api/save_animal);
如果保存成功,显示成功提示, 如果保存失败,显示错误信息;
数据保存操作通过异步请求实现,用户界面不会因数据提交而卡顿;
function saveAnimaltDetails() {
var animalDetails = localStorage.getItem('animalDetails');
if (!animalDetails) {
alert('没有动物详情可保存。');
return;
}
var data = JSON.parse(animalDetails);
// 获取上传的图片路径(不要传递base64)
var uploadData = localStorage.getItem('uploadedImage');
// 获取相似图片列表
var similarImages = data.similar_images || [];
var similarImagesStr = similarImages.join(',');
// 传递的是图片路径而不是base64编码
var image_url = data.image_url || uploadData || '';
// 准备要发送的数据
var payload = {
animal_name: data.animal_name || '未知动物',
image_url: image_url,
similar_images: similarImagesStr,
details: data.details || ''
};
// 发送请求
var apiUrl = 'http://localhost:5001/api/save_animal';
fetch(apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
})
.then(response => response.json())
.then(result => {
if (result.status === 'success') {
alert('动物详情已成功记录!');
} else {
alert('保存失败: ' + result.message);
}
})
.catch(error => {
console.error('保存失败:', error);
alert('保存失败');
});
}
</script>
2.2 我的工作感悟
2.3.1 关于项目:
--> 我晓得其实现在已经有很多跟我们这个项目功能类似的网站,而且别人的功能可能比我们更完善更强大,所以老师也一直提醒我们要挖掘项目的创新点和痛点,于是我们添加了文本和图片两种搜索方式进行搜索,而且添加了图鉴收藏的功能。可能有了这些还是不够“痛”,我们又在网页界面设计上下了功夫,相信美貌单出是死局,但是加上任一个技能都是王炸,,,哈哈开玩笑!!! 也就是说我还算对这个项目比较满意,尤其我觉得比较酷的地方是根据上传的图片进行相似图片的爬取,而且是实时爬虫----天哪!实时爬虫!听起来就很厉害,虽然我没有参与这部分的工作,但是知道这部分是使用selenium实现的。哦呦!又是Selenium!还记得上次爬取慕课网站的时候我写了一大篇关于selenium的感悟,当时觉得我对其一无所知,现在嘛,我仍然还是这么觉得!我有看过它的过程,整个代码不长只有五十几行,整体思路是模拟浏览器访问百度识图网页,等待页面加载并点击相机图标,模拟上传图片,等待搜索结果加载出来,用css方法获取前三个相似图片的直接URL,最后返回结果。我觉得学会数据采集这门课还是很方便的,这学期很多项目都涉及到这个,如果学会了爬虫,就可以根据需求,随时随地,想爬就爬,不过我还没有练就这般的本领,,,也是阴差阳错地,这些项目爬虫部分的工作我都没有参与,可能都是分配给更擅长的同学去做了,所以我的练习量只有平时的作业,这个寒假如果没事我想随便爬一些网站练习练习。
2.3.2 关于团队:
--> 我其实是个严重的拖延症患者,再加上现在天气比较冷,所以只想缩在被子里不想做任何工作;但是我的室友(跟我一样负责前端的工作)却不会这样,就算时间不赶,她也总是会提前完成工作,当我蜷缩在半空中时,她会坐在下面一边忍受寒冷一边为了我们的项目“嗒嗒嗒”地敲击着那没有温度的键盘,有时甚至连我的那部分工作也帮我做了,对我来说真的是楷模的存在!还有后端的伙伴也是有求必应,对于我们前端提出的数据相关要求,她们也都是会立刻满足。她们一个个执行力都是那么的强,我在该氛围下也多少被她们带动了一点,所以说圈子真的非常重要!因为我们是女生组队,所以平时开会也都是聚在宿舍以聊天的方式进行的,如果是工作可能是略显随意,但是对于实践作业我觉得刚刚好--是我喜欢的方式,不会那么令人厌烦和不耐烦,又能达到开会的目的,挺好的。我发现自己也是越来越擅长跟伙伴交流工作了,这也是小组合作的收获之一吧!