Towards-Realtime-MOT/live_track.ipynb

416 lines
64 KiB
Text
Raw Normal View History

{
"cells": [
{
"cell_type": "code",
"execution_count": 47,
"id": "d1489f9f-328c-4812-9cdb-0a2dee44ae88",
"metadata": {},
"outputs": [],
"source": [
"import cv2\n",
"import argparse\n",
"import logging\n",
"import os\n",
"from tqdm.auto import tqdm\n",
"from pathlib import Path\n",
"\n",
"import numpy as np\n",
"from tracker.multitracker import JDETracker\n",
"from utils.timer import Timer\n",
"\n",
"import matplotlib.pyplot as plt\n",
"from tracker.basetrack import TrackState\n",
"import torch\n",
"import pickle\n",
"\n",
"from utils import visualization as vis"
]
},
{
"cell_type": "code",
"execution_count": 48,
"id": "edec1b34-64ad-4610-856a-68d886a45142",
"metadata": {},
"outputs": [],
"source": [
"logger = logging.getLogger(__name__)"
]
},
{
"cell_type": "code",
"execution_count": 49,
"id": "010bf567-8845-46d4-8500-883efce2d010",
"metadata": {},
"outputs": [],
"source": [
"# path of video to use. Replace with integer to use /dev/video device\n",
"# that is, the webcam.\n",
"video_path = Path('OUT/embedding_test/V003.seq')\n",
"projector_pcl = Path('OUT/embedding_test/reducer_pca.pcl')"
]
},
{
"cell_type": "code",
"execution_count": 50,
"id": "8a413424-13c4-4bdc-825a-0aa6164e89e2",
"metadata": {},
"outputs": [],
"source": [
"from utils.parse_config import parse_model_cfg\n",
"from utils.utils import mkdir_if_missing\n",
"import utils.datasets as datasets\n",
"from utils.log import logger as trmlog # we need to override this...\n",
"\n",
"\n",
"trmlog.setLevel(logging.INFO)"
]
},
{
"cell_type": "code",
"execution_count": 51,
"id": "7b291b67-93ad-4b51-934a-dbaf095f7704",
"metadata": {},
"outputs": [],
"source": [
"# load options; quick'n'dirty copy from track.py (as the Namespace object is used in the multitracker)\n",
"parser = argparse.ArgumentParser(prog='visualise_embeddings.py')\n",
"parser.add_argument('--cfg', type=str, default='cfg/yolov3_1088x608.cfg', help='cfg file path')\n",
"parser.add_argument('--weights', type=str, default='jde.1088x608.uncertainty.pt', help='path to weights file')\n",
"parser.add_argument('--iou-thres', type=float, default=0.5, help='iou threshold required to qualify as detected')\n",
"parser.add_argument('--conf-thres', type=float, default=0.5, help='object confidence threshold')\n",
"parser.add_argument('--nms-thres', type=float, default=0.4, help='iou threshold for non-maximum suppression')\n",
"parser.add_argument('--min-box-area', type=float, default=200, help='filter out tiny boxes')\n",
"parser.add_argument('--track-buffer', type=int, default=30, help='tracking buffer')\n",
"parser.add_argument('--dataset-dir', type=str, default=\"/datasets\", help='Path to directory with datasets')\n",
"parser.add_argument('--experiment-name', type=str, default=\"embedding_test\", help=\"name to prefix output directory with\")\n",
"parser.add_argument('--output-dir', type=str, default=\"./OUT\", help=\"directory for results\")\n",
" \n",
"# we're running in notebook, so default to empty\n",
"opt = parser.parse_args([])\n",
"\n",
"logger.setLevel(logging.INFO)\n",
"result_path = os.path.join(opt.output_dir, opt.experiment_name)\n",
"result_frame_path = os.path.join(result_path, 'track-test')\n",
"mkdir_if_missing(result_path)\n",
"mkdir_if_missing(result_frame_path)\n",
"data_type = 'mot'\n",
"\n",
"# Read config\n",
"cfg_dict = parse_model_cfg(opt.cfg)\n",
"# set img_size in opt, so it is passed on to JDETracker\n",
"opt.img_size = [int(cfg_dict[0]['width']), int(cfg_dict[0]['height'])]"
]
},
{
"cell_type": "code",
"execution_count": 52,
"id": "648faf4b-d692-473a-a99d-06b50a2e2261",
"metadata": {},
"outputs": [],
"source": [
"import queue, threading\n",
"\n",
"# bufferless VideoCapture (by Ulrich Stern: https://stackoverflow.com/a/54577746)\n",
"# Usefull when using Webcam with higher fps than the script can process it.\n",
"class BufferlessVideoCapture:\n",
"\n",
" def __init__(self, name):\n",
" self.cap = cv2.VideoCapture(name)\n",
" self.q = queue.Queue()\n",
" t = threading.Thread(target=self._reader)\n",
" t.daemon = True\n",
" t.start()\n",
"\n",
" # read frames as soon as they are available, keeping only most recent one\n",
" def _reader(self):\n",
" while True:\n",
" ret, frame = self.cap.read()\n",
" if not ret:\n",
" break\n",
" if not self.q.empty():\n",
" try:\n",
" self.q.get_nowait() # discard previous (unprocessed) frame\n",
" except queue.Empty:\n",
" pass\n",
" self.q.put(frame)\n",
"\n",
" def read(self):\n",
" return self.q.get()"
]
},
{
"cell_type": "code",
"execution_count": 53,
"id": "a28ef404-2031-43cf-aeb1-357aa1be0934",
"metadata": {},
"outputs": [],
"source": [
"\n",
"with projector_pcl.open('rb') as fp:\n",
" reducer = pickle.load(fp)"
]
},
{
"cell_type": "markdown",
"id": "c433bdc3-e3bb-4d53-8e41-6e11791d8ac2",
"metadata": {},
"source": [
"Load video file and get it's properties. Use that to calculate the dimension to fit the loaded model"
]
},
{
"cell_type": "code",
"execution_count": 59,
"id": "642c1953-6f33-4a22-a223-797755ece204",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"video 640 x 480\n",
"Scale to 810 x 608\n"
]
}
],
"source": [
"stream = cv2.VideoCapture(str(video_path))\n",
"frame_rate = stream.get(cv2.CAP_PROP_FPS)\n",
"vw = int(stream.get(cv2.CAP_PROP_FRAME_WIDTH))\n",
"vh = int(stream.get(cv2.CAP_PROP_FRAME_HEIGHT))\n",
"\n",
"\n",
"print(\"video\", vw, \"x\", vh)\n",
"\n",
"# get_size (from datasets.py)\n",
"aspect_w, aspect_h = float(opt.img_size[0]) / vw, float(opt.img_size[1]) / vh\n",
"aspect = min(aspect_w, aspect_h)\n",
"w, h = int(vw *aspect), int(vh*aspect)\n",
"print(\"Scale to\", w, \"x\", h)\n"
]
},
{
"cell_type": "code",
"execution_count": 60,
"id": "be84553e-4309-46e5-9d9a-3075c20c0463",
"metadata": {},
"outputs": [],
"source": [
"umap_pcl = os.path.join(result_path, 'reducer_umap.pcl')\n",
"pca_pcl = os.path.join(result_path, 'reducer_pca.pcl')"
]
},
{
"cell_type": "code",
"execution_count": 61,
"id": "c623aa17-5ce2-4948-9adf-d4c9a6d1ccd2",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(-1.0, 1.0)"
]
},
"execution_count": 61,
"metadata": {},
"output_type": "execute_result"
},
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAABpQAAANkCAYAAAC588kTAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAB7CAAAewgFu0HU+AACZQklEQVR4nOz9e5jXdZ0//t+R48DQ4AGLZtBNSA6eVygQE+lgfRr7eKqktfUQqO22hX2gNGvXpc9ubAu0UmrlIdRskw4eSCzNGnEhCbBL7ROHFdIV0AsDARkcjr5/f/Dj/RVhXswMMyJwu13X47qezOv5fj4fb50/3u/rPq/nq12SUgAAAAAAAKARh+zrBgAAAAAAAHhrEygBAAAAAABQSKAEAAAAAABAIYESAAAAAAAAhQRKAAAAAAAAFBIoAQAAAAAAUEigBAAAAAAAQCGBEgAAAAAAAIUESgAAAAAAABQSKAEAAAAAAFBIoAQAAAAAAEAhgRIAAAAAAACFBEoAAAAAAAAUEigBAAAAAABQSKAEAAAAAABAIYESAAAAAAAAhQRKAAAAAAAAFBIoAQAAAAAAUEigBAAAAAAAQCGBEgAAAAAAAIXaNFDq2bNnamtrM378+Dz44IP5y1/+klKplFKplKlTp7bJniNHjsxDDz2UF198MQ0NDXnuuefywx/+MEOGDGmT/QAAgIPT/vp9p6KiIl/60pcyd+7crF69OvX19Vm4cGEmTZqUo446qk36BgAADgyltqoiU6dObdW9unTpUnrggQca3W/r1q2lf/qnf2qz96qUUkoppZQ6uGp//L7Tp0+f0uLFixtdZ+3ataXa2tp9/t9WKaWUUkop9darN+3Iu//5n//JQw891Gbr/+AHP0htbW2S5Le//W3OOeecDB48OJ/5zGeyZMmStG/fPuPHj8/ll1/eZj0AAAAHp/3h+05lZWVmzJiRY489Nkly88035/3vf3+GDh2aa6+9NuvXr09VVVWmTZuWk046qc3eCwAAsP9qs7Tqn//5n0u1tbWlI488spSkdPTRR7fJX+yNGDGivO79999fOuSQQ3a6fvjhh5eee+65UqlUKr388sulHj167PMkTymllFJKKbV/1/72fWf8+PHldcaNG7fL9aFDh5Y2b95cKpVKpbq6un3+31cppZRSSin1lqs3b7O2+oI1Y8aMUqlUKm3evLlUXV292zkXXnhh4ZcnpZRSSimllNqbeit/3+nQoUNpzZo1pVKpVPrTn/5Uateu3W7X+e53v1teZ9CgQfv8v6lSSimllFLqrVNv2pF3baWysjIf+MAHkiSPPPJIVqxYsdt599xzT9atW5ckOe+88960/gAAAFqqtb7vjBgxIj169EiS3HHHHSmVSrtd5/bbby+PfW8CAABeb78PlAYPHpzOnTsnSWbOnNnovC1btmTOnDnl13To0OFN6Q8AAKClWuv7zumnn14eF60zf/78bNiwIUkybNiwFvcNAAAcePb7QGngwIHl8aJFiwrn7rjesWPHvPvd727TvgAAAPZWa33faeo627Zty5IlS5IkAwYMaHa/AADAgWu/v02npqamPF6+fHnh3GXLlpXHvXv3zsKFC5u8T3V1deH1Tp06ZcCAAXnppZfy0ksvZdu2bU1eGwAA9jft27fPkUcemSR5+umns3nz5n3c0YGptb7v7Finvr6+fDRe0TonnXRSjjzyyHTq1KnJ/299ZwIAgJ0daN+b9vtAqXv37uVxfX194dwdRzck288ib449fXkDAICD1eDBgzN//vx93cYBqbW+7+xYZ09r7G6dl19+uUm9+s4EAACNOxC+N+33R9516dKlPN5Turdp06byuKKios16AgAAaA2t9X1nxzpN+YtI35sAAIDd2e/vUNq4cWN53KlTp8K5Ox5mmyQNDQ3N2uf1R000dv31D8F98cUXm7U+AADsT3r16pV58+YlSV566aV93M2Bq7W+7+xYZ09r7GmdIr4zAQDAzg607037faC0fv368nhPx9h169atPG7KUQ+vt2LFiibPffHFF5s1HwAA9meehdN2Wuv7zo51mnL0d0u/N/nOBAAAjTsQvjft90fevf6c7j39RVzv3r3L49c/sBYAAOCtqLW+7+xYp7KyMlVVVU1a56WXXtrvHxoMAAC0nv0+UFqwYEF53L9//8K5O65v2bIlzzzzTJv2BQAAsLda6/tOU9dp3759+vTpkyRZuHBhs/sFAAAOXPt9oDRv3rzyQ2OHDx/e6LyOHTtmyJAh5dds3br1TekPAACgpVrr+86sWbPK46J1Bg0aVD4Wb/bs2S3uGwAAOPDs94FSfX19fvOb3yRJPvjBD6a6unq3884///zy0Q733nvvm9YfAABAS7XW951HH300a9euTZJccsklje536aWXlse+NwEAAK/3lg+ULrnkkpRKpZRKpVx33XW7nTNp0qQk2/8q78Ybb8whh+z8tg4//PB885vfTJKsWbMmt956a9s2DQAA0ARv1vedLVu25Nvf/naSZODAgRk3btwuc4YMGZJRo0Yl2R5AzZ8/v+VvDAAAOOB0aMvFhw0blr59+5b/fcQRR5THffv23eUv4+64444W7VNXV5cf//jH+dSnPpVzzjknv/71r3P99dfnhRdeyAknnJCvfvWrOfroo5MkV199dfkv8wAAAFpqf/u+M3HixFx44YXp169fJk6cmL59++buu+9OQ0NDRowYkWuvvTYdO3bMq6++mquuuqpFvQIAAAe2UlvV1KlTS82xuzUuueSS8vXrrruu0b26dOlSeuCBBxpde+vWrYWv39uqrq4u71VdXd1m+yillFJKKfVWKJ9/98/vO3369CktXry40XXWrl1bqq2t9TujlFJKKaVUK9SB9hn4LX/kXVNt3LgxZ599dv7mb/4mDz/8cFauXJlNmzbl+eefz49+9KOcfvrpGT9+/L5uEwAAoNla6/vO0qVLc8opp+TLX/5y5s2blzVr1mTDhg1ZtGhRvvWtb+XEE0/MjBkz3oR3BAAA7G/aZXuyxF6qrq7O8uXLkyQ1NTVZsWLFPu4IAADajs+/NJffGQAADjYH2mfgA+YOJQAAAAAAANqGQAkAAAAAAIBCAiUAAAAAAAAKCZQAAAAAAAAoJFACAAAAAACgkEAJAAAAAACAQgIlAAAAAAAACgmUAAAAAAAAKCRQAgAAAAAAoJBACQAAAAAAgEICJQAAAAAAAAoJlAAAAAAAACgkUAIAAAAAAKCQQAkAAAAAAIBCAiUAAAAAAAAKCZQAAAAAAAAoJFACAAAAAACgkEAJAAAAAACAQgIlAAAAAAAACgmUAAAAAAAAKCRQAgAAAAAAoJBACQAAAAAAgEICJQAAAAAAAAoJlAAAAAAAACgkUAIAAAAAAKCQQAkAAAAAAIBCAiUAAAAAAAAKCZQAAAAAAAAoJFACAAAAAACgkEAJAAAAAACAQgIlAAAAAAAACgmUAAAAAAAAKCRQAgAAAAAAoJBACQAAAAAAgEICJQAAAAAAAAoJlAAAAAAAACgkUAIAAAAAAKCQQAkAAAAAAIBCAiUAAAAAAAAKCZQAAAAAAAAoJFACAAAAAACgkEAJAAAAAACAQgIlAAAAAAAACgmUAAAAAAAAKCRQAgAAAAAAoJBACQAAAAAAgEICJQAAAAAAAAoJlAAAAAAAACgkUAIAAAAAAKCQQAkAAAAAAIBCAiUAAAAAAAAKCZQAAAAAAAAoJFACAAAAAACgkEAJAAAAAACAQgIlAAAAAAAACgmUAAAAAAAAKCRQAgAAAAAAoJBACQAAAAAAgEICJQAAAAAAAAoJlAAAAAAAACgkUAIAAAAAAKCQQAkAAAAAAIBCAiUAAAAAAAAKCZQAAAAAAAAoJFACAAAAAACgkEAJAAAAAACAQgIlAAAAAAAACgmUAAAAAAAAKCRQAgAAAAAAoJBACQAAAAAAgEICJQAAAAAAAAoJlAAAAAAAACgkUAIAAAAAAKCQQAkAAAAAAIBCAiUAAIC9dNRRR2XSpElZuHBh6uvrs3r16sydOzfjxo1LRUVFi9c9+uijUyqVmlXPPvvsbteqq6tr8hoAAAB
"text/plain": [
"<Figure size 2000x1000 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"# matplotlib in interactive mode so the scatter can be updated.\n",
"\n",
"plt.style.use('dark_background')\n",
"\n",
"fig, ax = plt.subplots(1, 2, figsize=(10, 5), dpi=200)\n",
"x, y = [],[]\n",
"scatter = ax[1].scatter(x,y)\n",
"# scatter = ax.plot([],[],label='toto',ms=10,color='k',marker='o',ls='')\n",
"plt.xlim(-1,1)\n",
"plt.ylim(-1,1)\n"
]
},
{
"cell_type": "code",
"execution_count": 62,
"id": "e8952235-7e56-4606-858a-a9165b967726",
"metadata": {},
"outputs": [],
"source": [
"# init tracker\n",
"tracker = JDETracker(opt, frame_rate=frame_rate)\n",
"timer = Timer()\n",
"results = []\n",
"frame_id = -1\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f9cc6fd1-b9c2-4303-a21c-a193c6045526",
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "3ec1f9b647cf40edbf662db14ac36bcb",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"0it [00:00, ?it/s]"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"with tqdm() as pbar:\n",
" while True:\n",
" timer.tic()\n",
" frame_id += 1\n",
"\n",
" pbar.update(1)\n",
"\n",
" ret, frame = stream.read()\n",
" # scale down/up frame to fit tracker\n",
" frame = cv2.resize(frame, (w, h))\n",
"\n",
"\n",
" # letterbox as to have a constant size for the model\n",
" img, _, _, _ = datasets.letterbox(frame, height=opt.img_size[1], width=opt.img_size[0])\n",
"\n",
" # Normalize RGB\n",
" img = img[:, :, ::-1].transpose(2, 0, 1)\n",
" img = np.ascontiguousarray(img, dtype=np.float32)\n",
" img /= 255.0\n",
"\n",
" # send frame to GPU\n",
" blob = torch.from_numpy(img).cuda().unsqueeze(0)\n",
"\n",
" # online targets: all targets that are not timed out\n",
" # frame_embeddings: the embeddings of objects visible only in the current frame\n",
" online_targets, frame_embeddings = tracker.update(blob, frame)\n",
"\n",
" # all relevant tracks from the tracker directly (excludes the 'removed' ones)\n",
" # thus also accessing its those that it has stored from previous frames\n",
" all_stracks = tracker.tracked_stracks + tracker.lost_stracks\n",
"\n",
" # project them to 2D using PCA (see visualise_embeddings.ipynb)\n",
" if len(all_stracks):\n",
" projections = reducer.transform([strack.smooth_feat for strack in all_stracks])\n",
" else:\n",
" projections = []\n",
"\n",
" timer.toc()\n",
"\n",
"\n",
" online_tlwhs = []\n",
" online_ids = []\n",
" for t in online_targets:\n",
" tlwh = t.tlwh\n",
" tid = t.track_id\n",
" vertical = tlwh[2] / tlwh[3] > 1.6\n",
" if tlwh[2] * tlwh[3] > opt.min_box_area and not vertical:\n",
" online_tlwhs.append(tlwh)\n",
" online_ids.append(tid)\n",
" # visualise results for single frame\n",
" online_im = vis.plot_tracking(frame, online_tlwhs, online_ids, frame_id=frame_id,\n",
" fps=1. / timer.average_time)\n",
" \n",
" # redraw the canvas\n",
" # fig.canvas.draw()\n",
" # ax[0].clear()\n",
" # ax[1].clear()\n",
" \n",
" ax[0].imshow(online_im)\n",
" # cv2.imwrite(result_frame_path + f\"/{frame_id:04d}-live.jpg\", online_im)\n",
"\n",
"\n",
" # scatter.set_data(projections[:,0],projections[:,1])\n",
" if len(projections):\n",
" scatter.set_offsets(projections)\n",
" scatter.set_color([(0,0,1,0.5) if strack.state == TrackState.Lost else (0,0,1,1) for strack in all_stracks])\n",
" else:\n",
" scatter = ax[1].scatter([],[])\n",
" pbar.set_description('todo: empty axes')\n",
" # print()\n",
"\n",
" # plot = np.fromstring(fig.canvas.tostring_rgb(), dtype=np.uint8, sep='')\n",
" # plot = plot.reshape(fig.canvas.get_width_height()[::-1] + (3,))\n",
"\n",
" # # img is rgb, convert to opencv's default bgr\n",
" # plot = cv2.cvtColor(plot,cv2.COLOR_RGB2BGR)\n",
"\n",
"\n",
"\n",
"\n",
" fig.savefig(result_frame_path + f\"/{frame_id:04d}.png\")\n",
"\n",
" # fig.canvas.draw_idle()\n",
" # plt.pause(0.1)\n",
" # if cv2.waitKey(1) & 0xFF == ord('q'): # wait for 1 millisecond\n",
" # break"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8f21e642-2320-4a7e-a0ea-af32e8d1a182",
"metadata": {},
"outputs": [],
"source": [
"! ffmpeg -i OUT/embedding_test/track-test/%04d.png -c:v libx264 -crf 10 OUT/embedding_test/track-test.mp4"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "94d6e0cb-4d32-4d18-8b18-4ecfcc67a634",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"id": "30953716-c267-401f-ad30-51b2a139bfc2",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "trm",
"language": "python",
"name": "trm"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.8"
}
},
"nbformat": 4,
"nbformat_minor": 5
}