图像标注的功能
This commit is contained in:
parent
5cc48fa229
commit
7e42679767
|
|
@ -0,0 +1,49 @@
|
|||
import request from '@/utils/request'
|
||||
import request_formdata from "@/utils/request_formdata";
|
||||
|
||||
// 查询标签列表
|
||||
export function getSelectedAPI(data) {
|
||||
return request({
|
||||
url: '/image/caption/getSelected',
|
||||
method: 'POST',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
// 查询左侧历史记录
|
||||
export function getImageListAPI(data) {
|
||||
return request({
|
||||
url: '/image/caption/getImageList',
|
||||
method: 'POST',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
//查询右侧的历史记录详情
|
||||
export function getImageListDetailsAPI(data) {
|
||||
return request({
|
||||
url: '/image/caption/getImageListDetails',
|
||||
method: 'POST',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
//新增标注
|
||||
export function addImageInfoAPI(data) {
|
||||
return request_formdata({
|
||||
url: '/image/caption/addImageInfo',
|
||||
method: 'POST',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
//修改图片
|
||||
export function updateImageSureAPI(data) {
|
||||
return request_formdata({
|
||||
url: '/image/caption/updateImageSure',
|
||||
method: 'POST',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -6,8 +6,10 @@
|
|||
:upload-info="uploadInfo"
|
||||
:image-results="imageResults"
|
||||
:selected-images="selectedImages"
|
||||
:is-sure="isSure"
|
||||
@hand-click="handleHandClickFromChild"
|
||||
@confirm-results="$emit('confirm-results')"
|
||||
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -76,6 +78,10 @@ export default {
|
|||
fileList: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
isSure: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
|
|
|||
|
|
@ -20,14 +20,14 @@
|
|||
>
|
||||
<!-- 右上角的hand图标 -->
|
||||
<img
|
||||
v-if="selectedImages[index]"
|
||||
v-if="selectedImages[index] && isSure == '0'"
|
||||
src="@/assets/images/imageCaptioning/hand-yellow.png"
|
||||
alt="selected"
|
||||
class="icon-hand"
|
||||
@click="$emit('hand-click', result, index)"
|
||||
>
|
||||
<img
|
||||
v-else
|
||||
v-else-if="!selectedImages[index] && isSure == '0'"
|
||||
src="@/assets/images/imageCaptioning/hand.png"
|
||||
alt="hand"
|
||||
class="icon-hand"
|
||||
|
|
@ -38,7 +38,7 @@
|
|||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="action-buttons">
|
||||
<el-button type="primary" @click="$emit('confirm-results')">确认</el-button>
|
||||
<el-button type="primary" @click="$emit('confirm-results')" v-if="isSure == '0'">确认</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -58,7 +58,20 @@ export default {
|
|||
selectedImages: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
isSure: {
|
||||
type: String,
|
||||
default: '0'
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
isSure(newValue) {
|
||||
console.log('isSure changed to:', newValue);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 打印初始 isSure 的值
|
||||
console.log('Initial isSure:', this.isSure);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,22 +1,13 @@
|
|||
<template>
|
||||
<div class="tag-container">
|
||||
<!-- <div-->
|
||||
<!-- v-for="(tag, index) in visibleTags"-->
|
||||
<!-- :key="index"-->
|
||||
<!-- class="tag-item"-->
|
||||
<!-- :class="{ active: selectedTag.includes(tag) }"-->
|
||||
<!-- @click="$emit('toggle-tag', tag)"-->
|
||||
<!-- >-->
|
||||
<!-- {{ tag }}-->
|
||||
<!-- </div>-->
|
||||
<template v-for="(tag, index) in visibleTags">
|
||||
<div
|
||||
:key="'tag-' + index"
|
||||
:key="'tag-' + tag.id"
|
||||
class="tag-item"
|
||||
:class="{ active: selectedTag.includes(tag) }"
|
||||
:class="{ active: isSelected(tag) }"
|
||||
@click="$emit('toggle-tag', tag)"
|
||||
>
|
||||
{{ tag }}
|
||||
{{ tag.name }}
|
||||
</div>
|
||||
<span
|
||||
v-if="index < visibleTags.length - 1"
|
||||
|
|
@ -50,6 +41,12 @@ export default {
|
|||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 检查标签是否被选中
|
||||
isSelected(tag) {
|
||||
return this.selectedTag.some(item => item.id === tag.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@
|
|||
:visible-tags="visibleTags"
|
||||
:selected-tag="selectedTag"
|
||||
:file-list="fileList"
|
||||
:is-sure="isSure"
|
||||
@hand-click="handleHandClick"
|
||||
@confirm-results="confirmResults"
|
||||
@toggle-tag="toggleTag"
|
||||
|
|
@ -63,10 +64,18 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
getSelectedAPI,
|
||||
getImageListAPI,
|
||||
getImageListDetailsAPI,
|
||||
addImageInfoAPI,
|
||||
updateImageSureAPI
|
||||
} from "@/api/imageCaptioning/imageCaptioning";
|
||||
import Sidebar from "./components/Sidebar";
|
||||
import TagSelector from "./components/TagSelector";
|
||||
import FileUploader from "./components/FileUploader";
|
||||
import HistoryView from "./components/HistoryView";
|
||||
|
||||
export default {
|
||||
name: "imageCaptioning",
|
||||
components: {
|
||||
|
|
@ -79,21 +88,14 @@ export default {
|
|||
return {
|
||||
showNewAnnotation: true, // 控制新建标注界面的显示状态
|
||||
showNewAnnotationAdd: false, // 控制历史界面的显示状态
|
||||
|
||||
// 标签数据(从字典获取)
|
||||
tags: ['安全帽', '绝缘子', '人', '电线杆', '变压器', '电缆'],
|
||||
visibleTags: ['安全帽', '绝缘子', '人'], // 默认显示前3个
|
||||
visibleTagsOld: [],
|
||||
selectedTag: [],
|
||||
showAllTags: false,
|
||||
tags: [],// 标签数据
|
||||
visibleTags: [], // 默认显示前3个
|
||||
selectedTag: [], // 当前选中的标签
|
||||
showAllTags: false, // 控制是否显示所有标签
|
||||
|
||||
// 导航栏数据
|
||||
labelList: [
|
||||
{id: 1, date: '2025-09-13', name: '安全帽、绝缘子、虎视眈眈、去去去、滴滴滴、'},
|
||||
{id: 2, date: '2025-09-10', name: '安全帽'}
|
||||
],
|
||||
labelList: [],
|
||||
selectedItem: null,
|
||||
|
||||
// 文件上传相关
|
||||
fileList: [],
|
||||
showImageResults: false,
|
||||
|
|
@ -103,14 +105,58 @@ export default {
|
|||
imageResults: [],
|
||||
selectedImages: {}, // 用于跟踪选中的图片
|
||||
isSidebarVisible: true, // 控制左侧导航栏显示/隐藏
|
||||
isSure:'', //小图标,确定按钮 0-未确定 1-已确定
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
// 获取标签列表数据
|
||||
this.getSelectedAPI()
|
||||
//查询左侧历史记录
|
||||
this.getImageListAPI()
|
||||
},
|
||||
|
||||
methods: {
|
||||
createNewLabel() {
|
||||
// 只有当 visibleTagsOld 不为空时才赋值
|
||||
if (this.visibleTagsOld && this.visibleTagsOld.length > 0) {
|
||||
this.visibleTags = this.visibleTagsOld;
|
||||
async getSelectedAPI() {
|
||||
try {
|
||||
const res = await getSelectedAPI()
|
||||
// 判断返回码和数据
|
||||
if (res.code === 200 && res.data && Array.isArray(res.data)) {
|
||||
// 将返回的数据转换为包含 id 和 name 的对象数组
|
||||
this.tags = res.data.map(item => ({
|
||||
id: item.id,
|
||||
name: item.name
|
||||
}))
|
||||
this.visibleTags = this.tags.slice(0, 3);
|
||||
|
||||
} else {
|
||||
console.error('获取标签列表失败:', res)
|
||||
this.tags = []
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取标签列表异常:', error)
|
||||
this.tags = []
|
||||
}
|
||||
},
|
||||
|
||||
async getImageListAPI() {
|
||||
try {
|
||||
const res = await getImageListAPI({operaType: 1})
|
||||
// 判断返回码和数据
|
||||
if (res.code === 200 && res.data && Array.isArray(res.data)) {
|
||||
this.labelList = res.data.map(item => ({
|
||||
id: item.id,
|
||||
date: item.operaTime,
|
||||
name: item.operaName
|
||||
}))
|
||||
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取列表异常:', error)
|
||||
}
|
||||
},
|
||||
|
||||
createNewLabel() {
|
||||
this.selectedItem = null;
|
||||
this.fileList = [];
|
||||
this.selectedTag = [];
|
||||
|
|
@ -128,15 +174,17 @@ export default {
|
|||
this.$message.warning('请选择需要标注的内容标签')
|
||||
return
|
||||
}
|
||||
|
||||
// 标签处理
|
||||
const selectedTagIds = this.selectedTag.map(tag => tag.id).join(',');
|
||||
const selectedTagNames = this.selectedTag.map(tag => tag.name).join(',');
|
||||
console.log('选中的标签IDs:', selectedTagIds);
|
||||
console.log('选中的标签名称:', selectedTagNames);
|
||||
if (this.fileList.length === 0) {
|
||||
this.$message.warning('请上传图片')
|
||||
return
|
||||
}
|
||||
|
||||
// 初始化 selectedImages
|
||||
this.selectedImages = {};
|
||||
|
||||
// 显示加载状态
|
||||
const loading = this.$loading({
|
||||
lock: true,
|
||||
|
|
@ -144,39 +192,71 @@ export default {
|
|||
spinner: 'el-icon-loading',
|
||||
background: 'rgba(0, 0, 0, 0.7)'
|
||||
});
|
||||
|
||||
// 保存原始文件列表用于显示结果
|
||||
const originalFileList = [...this.fileList];
|
||||
|
||||
// 清空上传预览区域
|
||||
this.fileList = [];
|
||||
|
||||
// 模拟网络延迟
|
||||
// 创建 FormData 对象
|
||||
const formData = new FormData();
|
||||
// 添加文件
|
||||
originalFileList.forEach(file => {
|
||||
formData.append('files', file.raw); // 使用 raw 字段获取原始文件对象
|
||||
});
|
||||
formData.append('param', selectedTagNames);
|
||||
//调用上传的接口,传递文件列表和标签IDs,name
|
||||
addImageInfoAPI(formData)
|
||||
.then(res => {
|
||||
loading.close();
|
||||
if (res.code === 200 && res.data && Array.isArray(res.data)) {
|
||||
// 处理成功响应
|
||||
const record = res.data[0]; // 获取第一条记录
|
||||
const totalCount = record.imageNum || originalFileList.length;
|
||||
const recognizedCount = record.bzNum || 0;
|
||||
const unrecognizedCount = record.wbzNum || (totalCount - recognizedCount);
|
||||
|
||||
this.uploadInfo = `上传${totalCount}张图片,可识别标注${recognizedCount}张,${unrecognizedCount}张无法识别标注。已识别标注图片如下,请确认。`;
|
||||
|
||||
// 设置图片结果
|
||||
this.imageResults = record.fileVoList.map(item => ({
|
||||
url: item.bjUrl || "未找到图片地址",
|
||||
id: item.imageId || item.id,
|
||||
name: item.originalName,
|
||||
contentImage: item.contentImage,
|
||||
fileSize: item.fileSize,
|
||||
operId: res.data[0].id , // 操作ID
|
||||
}));
|
||||
|
||||
this.isSure = record.isSure;
|
||||
|
||||
// 刷新左侧标签文本并选中对应标签
|
||||
this.refreshSidebarLabel(record.operaName, record.id);
|
||||
|
||||
this.showImageResults = true;
|
||||
this.showNewAnnotation = false;
|
||||
this.showNewAnnotationAdd = true;
|
||||
this.$message.success('上传和识别完成');
|
||||
} else {
|
||||
this.$message.error('上传失败');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
loading.close();
|
||||
console.error('上传异常:', error);
|
||||
this.$message.error('上传过程中出现错误');
|
||||
});
|
||||
},
|
||||
|
||||
refreshSidebarLabel(operaName, recordId) {
|
||||
this.getImageListAPI()
|
||||
setTimeout(() => {
|
||||
loading.close();
|
||||
// 模拟上传和识别过程
|
||||
const recognizedCount = Math.floor(originalFileList.length * 0.7);
|
||||
const unrecognizedCount = originalFileList.length - recognizedCount;
|
||||
|
||||
this.uploadInfo = `上传${originalFileList.length}张图片,可识别标注${recognizedCount}张,${unrecognizedCount}张无法识别标注。已识别标注图片如下,请确认。`;
|
||||
|
||||
// 模拟识别结果(返回与上传相同数量的图片,用status字段区分成功失败)
|
||||
this.imageResults = originalFileList.map((file, index) => ({
|
||||
url: file.url,
|
||||
id: index,
|
||||
// status: 1 表示识别成功,0 表示识别失败
|
||||
status: index < recognizedCount ? 1 : 0
|
||||
}));
|
||||
|
||||
console.log(this.imageResults);
|
||||
this.showImageResults = true;
|
||||
|
||||
// 切换到结果显示界面
|
||||
this.showNewAnnotation = false;
|
||||
this.showNewAnnotationAdd = true;
|
||||
|
||||
this.$message.success('上传和识别完成');
|
||||
}, 1500); // 模拟1.5秒的处理时间
|
||||
const index = this.labelList.findIndex(item => item.id === recordId);
|
||||
if (index !== -1) {
|
||||
this.labelList[index].name = operaName;
|
||||
}
|
||||
// 选中对应的标签
|
||||
this.selectedItem = recordId;
|
||||
}, 500); // 延迟确保数据加载完成
|
||||
},
|
||||
|
||||
selectItem(item) {
|
||||
|
|
@ -189,29 +269,26 @@ export default {
|
|||
// 模拟加载历史记录数据
|
||||
const record = this.labelList.find(r => r.id === recordId);
|
||||
if (record) {
|
||||
|
||||
this.showNewAnnotation = false;
|
||||
this.showNewAnnotationAdd = true;
|
||||
|
||||
// 这里可以添加实际的数据加载逻辑
|
||||
this.fileList = []; // 清空文件列表
|
||||
this.selectedTag = []; // 清空标签选择
|
||||
this.showImageResults = true; // 显示识别结果
|
||||
this.selectedImages = {}; // 初始化选中状态
|
||||
// 生成测试数据
|
||||
// 生成数据
|
||||
this.generateTestData(recordId);
|
||||
}
|
||||
},
|
||||
|
||||
toggleTag(tag) {
|
||||
const index = this.selectedTag.indexOf(tag)
|
||||
const index = this.selectedTag.findIndex(item => item.id === tag.id)
|
||||
if (index === -1) {
|
||||
this.selectedTag.push(tag)
|
||||
} else {
|
||||
this.selectedTag.splice(index, 1)
|
||||
}
|
||||
|
||||
// 如果当前处于收起状态,需要同步更新visibleTags
|
||||
if (!this.showAllTags) {
|
||||
this.updateVisibleTagsInCollapsedState();
|
||||
}
|
||||
|
|
@ -222,16 +299,15 @@ export default {
|
|||
const defaultTags = this.tags.slice(0, 3)
|
||||
|
||||
if (selectedTags.length > 0) {
|
||||
// 有选中标签时,优先显示选中的
|
||||
if (selectedTags.length >= 3) {
|
||||
this.visibleTags = selectedTags.slice(0, 3)
|
||||
} else {
|
||||
// 选中标签不足3个时,用默认标签补充
|
||||
const tags = defaultTags.filter(tag => !selectedTags.includes(tag)).slice(0, 3 - selectedTags.length)
|
||||
const tags = defaultTags.filter(tag =>
|
||||
!selectedTags.some(selected => selected.id === tag.id))
|
||||
.slice(0, 3 - selectedTags.length)
|
||||
this.visibleTags = [...selectedTags, ...tags]
|
||||
}
|
||||
} else {
|
||||
// 没有选中标签时,显示默认的前3个标签
|
||||
this.visibleTags = defaultTags
|
||||
}
|
||||
},
|
||||
|
|
@ -239,28 +315,21 @@ export default {
|
|||
toggleAllTags() {
|
||||
this.showAllTags = !this.showAllTags
|
||||
if (this.showAllTags) {
|
||||
// 展开时显示所有标签
|
||||
this.visibleTags = this.tags
|
||||
} else {
|
||||
// 收起时的处理逻辑
|
||||
const selectedTags = this.selectedTag || []
|
||||
const defaultTags = this.tags.slice(0, 3)
|
||||
|
||||
if (selectedTags.length > 0) {
|
||||
// 如果有选中的标签,优先展示所有选中标签
|
||||
// 如果选中标签超过3个,就显示所有选中标签
|
||||
// 如果选中标签不足3个,则补充默认标签
|
||||
if (selectedTags.length <= 3) {
|
||||
// 选中标签不超过3个,合并选中标签和默认标签
|
||||
const allTags = [...selectedTags, ...defaultTags]
|
||||
const uniqueTags = [...new Set(allTags)]
|
||||
const uniqueTags = allTags.filter((tag, index, self) =>
|
||||
index === self.findIndex(t => t.id === tag.id))
|
||||
this.visibleTags = uniqueTags.slice(0, 3)
|
||||
} else {
|
||||
// 选中标签超过3个,直接显示所有选中标签(突破3个限制)
|
||||
this.visibleTags = selectedTags
|
||||
}
|
||||
} else {
|
||||
// 没有选中标签时,显示默认的前3个标签
|
||||
this.visibleTags = defaultTags
|
||||
}
|
||||
}
|
||||
|
|
@ -287,6 +356,7 @@ export default {
|
|||
|
||||
// 处理手图标点击事件
|
||||
handleHandClick(result, index) {
|
||||
console.log(result)
|
||||
console.log("点击了图片:", index)
|
||||
// 使用对象属性的方式跟踪选中状态
|
||||
if (this.selectedImages[index]) {
|
||||
|
|
@ -298,38 +368,75 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
generateTestData(recordId) {
|
||||
// 模拟从后端获取的测试数据
|
||||
const testData = {
|
||||
id: recordId,
|
||||
date: '2025-09-13',
|
||||
name: '安全帽、绝缘子...',
|
||||
uploadInfo: `上传10张图片,可识别标注6张,4张无法识别标注。已识别标注图片如下,请确认。`,
|
||||
imageResults: [
|
||||
{
|
||||
url: 'https://fastly.picsum.photos/id/830/200/200.jpg?hmac=3ce7zNUn5yg_XKy7dHgIHta7t_0vghPQnAGUSGJuBZE',
|
||||
name: 'image1.jpg',
|
||||
},
|
||||
{
|
||||
url: 'https://example.com/image2.jpg',
|
||||
name: 'image1.jpg',
|
||||
},
|
||||
],
|
||||
selectedTags: ['安全帽', '绝缘子', '电线杆']
|
||||
};
|
||||
async generateTestData(recordId) {
|
||||
// 后端获取的数据
|
||||
try {
|
||||
const res = await getImageListDetailsAPI({id: recordId, operaType: 1})
|
||||
// 判断返回码和数据
|
||||
if (res.code === 200 && res.data && Array.isArray(res.data)) {
|
||||
// 处理图片结果数据
|
||||
this.imageResults = res.data[0].fileVoList.map((item, index) => ({
|
||||
url: item.bjUrl || item.url, // 使用 bjUrl 作为图片URL
|
||||
id: item.imageId || index, // 使用 imageId 作为ID
|
||||
name: item.originalName, // 原始文件名
|
||||
contentImage: item.contentImage, // 图片内容标签
|
||||
fileSize: item.fileSize, // 文件大小
|
||||
operId: res.data[0].id , // 操作ID
|
||||
}));
|
||||
|
||||
this.selectedTag = testData.selectedTags;
|
||||
this.uploadInfo = testData.uploadInfo;
|
||||
this.imageResults = testData.imageResults;
|
||||
this.isSure = res.data[0].isSure;
|
||||
|
||||
// 设置历史记录的可见标签
|
||||
this.visibleTagsOld = this.visibleTags;
|
||||
this.visibleTags = testData.selectedTags;
|
||||
const totalCount = res.data[0].imageNum || 0;
|
||||
const recognizedCount = res.data[0].bzNum || 0;
|
||||
const unrecognizedCount = res.data[0].wbzNum ;
|
||||
this.uploadInfo = `上传${totalCount}张图片,可识别标注${recognizedCount}张,${unrecognizedCount}张无法识别标注。已识别标注图片如下,请确认。`;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取列表异常:', error)
|
||||
}
|
||||
},
|
||||
|
||||
confirmResults(){
|
||||
console.log("确定")
|
||||
confirmResults() {
|
||||
console.log('当前 selectedImages 状态:', this.selectedImages);
|
||||
// 获取所有被选中的图片(基于 index 判断)
|
||||
const selectedItems = [];
|
||||
for (let index in this.selectedImages) {
|
||||
const idx = parseInt(index);
|
||||
if (this.selectedImages[idx]) {
|
||||
selectedItems.push(this.imageResults[idx]); // 根据 index 取出 imageResults 中对应图片
|
||||
}
|
||||
}
|
||||
if (selectedItems.length === 0) {
|
||||
this.$message.warning('请至少选择一张图片');
|
||||
return;
|
||||
}
|
||||
// 提取 operId 和 imageId
|
||||
const imageIds = selectedItems.map(item => item.id);
|
||||
const operId = selectedItems[0].operId; // 所有图片的 operId 应该一致
|
||||
// 调用确认接口
|
||||
this.handleConfirm(operId,imageIds);
|
||||
},
|
||||
|
||||
async handleConfirm(operId,imageIds) {
|
||||
try {
|
||||
// 调用后端接口
|
||||
const res = await updateImageSureAPI({
|
||||
id: operId,
|
||||
imageId:imageIds
|
||||
});
|
||||
if (res.code === 200) {
|
||||
this.$message.success('确认成功');
|
||||
// 可以在这里处理成功后的逻辑
|
||||
await this.generateTestData(operId);
|
||||
} else {
|
||||
this.$message.error('确认失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('确认异常:', error);
|
||||
this.$message.error('确认过程中出现错误');
|
||||
}
|
||||
},
|
||||
|
||||
toggleSidebar() {
|
||||
this.isSidebarVisible = !this.isSidebarVisible;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue