feat: 优化标注页面

This commit is contained in:
2025-10-26 15:34:58 +08:00
parent 856669de69
commit 551555d526
4 changed files with 882 additions and 106 deletions

View File

@@ -141,8 +141,23 @@ def get_images_api():
"""API: 获取图片列表 (JSON 格式)"""
conn = sqlite3.connect(DATABASE_PATH)
cursor = conn.cursor()
# 按时间倒序排列,包含标注图片字段
cursor.execute("SELECT id, left_filename, right_filename, left_marked_filename, right_marked_filename, timestamp, metadata, comment, created_at FROM images ORDER BY timestamp DESC")
# 按时间倒序排列,包含标注图片字段和人工标注字段
try:
cursor.execute("""
SELECT id, left_filename, right_filename, left_marked_filename, right_marked_filename,
timestamp, metadata, comment, created_at, manual_detections, is_manual_labeled
FROM images
ORDER BY timestamp DESC
""")
except sqlite3.OperationalError:
# 如果字段不存在,使用基本查询
cursor.execute("""
SELECT id, left_filename, right_filename, left_marked_filename, right_marked_filename,
timestamp, metadata, comment, created_at, NULL as manual_detections, 0 as is_manual_labeled
FROM images
ORDER BY timestamp DESC
""")
rows = cursor.fetchall()
conn.close()
@@ -157,7 +172,9 @@ def get_images_api():
"timestamp": row[5],
"metadata": row[6],
"comment": row[7] or "", # 如果没有 comment 则显示空字符串
"created_at": row[8]
"created_at": row[8],
"manual_detections": row[9] or "[]", # 人工标注检测框结果
"is_manual_labeled": bool(row[10]) if row[10] is not None else False # 是否已完成人工标注
})
return jsonify(images)
@@ -323,31 +340,41 @@ def upload_images():
right_marked_path = None
try:
with VisionAPIClient() as client:
# 处理左图
left_task_id = client.submit_task(image_id=1, image=img_left)
# 处理右图
right_task_id = client.submit_task(image_id=2, image=img_right)
# with VisionAPIClient() as client:
# # 处理左图
# left_task_id = client.submit_task(image_id=1, image=img_left)
# # 处理右图
# right_task_id = client.submit_task(image_id=2, image=img_right)
# 等待任务完成
client.task_queue.join()
# # 等待任务完成
# client.task_queue.join()
# 获取处理结果
left_result = client.get_result(left_task_id)
right_result = client.get_result(right_task_id)
# # 获取处理结果
# left_result = client.get_result(left_task_id)
# right_result = client.get_result(right_task_id)
# 生成标注图片
if left_result and left_result.success:
marked_left_img = draw_detections_on_image(img_left, left_result.detections)
left_marked_path = os.path.join(SAVE_PATH_LEFT_MARKED, left_marked_filename)
cv2.imwrite(left_marked_path, marked_left_img)
logger.info(f"Saved marked left image: {left_marked_path}")
# # 生成标注图片
# if left_result and left_result.success:
# marked_left_img = draw_detections_on_image(img_left, left_result.detections)
# left_marked_path = os.path.join(SAVE_PATH_LEFT_MARKED, left_marked_filename)
# cv2.imwrite(left_marked_path, marked_left_img)
# logger.info(f"Saved marked left image: {left_marked_path}")
if right_result and right_result.success:
marked_right_img = draw_detections_on_image(img_right, right_result.detections)
right_marked_path = os.path.join(SAVE_PATH_RIGHT_MARKED, right_marked_filename)
cv2.imwrite(right_marked_path, marked_right_img)
logger.info(f"Saved marked right image: {right_marked_path}")
# if right_result and right_result.success:
# marked_right_img = draw_detections_on_image(img_right, right_result.detections)
# right_marked_path = os.path.join(SAVE_PATH_RIGHT_MARKED, right_marked_filename)
# cv2.imwrite(right_marked_path, marked_right_img)
# logger.info(f"Saved marked right image: {right_marked_path}")
# Debug 直接原图覆盖
left_marked_path = os.path.join(SAVE_PATH_LEFT_MARKED, left_marked_filename)
cv2.imwrite(left_marked_path, img_left)
logger.info(f"Saved marked left image: {left_marked_path}")
right_marked_path = os.path.join(SAVE_PATH_RIGHT_MARKED, right_marked_filename)
cv2.imwrite(right_marked_path, img_right)
logger.info(f"Saved marked right image: {right_marked_path}")
except Exception as e:
logger.error(f"Error processing images with VisionAPIClient: {e}")
# 即使处理失败,也继续保存原图
@@ -418,6 +445,155 @@ def status():
timestamp = latest_timestamp if has_frames else "N/A"
return jsonify({"has_frames": has_frames, "latest_timestamp": timestamp})
@app.route('/api/images/manual-detections', methods=['PUT'])
def update_manual_detections():
"""API: 更新图片的人工标注检测框结果,支持左右图像分别标注"""
data = request.json
image_id = data.get('id')
side = data.get('side', 'left') # 获取side参数默认为左侧
detections = data.get('detections')
if not image_id or detections is None:
return jsonify({"error": "Image ID and detections are required"}), 400
# 验证检测数据格式
if not isinstance(detections, list):
return jsonify({"error": "Detections must be a list"}), 400
for detection in detections:
if not isinstance(detection, dict):
return jsonify({"error": "Each detection must be a dictionary"}), 400
required_keys = ['id', 'label', 'bbox']
for key in required_keys:
if key not in detection:
return jsonify({"error": f"Missing required key '{key}' in detection"}), 400
# 验证ID
if not isinstance(detection['id'], int) or detection['id'] not in [1, 2, 3, 4]:
return jsonify({"error": f"Invalid ID in detection: {detection['id']}"}), 400
# 验证标签
valid_labels = ['caisson', 'soldier', 'gun', 'number']
if detection['label'] not in valid_labels:
return jsonify({"error": f"Invalid label in detection: {detection['label']}"}), 400
# 验证边界框
bbox = detection['bbox']
if not isinstance(bbox, list) or len(bbox) != 4:
return jsonify({"error": f"Invalid bbox format in detection"}), 400
for coord in bbox:
if not isinstance(coord, int) or not (0 <= coord <= 999):
return jsonify({"error": f"Invalid bbox coordinate: {coord}"}), 400
conn = sqlite3.connect(DATABASE_PATH)
cursor = conn.cursor()
cursor.execute("SELECT id FROM images WHERE id = ?", (image_id,))
if not cursor.fetchone():
conn.close()
return jsonify({"error": "Image not found"}), 404
# 添加人工标注字段(如果不存在)
try:
cursor.execute("""
ALTER TABLE images ADD COLUMN manual_detections_left TEXT
""")
except sqlite3.OperationalError:
pass
try:
cursor.execute("""
ALTER TABLE images ADD COLUMN manual_detections_right TEXT
""")
except sqlite3.OperationalError:
pass
try:
cursor.execute("""
ALTER TABLE images ADD COLUMN is_manual_labeled_left INTEGER DEFAULT 0
""")
except sqlite3.OperationalError:
pass
try:
cursor.execute("""
ALTER TABLE images ADD COLUMN is_manual_labeled_right INTEGER DEFAULT 0
""")
except sqlite3.OperationalError:
pass
# 根据side参数更新对应的人工标注结果
if side == 'left':
cursor.execute("""
UPDATE images
SET manual_detections_left = ?, is_manual_labeled_left = 1
WHERE id = ?
""", (json.dumps(detections), image_id))
else: # side == 'right'
cursor.execute("""
UPDATE images
SET manual_detections_right = ?, is_manual_labeled_right = 1
WHERE id = ?
""", (json.dumps(detections), image_id))
conn.commit()
conn.close()
# 重新生成标注图片
try:
regenerate_marked_images(image_id, detections, side)
return jsonify({"message": f"Manual detections for image {image_id} ({side}) updated successfully and marked images regenerated"})
except Exception as e:
return jsonify({"message": f"Manual detections for image {image_id} ({side}) updated successfully but failed to regenerate marked images: {str(e)}"}), 500
# 添加重新生成标注图片的函数
def regenerate_marked_images(image_id, detections, side):
"""重新生成标注图片,支持左右图像分别处理"""
conn = sqlite3.connect(DATABASE_PATH)
cursor = conn.cursor()
cursor.execute("""
SELECT left_filename, right_filename, left_marked_filename, right_marked_filename
FROM images
WHERE id = ?
""", (image_id,))
row = cursor.fetchone()
conn.close()
if not row:
raise Exception("Image not found")
left_filename, right_filename, left_marked_filename, right_marked_filename = row
# 根据指定的side重新生成对应的标注图片
if side == 'left' and left_marked_filename:
left_path = os.path.join(SAVE_PATH_LEFT, left_filename)
left_marked_path = os.path.join(SAVE_PATH_LEFT_MARKED, left_marked_filename)
if os.path.exists(left_path):
img_left = cv2.imread(left_path)
if img_left is not None:
marked_left_img = draw_detections_on_image(img_left, detections)
cv2.imwrite(left_marked_path, marked_left_img)
elif side == 'right' and right_marked_filename:
right_path = os.path.join(SAVE_PATH_RIGHT, right_filename)
right_marked_path = os.path.join(SAVE_PATH_RIGHT_MARKED, right_marked_filename)
if os.path.exists(right_path):
img_right = cv2.imread(right_path)
if img_right is not None:
marked_right_img = draw_detections_on_image(img_right, detections)
cv2.imwrite(right_marked_path, marked_right_img)
# 添加人工标注页面路由
@app.route('/manual-annotation')
def manual_annotation():
"""人工标注页面"""
return render_template('manual_annotation.html')
# --- SocketIO 事件处理程序 ---
@socketio.event
def connect():