feat: 优化标注页面
This commit is contained in:
224
cam_web.py
224
cam_web.py
@@ -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():
|
||||
|
||||
Reference in New Issue
Block a user