| @@ -1,9 +1,9 @@ | |||
| nodes: | |||
| - id: camera | |||
| build: pip install ../../node-hub/opencv-video-capture | |||
| build: pip install -e ../../node-hub/opencv-video-capture | |||
| path: opencv-video-capture | |||
| inputs: | |||
| tick: dora/timer/millis/16 | |||
| tick: dora/timer/millis/20 | |||
| outputs: | |||
| - image | |||
| env: | |||
| @@ -12,7 +12,7 @@ nodes: | |||
| IMAGE_HEIGHT: 480 | |||
| - id: object-detection | |||
| build: pip install ../../node-hub/ultralytics-yolo | |||
| build: pip install -e ../../node-hub/ultralytics-yolo | |||
| path: ultralytics-yolo | |||
| inputs: | |||
| image: | |||
| @@ -24,7 +24,7 @@ nodes: | |||
| MODEL: yolov8n.pt | |||
| - id: plot | |||
| build: pip install ../../node-hub/opencv-plot | |||
| build: pip install -e ../../node-hub/opencv-plot | |||
| path: opencv-plot | |||
| inputs: | |||
| image: | |||
| @@ -5,17 +5,17 @@ This node is used to plot a text and a list of bbox on a base image (ideal for o | |||
| # YAML | |||
| ```yaml | |||
| - id: opencv-plot | |||
| build: pip install ../../node-hub/opencv-plot | |||
| path: opencv-plot | |||
| inputs: | |||
| # image: Arrow array of size 1 containing the base image | |||
| # bbox: Arrow array of bbox | |||
| # text: Arrow array of size 1 containing the text to be plotted | |||
| env: | |||
| PLOT_WIDTH: 640 # optional, default is image input width | |||
| PLOT_HEIGHT: 480 # optional, default is image input height | |||
| - id: opencv-plot | |||
| build: pip install ../../node-hub/opencv-plot | |||
| path: opencv-plot | |||
| inputs: | |||
| # image: Arrow array of size 1 containing the base image | |||
| # bbox: Arrow array of bbox | |||
| # text: Arrow array of size 1 containing the text to be plotted | |||
| env: | |||
| PLOT_WIDTH: 640 # optional, default is image input width | |||
| PLOT_HEIGHT: 480 # optional, default is image input height | |||
| ``` | |||
| # Inputs | |||
| @@ -26,19 +26,18 @@ This node is used to plot a text and a list of bbox on a base image (ideal for o | |||
| image: { | |||
| "width": np.uint32, | |||
| "height": np.uint32, | |||
| "channels": np.uint8, | |||
| "encoding": bytes, | |||
| "data": np.array # flattened image data | |||
| } | |||
| encoded_image = pa.array([image]) | |||
| decoded_image = { | |||
| "width": np.uint32(encoded_image[0]["width"].as_py()), | |||
| "height": np.uint32(encoded_image[0]["height"].as_py()), | |||
| "channels": np.uint8(encoded_image[0]["channels"].as_py()), | |||
| "width": np.uint32(encoded_image[0]["width"]), | |||
| "height": np.uint32(encoded_image[0]["height"]), | |||
| "encoding": encoded_image[0]["encoding"].as_py(), | |||
| "data": encoded_image[0]["data"].values.to_numpy().astype(np.uint8) | |||
| } | |||
| } | |||
| ``` | |||
| - `bbox`: an arrow array containing the bounding boxes, confidence scores, and class names of the detected objects | |||
| @@ -68,7 +67,7 @@ text: str | |||
| encoded_text = pa.array([text]) | |||
| decoded_text = encoded_text[0].as_py() | |||
| ``` | |||
| ``` | |||
| ## License | |||
| @@ -130,11 +130,20 @@ def main(): | |||
| if event_id == "image": | |||
| arrow_image = event["value"][0] | |||
| encoding = arrow_image["encoding"].as_py() | |||
| if encoding == "bgr8": | |||
| channels = 3 | |||
| storage_type = np.uint8 | |||
| else: | |||
| raise Exception(f"Unsupported image encoding: {encoding}") | |||
| image = { | |||
| "width": np.uint32(arrow_image["width"].as_py()), | |||
| "height": np.uint32(arrow_image["height"].as_py()), | |||
| "channels": np.uint8(arrow_image["channels"].as_py()), | |||
| "data": arrow_image["data"].values.to_numpy().astype(np.uint8), | |||
| "encoding": encoding, | |||
| "channels": channels, | |||
| "data": arrow_image["data"].values.to_numpy().astype(storage_type), | |||
| } | |||
| plot.frame = np.reshape( | |||
| @@ -3,14 +3,12 @@ name = "opencv-plot" | |||
| version = "0.1" | |||
| authors = [ | |||
| "Haixuan Xavier Tao <tao.xavier@outlook.com>", | |||
| "Enzo Le Van <dev@enzo-le-van.fr>" | |||
| "Enzo Le Van <dev@enzo-le-van.fr>", | |||
| ] | |||
| description = "Dora Node for plotting text and bbox on image with OpenCV" | |||
| readme = "README.md" | |||
| packages = [ | |||
| { include = "main.py", to = "opencv_plot" } | |||
| ] | |||
| packages = [{ include = "opencv_plot" }] | |||
| [tool.poetry.dependencies] | |||
| dora-rs = "0.3.5" | |||
| @@ -22,4 +20,4 @@ opencv-plot = "opencv_plot.main:main" | |||
| [build-system] | |||
| requires = ["poetry-core>=1.8.0"] | |||
| build-backend = "poetry.core.masonry.api" | |||
| build-backend = "poetry.core.masonry.api" | |||
| @@ -5,19 +5,19 @@ This node is used to capture video from a camera using OpenCV. | |||
| # YAML | |||
| ```yaml | |||
| - id: opencv-video-capture | |||
| build: pip install ../../node-hub/opencv-video-capture | |||
| path: opencv-video-capture | |||
| inputs: | |||
| tick: dora/timer/millis/16 # try to capture at 60fps | |||
| outputs: | |||
| - image: # the captured image | |||
| env: | |||
| PATH: 0 # optional, default is 0 | |||
| IMAGE_WIDTH: 640 # optional, default is video capture width | |||
| IMAGE_HEIGHT: 480 # optional, default is video capture height | |||
| - id: opencv-video-capture | |||
| build: pip install ../../node-hub/opencv-video-capture | |||
| path: opencv-video-capture | |||
| inputs: | |||
| tick: dora/timer/millis/16 # try to capture at 60fps | |||
| outputs: | |||
| - image: # the captured image | |||
| env: | |||
| PATH: 0 # optional, default is 0 | |||
| IMAGE_WIDTH: 640 # optional, default is video capture width | |||
| IMAGE_HEIGHT: 480 # optional, default is video capture height | |||
| ``` | |||
| # Inputs | |||
| @@ -33,16 +33,16 @@ This node is used to capture video from a camera using OpenCV. | |||
| image: { | |||
| "width": np.uint32, | |||
| "height": np.uint32, | |||
| "channels": np.uint8, | |||
| "encoding": str, | |||
| "data": np.array # flattened image data | |||
| } | |||
| encoded_image = pa.array([image]) | |||
| decoded_image = { | |||
| "width": np.uint32(encoded_image[0]["width"].as_py()), | |||
| "height": np.uint32(encoded_image[0]["height"].as_py()), | |||
| "channels": np.uint8(encoded_image[0]["channels"].as_py()), | |||
| "width": np.uint32(encoded_image[0]["width"]), | |||
| "height": np.uint32(encoded_image[0]["height"]), | |||
| "encoding": encoded_image[0]["encoding"].as_py(), | |||
| "data": encoded_image[0]["data"].values.to_numpy().astype(np.uint8) | |||
| } | |||
| ``` | |||
| @@ -54,18 +54,21 @@ def main(): | |||
| if isinstance(video_capture_path, str) and video_capture_path.isnumeric(): | |||
| video_capture_path = int(video_capture_path) | |||
| video_capture = cv2.VideoCapture(video_capture_path) | |||
| image_width = os.getenv("IMAGE_WIDTH", args.image_width) | |||
| image_height = os.getenv("IMAGE_HEIGHT", args.image_height) | |||
| if image_width is not None: | |||
| if isinstance(image_width, str) and image_width.isnumeric(): | |||
| image_width = int(image_width) | |||
| video_capture.set(cv2.CAP_PROP_FRAME_WIDTH, image_width) | |||
| image_height = os.getenv("IMAGE_HEIGHT", args.image_height) | |||
| if image_height is not None: | |||
| if isinstance(image_height, str) and image_height.isnumeric(): | |||
| image_height = int(image_height) | |||
| video_capture.set(cv2.CAP_PROP_FRAME_HEIGHT, image_height) | |||
| video_capture = cv2.VideoCapture(video_capture_path) | |||
| node = Node(args.name) | |||
| start_time = time.time() | |||
| @@ -105,16 +108,12 @@ def main(): | |||
| image = { | |||
| "width": np.uint32(frame.shape[1]), | |||
| "height": np.uint32(frame.shape[0]), | |||
| "channels": np.uint8(frame.shape[2]), | |||
| "encoding": "bgr8", | |||
| "data": frame.ravel(), | |||
| } | |||
| node.send_output("image", pa.array([image]), event["metadata"]) | |||
| if event_id == "stop": | |||
| video_capture.release() | |||
| break | |||
| elif event_type == "ERROR": | |||
| raise Exception(event["error"]) | |||
| @@ -3,14 +3,12 @@ name = "opencv-video-capture" | |||
| version = "0.1" | |||
| authors = [ | |||
| "Haixuan Xavier Tao <tao.xavier@outlook.com>", | |||
| "Enzo Le Van <dev@enzo-le-van.fr>" | |||
| "Enzo Le Van <dev@enzo-le-van.fr>", | |||
| ] | |||
| description = "Dora Node for capturing video with OpenCV" | |||
| readme = "README.md" | |||
| packages = [ | |||
| { include = "main.py", to = "opencv_video_capture" } | |||
| ] | |||
| packages = [{ include = "opencv_video_capture" }] | |||
| [tool.poetry.dependencies] | |||
| dora-rs = "0.3.5" | |||
| @@ -22,4 +20,4 @@ opencv-video-capture = "opencv_video_capture.main:main" | |||
| [build-system] | |||
| requires = ["poetry-core>=1.8.0"] | |||
| build-backend = "poetry.core.masonry.api" | |||
| build-backend = "poetry.core.masonry.api" | |||
| @@ -25,16 +25,16 @@ This node is used to detect objects in images using YOLOv8. | |||
| image: { | |||
| "width": np.uint32, | |||
| "height": np.uint32, | |||
| "channels": np.uint8, | |||
| "encoding": str, | |||
| "data": np.array # flattened image data | |||
| } | |||
| encoded_image = pa.array([image]) | |||
| decoded_image = { | |||
| "width": np.uint32(encoded_image[0]["width"].as_py()), | |||
| "height": np.uint32(encoded_image[0]["height"].as_py()), | |||
| "channels": np.uint8(encoded_image[0]["channels"].as_py()), | |||
| "width": np.uint32(encoded_image[0]["width"]), | |||
| "height": np.uint32(encoded_image[0]["height"]), | |||
| "encoding": encoded_image[0]["encoding"].as_py(), | |||
| "data": encoded_image[0]["data"].values.to_numpy().astype(np.uint8) | |||
| } | |||
| @@ -3,14 +3,12 @@ name = "ultralytics-yolo" | |||
| version = "0.1" | |||
| authors = [ | |||
| "Haixuan Xavier Tao <tao.xavier@outlook.com>", | |||
| "Enzo Le Van <dev@enzo-le-van.fr>" | |||
| "Enzo Le Van <dev@enzo-le-van.fr>", | |||
| ] | |||
| description = "Dora Node for object detection with Ultralytics YOLOv8" | |||
| readme = "README.md" | |||
| packages = [ | |||
| { include = "main.py", to = "ultralytics_yolo" } | |||
| ] | |||
| packages = [{ include = "ultralytics_yolo" }] | |||
| [tool.poetry.dependencies] | |||
| dora-rs = "0.3.5" | |||
| @@ -22,4 +20,4 @@ ultralytics-yolo = "ultralytics_yolo.main:main" | |||
| [build-system] | |||
| requires = ["poetry-core>=1.8.0"] | |||
| build-backend = "poetry.core.masonry.api" | |||
| build-backend = "poetry.core.masonry.api" | |||
| @@ -46,18 +46,29 @@ def main(): | |||
| if event_id == "image": | |||
| arrow_image = event["value"][0] | |||
| encoding = arrow_image["encoding"].as_py() | |||
| if encoding == "bgr8": | |||
| channels = 3 | |||
| storage_type = np.uint8 | |||
| else: | |||
| raise Exception(f"Unsupported image encoding: {encoding}") | |||
| image = { | |||
| "width": np.uint32(arrow_image["width"].as_py()), | |||
| "height": np.uint32(arrow_image["height"].as_py()), | |||
| "channels": np.uint8(arrow_image["channels"].as_py()), | |||
| "data": arrow_image["data"].values.to_numpy().astype(np.uint8), | |||
| "encoding": encoding, | |||
| "channels": channels, | |||
| "data": arrow_image["data"].values.to_numpy().astype(storage_type), | |||
| } | |||
| frame = image["data"].reshape( | |||
| (image["height"], image["width"], image["channels"]) | |||
| ) | |||
| frame = frame[:, :, ::-1] # OpenCV image (BGR to RGB) | |||
| if encoding == "bgr8": | |||
| frame = frame[:, :, ::-1] # OpenCV image (BGR to RGB) | |||
| results = model(frame, verbose=False) # includes NMS | |||
| bboxes = np.array(results[0].boxes.xyxy.cpu()) | |||