|
@@ -223,6 +223,7 @@ class PairedImageSaver:
|
|
|
# 初始化上传状态
|
|
# 初始化上传状态
|
|
|
self._upload_status[batch_id] = {
|
|
self._upload_status[batch_id] = {
|
|
|
'panorama': False,
|
|
'panorama': False,
|
|
|
|
|
+ 'panorama_url': None,
|
|
|
'ptz': {},
|
|
'ptz': {},
|
|
|
'completed': False
|
|
'completed': False
|
|
|
}
|
|
}
|
|
@@ -314,7 +315,7 @@ class PairedImageSaver:
|
|
|
person_info: Dict = None) -> Optional[str]:
|
|
person_info: Dict = None) -> Optional[str]:
|
|
|
"""
|
|
"""
|
|
|
保存球机聚焦图片
|
|
保存球机聚焦图片
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
Args:
|
|
Args:
|
|
|
batch_id: 批次ID
|
|
batch_id: 批次ID
|
|
|
person_index: 人员序号(0-based)
|
|
person_index: 人员序号(0-based)
|
|
@@ -322,15 +323,19 @@ class PairedImageSaver:
|
|
|
ptz_position: PTZ位置 (pan, tilt, zoom)
|
|
ptz_position: PTZ位置 (pan, tilt, zoom)
|
|
|
ptz_bbox: 球机图中检测到的bbox (x1, y1, x2, y2)
|
|
ptz_bbox: 球机图中检测到的bbox (x1, y1, x2, y2)
|
|
|
person_info: 额外人员信息
|
|
person_info: 额外人员信息
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
Returns:
|
|
Returns:
|
|
|
保存路径或 None
|
|
保存路径或 None
|
|
|
"""
|
|
"""
|
|
|
|
|
+ # 【调试】记录传入的参数
|
|
|
|
|
+ logger.info(f"[配对保存] save_ptz_image: batch={batch_id}, person={person_index}, "
|
|
|
|
|
+ f"PTZ=({ptz_position[0]:.1f}°, {ptz_position[1]:.1f}°, zoom={ptz_position[2]})")
|
|
|
|
|
+
|
|
|
with self._batch_lock:
|
|
with self._batch_lock:
|
|
|
if self._current_batch is None or self._current_batch.batch_id != batch_id:
|
|
if self._current_batch is None or self._current_batch.batch_id != batch_id:
|
|
|
- logger.warning(f"[配对保存] 批次不存在或已过期: {batch_id}")
|
|
|
|
|
|
|
+ logger.warning(f"[配对保存] 批次不存在或已过期: {batch_id}, current={self._current_batch.batch_id if self._current_batch else None}")
|
|
|
return None
|
|
return None
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
batch_dir = self.base_dir / f"batch_{batch_id}"
|
|
batch_dir = self.base_dir / f"batch_{batch_id}"
|
|
|
|
|
|
|
|
try:
|
|
try:
|
|
@@ -410,68 +415,92 @@ class PairedImageSaver:
|
|
|
def _upload_panorama_to_oss(self, batch_id: str, panorama_path: str):
|
|
def _upload_panorama_to_oss(self, batch_id: str, panorama_path: str):
|
|
|
"""上传全景图到 OSS"""
|
|
"""上传全景图到 OSS"""
|
|
|
def on_upload_complete(result):
|
|
def on_upload_complete(result):
|
|
|
- if result.success:
|
|
|
|
|
|
|
+ # 更新上传状态(即使 _current_batch 已切换也能正确记录)
|
|
|
|
|
+ self._upload_status[batch_id]['panorama'] = True
|
|
|
|
|
+ self._upload_status[batch_id]['panorama_url'] = result.oss_url
|
|
|
|
|
+ if self._current_batch and self._current_batch.batch_id == batch_id:
|
|
|
self._current_batch.panorama_oss_url = result.oss_url
|
|
self._current_batch.panorama_oss_url = result.oss_url
|
|
|
- self._upload_status[batch_id]['panorama'] = True
|
|
|
|
|
- with self._stats_lock:
|
|
|
|
|
- self._stats['oss_upload_success'] += 1
|
|
|
|
|
- logger.info(f"[OSS] 全景图上传成功: {result.oss_url}")
|
|
|
|
|
|
|
+ with self._stats_lock:
|
|
|
|
|
+ self._stats['oss_upload_success'] += 1
|
|
|
|
|
+ logger.info(f"[OSS] 全景图上传成功: {result.oss_url}")
|
|
|
|
|
+
|
|
|
|
|
+ def on_upload_error(result):
|
|
|
|
|
+ self._upload_status[batch_id]['panorama'] = False
|
|
|
|
|
+ self._upload_status[batch_id]['panorama_url'] = None
|
|
|
|
|
+ with self._stats_lock:
|
|
|
|
|
+ self._stats['oss_upload_failed'] += 1
|
|
|
|
|
+ logger.error(f"[OSS] 全景图上传失败: {result.error}")
|
|
|
|
|
+
|
|
|
|
|
+ # 包装回调,同时处理成功和失败
|
|
|
|
|
+ def on_upload_done(result):
|
|
|
|
|
+ if result.success:
|
|
|
|
|
+ on_upload_complete(result)
|
|
|
else:
|
|
else:
|
|
|
- with self._stats_lock:
|
|
|
|
|
- self._stats['oss_upload_failed'] += 1
|
|
|
|
|
- logger.error(f"[OSS] 全景图上传失败: {result.error}")
|
|
|
|
|
-
|
|
|
|
|
|
|
+ on_upload_error(result)
|
|
|
|
|
+
|
|
|
self.oss_uploader.upload_image(
|
|
self.oss_uploader.upload_image(
|
|
|
local_path=panorama_path,
|
|
local_path=panorama_path,
|
|
|
batch_id=batch_id,
|
|
batch_id=batch_id,
|
|
|
image_type='panorama',
|
|
image_type='panorama',
|
|
|
- callback=on_upload_complete
|
|
|
|
|
|
|
+ callback=on_upload_done
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
def _upload_ptz_to_oss(self, batch_id: str, person_index: int, ptz_path: str):
|
|
def _upload_ptz_to_oss(self, batch_id: str, person_index: int, ptz_path: str):
|
|
|
"""上传球机图到 OSS"""
|
|
"""上传球机图到 OSS"""
|
|
|
def on_upload_complete(result):
|
|
def on_upload_complete(result):
|
|
|
- if result.success:
|
|
|
|
|
- # 更新人员信息中的 OSS URL
|
|
|
|
|
|
|
+ # 更新上传状态(即使 _current_batch 已切换也能正确记录)
|
|
|
|
|
+ self._upload_status[batch_id]['ptz'][person_index] = result.oss_url
|
|
|
|
|
+ if self._current_batch and self._current_batch.batch_id == batch_id:
|
|
|
if person_index < len(self._current_batch.persons):
|
|
if person_index < len(self._current_batch.persons):
|
|
|
self._current_batch.persons[person_index].ptz_oss_url = result.oss_url
|
|
self._current_batch.persons[person_index].ptz_oss_url = result.oss_url
|
|
|
- self._upload_status[batch_id]['ptz'][person_index] = result.oss_url
|
|
|
|
|
- with self._stats_lock:
|
|
|
|
|
- self._stats['oss_upload_success'] += 1
|
|
|
|
|
- logger.info(f"[OSS] 球机图上传成功 (person_{person_index}): {result.oss_url}")
|
|
|
|
|
|
|
+ with self._stats_lock:
|
|
|
|
|
+ self._stats['oss_upload_success'] += 1
|
|
|
|
|
+ logger.info(f"[OSS] 球机图上传成功 (person_{person_index}): {result.oss_url}")
|
|
|
|
|
+
|
|
|
|
|
+ def on_upload_error(result):
|
|
|
|
|
+ self._upload_status[batch_id]['ptz'][person_index] = None
|
|
|
|
|
+ with self._stats_lock:
|
|
|
|
|
+ self._stats['oss_upload_failed'] += 1
|
|
|
|
|
+ logger.error(f"[OSS] 球机图上传失败 (person_{person_index}): {result.error}")
|
|
|
|
|
+
|
|
|
|
|
+ # 包装回调,同时处理成功和失败
|
|
|
|
|
+ def on_upload_done(result):
|
|
|
|
|
+ if result.success:
|
|
|
|
|
+ on_upload_complete(result)
|
|
|
else:
|
|
else:
|
|
|
- with self._stats_lock:
|
|
|
|
|
- self._stats['oss_upload_failed'] += 1
|
|
|
|
|
- logger.error(f"[OSS] 球机图上传失败 (person_{person_index}): {result.error}")
|
|
|
|
|
-
|
|
|
|
|
|
|
+ on_upload_error(result)
|
|
|
|
|
+
|
|
|
self.oss_uploader.upload_image(
|
|
self.oss_uploader.upload_image(
|
|
|
local_path=ptz_path,
|
|
local_path=ptz_path,
|
|
|
batch_id=batch_id,
|
|
batch_id=batch_id,
|
|
|
image_type='ptz',
|
|
image_type='ptz',
|
|
|
person_index=person_index,
|
|
person_index=person_index,
|
|
|
- callback=on_upload_complete
|
|
|
|
|
|
|
+ callback=on_upload_done
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
def _finalize_batch(self, batch: DetectionBatch):
|
|
def _finalize_batch(self, batch: DetectionBatch):
|
|
|
"""完成批次处理"""
|
|
"""完成批次处理"""
|
|
|
batch.completed = True
|
|
batch.completed = True
|
|
|
-
|
|
|
|
|
- # 等待 OSS 上传完成(最多等待5秒)
|
|
|
|
|
|
|
+
|
|
|
|
|
+ # 等待 OSS 上传完成(最多等待10秒)
|
|
|
if self.enable_oss and batch.batch_id in self._upload_status:
|
|
if self.enable_oss and batch.batch_id in self._upload_status:
|
|
|
wait_start = time.time()
|
|
wait_start = time.time()
|
|
|
- while time.time() - wait_start < 5.0:
|
|
|
|
|
|
|
+ max_wait = 10.0 # 增加等待时间
|
|
|
|
|
+ while time.time() - wait_start < max_wait:
|
|
|
status = self._upload_status[batch.batch_id]
|
|
status = self._upload_status[batch.batch_id]
|
|
|
- # 检查全景图是否上传完成
|
|
|
|
|
- panorama_done = status.get('panorama', False) or not batch.panorama_path
|
|
|
|
|
|
|
+ # 检查全景图是否上传完成(优先检查 _upload_status 中的状态)
|
|
|
|
|
+ panorama_url = status.get('panorama_url')
|
|
|
|
|
+ panorama_done = panorama_url is not None or not batch.panorama_path
|
|
|
# 检查所有球机图是否上传完成
|
|
# 检查所有球机图是否上传完成
|
|
|
|
|
+ ptz_status = status.get('ptz', {})
|
|
|
ptz_done = all(
|
|
ptz_done = all(
|
|
|
- idx in status.get('ptz', {})
|
|
|
|
|
|
|
+ ptz_status.get(idx) is not None
|
|
|
for idx, person in enumerate(batch.persons)
|
|
for idx, person in enumerate(batch.persons)
|
|
|
if person.ptz_image_saved
|
|
if person.ptz_image_saved
|
|
|
)
|
|
)
|
|
|
if panorama_done and ptz_done:
|
|
if panorama_done and ptz_done:
|
|
|
break
|
|
break
|
|
|
- time.sleep(0.1)
|
|
|
|
|
|
|
+ time.sleep(0.2)
|
|
|
|
|
|
|
|
# 创建 batch_info.json 文件
|
|
# 创建 batch_info.json 文件
|
|
|
try:
|
|
try:
|
|
@@ -514,13 +543,22 @@ class PairedImageSaver:
|
|
|
def _build_batch_info_json(self, batch: DetectionBatch) -> Dict:
|
|
def _build_batch_info_json(self, batch: DetectionBatch) -> Dict:
|
|
|
"""
|
|
"""
|
|
|
构建 batch_info.json 数据结构
|
|
构建 batch_info.json 数据结构
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
Returns:
|
|
Returns:
|
|
|
Dict: 批次信息字典
|
|
Dict: 批次信息字典
|
|
|
"""
|
|
"""
|
|
|
|
|
+ # 从上传状态获取最新的 OSS URL(异步回调可能已更新)
|
|
|
|
|
+ upload_status = self._upload_status.get(batch.batch_id, {})
|
|
|
|
|
+
|
|
|
|
|
+ # 获取全景图 OSS URL(优先使用回调更新的状态)
|
|
|
|
|
+ panorama_oss_url = upload_status.get('panorama_url', batch.panorama_oss_url)
|
|
|
|
|
+
|
|
|
# 人员信息列表
|
|
# 人员信息列表
|
|
|
persons_list = []
|
|
persons_list = []
|
|
|
for person in batch.persons:
|
|
for person in batch.persons:
|
|
|
|
|
+ # 获取球机图 OSS URL(优先使用回调更新的状态)
|
|
|
|
|
+ ptz_oss_url = upload_status.get('ptz', {}).get(person.person_index, person.ptz_oss_url)
|
|
|
|
|
+
|
|
|
person_data = {
|
|
person_data = {
|
|
|
'person_index': person.person_index,
|
|
'person_index': person.person_index,
|
|
|
'position': {
|
|
'position': {
|
|
@@ -547,10 +585,10 @@ class PairedImageSaver:
|
|
|
} if person.ptz_bbox else None,
|
|
} if person.ptz_bbox else None,
|
|
|
'ptz_image_saved': person.ptz_image_saved,
|
|
'ptz_image_saved': person.ptz_image_saved,
|
|
|
'ptz_image_path': person.ptz_image_path,
|
|
'ptz_image_path': person.ptz_image_path,
|
|
|
- 'ptz_oss_url': person.ptz_oss_url
|
|
|
|
|
|
|
+ 'ptz_oss_url': ptz_oss_url
|
|
|
}
|
|
}
|
|
|
persons_list.append(person_data)
|
|
persons_list.append(person_data)
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
# 构建完整批次信息
|
|
# 构建完整批次信息
|
|
|
batch_info = {
|
|
batch_info = {
|
|
|
'batch_id': batch.batch_id,
|
|
'batch_id': batch.batch_id,
|
|
@@ -562,15 +600,18 @@ class PairedImageSaver:
|
|
|
'ptz_images_count': batch.ptz_images_count,
|
|
'ptz_images_count': batch.ptz_images_count,
|
|
|
'panorama': {
|
|
'panorama': {
|
|
|
'local_path': batch.panorama_path,
|
|
'local_path': batch.panorama_path,
|
|
|
- 'oss_url': batch.panorama_oss_url
|
|
|
|
|
|
|
+ 'oss_url': panorama_oss_url
|
|
|
},
|
|
},
|
|
|
'persons': persons_list,
|
|
'persons': persons_list,
|
|
|
'upload_status': {
|
|
'upload_status': {
|
|
|
- 'panorama_uploaded': batch.panorama_oss_url is not None,
|
|
|
|
|
- 'all_ptz_uploaded': all(p.ptz_oss_url is not None for p in batch.persons if p.ptz_image_saved)
|
|
|
|
|
|
|
+ 'panorama_uploaded': panorama_oss_url is not None,
|
|
|
|
|
+ 'all_ptz_uploaded': all(
|
|
|
|
|
+ upload_status.get('ptz', {}).get(p.person_index) is not None
|
|
|
|
|
+ for p in batch.persons if p.ptz_image_saved
|
|
|
|
|
+ )
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
return batch_info
|
|
return batch_info
|
|
|
|
|
|
|
|
def _save_batch_info_txt(self, batch: DetectionBatch, txt_path: Path):
|
|
def _save_batch_info_txt(self, batch: DetectionBatch, txt_path: Path):
|