upload_cvat.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. #!/usr/bin/env python3
  2. """
  3. CVAT自动上传脚本
  4. ================
  5. 将pipeline生成的ZIP数据集自动上传到CVAT标注平台。
  6. 用法:
  7. python upload_cvat.py # 上传所有机位
  8. python upload_cvat.py --cameras cam01 cam02 # 只上传指定机位
  9. """
  10. import os
  11. import sys
  12. import time
  13. import zipfile
  14. import argparse
  15. import requests
  16. from pathlib import Path
  17. from config import *
  18. class CVATClient:
  19. def __init__(self, url, username, password):
  20. self.url = url.rstrip('/')
  21. self.session = requests.Session()
  22. self.login(username, password)
  23. def login(self, username, password):
  24. self.session.get(f'{self.url}/api/v1/auth/login')
  25. csrf = self.session.cookies.get('csrftoken', '')
  26. resp = self.session.post(f'{self.url}/api/v1/auth/login', json={
  27. 'username': username, 'password': password
  28. }, headers={'X-CSRFToken': csrf})
  29. resp.raise_for_status()
  30. self.csrf = self.session.cookies.get('csrftoken', '')
  31. print(f"Login OK")
  32. def _headers(self):
  33. return {'X-CSRFToken': self.csrf}
  34. def get_project_id(self, name):
  35. resp = self.session.get(f'{self.url}/api/v1/projects', params={'search': name})
  36. resp.raise_for_status()
  37. for p in resp.json()['results']:
  38. if p['name'] == name:
  39. return p['id']
  40. raise ValueError(f"Project '{name}' not found")
  41. def get_label_map(self, project_id):
  42. resp = self.session.get(f'{self.url}/api/v1/projects/{project_id}')
  43. resp.raise_for_status()
  44. labels = resp.json().get('labels', [])
  45. return {l['name']: l['id'] for l in labels}
  46. def list_tasks(self, project_id):
  47. resp = self.session.get(f'{self.url}/api/v1/tasks', params={'project': project_id})
  48. resp.raise_for_status()
  49. return resp.json()['results']
  50. def create_task(self, project_id, task_name):
  51. resp = self.session.post(f'{self.url}/api/v1/tasks', json={
  52. 'name': task_name, 'project_id': project_id
  53. }, headers=self._headers())
  54. resp.raise_for_status()
  55. task = resp.json()
  56. print(f" Created task: {task['id']} - {task_name}")
  57. return task['id']
  58. def delete_task(self, task_id):
  59. resp = self.session.delete(f'{self.url}/api/v1/tasks/{task_id}', headers=self._headers())
  60. return resp.status_code
  61. def upload_data(self, task_id, zip_path):
  62. with open(zip_path, 'rb') as f:
  63. resp = self.session.post(
  64. f'{self.url}/api/v1/tasks/{task_id}/data',
  65. files={'client_files[0]': (zip_path.name, f, 'application/zip')},
  66. data={'image_quality': '100'},
  67. headers=self._headers()
  68. )
  69. resp.raise_for_status()
  70. print(f" Uploaded data")
  71. return resp.json()
  72. def upload_annotations(self, task_id, zip_path, class_names, label_map):
  73. shapes = []
  74. with zipfile.ZipFile(zip_path) as zf:
  75. ann_files = sorted([n for n in zf.namelist() if n.endswith('.txt') and 'obj_train_data' in n])
  76. for i, ann_file in enumerate(ann_files):
  77. with zf.open(ann_file) as f:
  78. for line in f:
  79. parts = line.decode().strip().split()
  80. if len(parts) >= 5:
  81. cls_id = int(parts[0])
  82. if cls_id not in class_names:
  83. continue
  84. label_name = class_names[cls_id]
  85. if label_name not in label_map:
  86. continue
  87. xc, yc, w, h = float(parts[1]), float(parts[2]), float(parts[3]), float(parts[4])
  88. shapes.append({
  89. 'frame': i,
  90. 'label_id': label_map[label_name],
  91. 'type': 'rectangle',
  92. 'occluded': False,
  93. 'points': [
  94. round((xc - w/2) * IMG_WIDTH, 2),
  95. round((yc - h/2) * IMG_HEIGHT, 2),
  96. round((xc + w/2) * IMG_WIDTH, 2),
  97. round((yc + h/2) * IMG_HEIGHT, 2)
  98. ],
  99. 'group': 0,
  100. 'attributes': []
  101. })
  102. if not shapes:
  103. print(f" No annotations to upload")
  104. return
  105. body = {'version': 0, 'tags': [], 'shapes': shapes, 'tracks': []}
  106. resp = self.session.put(
  107. f'{self.url}/api/v1/tasks/{task_id}/annotations',
  108. json=body, headers=self._headers()
  109. )
  110. if resp.status_code == 200:
  111. print(f" Uploaded {len(shapes)} annotations")
  112. else:
  113. print(f" Warning: {resp.status_code} {resp.text[:200]}")
  114. def main():
  115. parser = argparse.ArgumentParser(description='Upload dataset to CVAT')
  116. parser.add_argument('--cameras', nargs='+', help='Specific cameras to upload (e.g. cam01 cam02)')
  117. parser.add_argument('--delete-test', action='store_true', help='Delete test tasks (czzt_*_reinfer)')
  118. args = parser.parse_args()
  119. print("Connecting to CVAT...")
  120. client = CVATClient(CVAT_URL, CVAT_USERNAME, CVAT_PASSWORD)
  121. project_id = client.get_project_id(CVAT_PROJECT_NAME)
  122. label_map = client.get_label_map(project_id)
  123. class_names = {0: 'hoist', 1: 'ear', 2: 'ready'}
  124. print(f"Project: {CVAT_PROJECT_NAME} (id={project_id}), labels: {label_map}")
  125. # 删除测试任务
  126. if args.delete_test:
  127. tasks = client.list_tasks(project_id)
  128. for t in tasks:
  129. if '_reinfer' in t['name']:
  130. print(f" Deleting test task: {t['id']} - {t['name']}")
  131. client.delete_task(t['id'])
  132. time.sleep(0.5)
  133. # 确定要上传的机位
  134. if args.cameras:
  135. zips = [CVAT_ZIP_DIR / f'{cam}.zip' for cam in args.cameras]
  136. zips = [z for z in zips if z.exists()]
  137. else:
  138. zips = sorted(CVAT_ZIP_DIR.glob('*.zip'))
  139. print(f"\nFound {len(zips)} zips to upload\n")
  140. for zip_path in zips:
  141. cam_name = zip_path.stem
  142. print(f"\n{'='*40}")
  143. print(f"Processing {cam_name}...")
  144. print(f"{'='*40}")
  145. task_id = client.create_task(project_id, f'czzt_{cam_name}')
  146. client.upload_data(task_id, zip_path)
  147. time.sleep(1)
  148. client.upload_annotations(task_id, zip_path, class_names, label_map)
  149. time.sleep(0.5)
  150. print(f"\n{'='*40}")
  151. print("Done!")
  152. if __name__ == '__main__':
  153. main()