|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225 |
- # Image Processing Pipeline
-
- This advanced example demonstrates a real-world image processing pipeline using Dora.
-
- ## Overview
- This example shows how to:
- 1. Create a complex multi-node pipeline
- 2. Handle binary data (images)
- 3. Use external libraries (OpenCV)
- 4. Implement error handling
- 5. Use async processing
-
- ## Project Structure
- ```
- image_processing/
- ├── dora.yml
- ├── requirements.txt
- └── nodes/
- ├── camera.py
- ├── preprocessor.py
- ├── detector.py
- └── visualizer.py
- ```
-
- ## Implementation
-
- ### 1. Dependencies (requirements.txt)
- ```
- opencv-python>=4.5.0
- numpy>=1.19.0
- ```
-
- ### 2. Camera Node (nodes/camera.py)
- ```python
- from dora import AsyncNode
- import cv2
- import numpy as np
-
- class CameraNode(AsyncNode):
- def __init__(self):
- super().__init__()
- self.cap = None
-
- async def setup(self):
- self.cap = cv2.VideoCapture(0)
- if not self.cap.isOpened():
- raise RuntimeError("Failed to open camera")
-
- async def on_input(self, input_data):
- ret, frame = self.cap.read()
- if not ret:
- raise RuntimeError("Failed to read frame")
-
- # Convert to RGB
- frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
- return {"image": frame_rgb.tobytes(), "shape": frame_rgb.shape}
-
- async def cleanup(self):
- if self.cap:
- self.cap.release()
- ```
-
- ### 3. Preprocessor Node (nodes/preprocessor.py)
- ```python
- from dora import Node
- import numpy as np
-
- class PreprocessorNode(Node):
- def __init__(self):
- super().__init__()
-
- def on_input(self, input_data):
- # Convert bytes back to numpy array
- image = np.frombuffer(input_data["image"], dtype=np.uint8)
- image = image.reshape(input_data["shape"])
-
- # Resize image
- resized = cv2.resize(image, (640, 480))
-
- # Normalize
- normalized = resized.astype(np.float32) / 255.0
-
- return {
- "image": normalized.tobytes(),
- "shape": normalized.shape
- }
- ```
-
- ### 4. Detector Node (nodes/detector.py)
- ```python
- from dora import Node
- import numpy as np
-
- class DetectorNode(Node):
- def __init__(self):
- super().__init__()
- # Load your ML model here
- self.model = None
-
- def on_input(self, input_data):
- # Convert to numpy array
- image = np.frombuffer(input_data["image"], dtype=np.float32)
- image = image.reshape(input_data["shape"])
-
- # Run detection (simulated)
- detections = self.simulate_detection(image)
-
- return {
- "detections": detections,
- "image": input_data["image"],
- "shape": input_data["shape"]
- }
-
- def simulate_detection(self, image):
- # Simulate object detection
- return [
- {"class": "person", "confidence": 0.95, "bbox": [100, 100, 200, 200]},
- {"class": "car", "confidence": 0.88, "bbox": [300, 150, 400, 250]}
- ]
- ```
-
- ### 5. Visualizer Node (nodes/visualizer.py)
- ```python
- from dora import Node
- import cv2
- import numpy as np
-
- class VisualizerNode(Node):
- def __init__(self):
- super().__init__()
-
- def on_input(self, input_data):
- # Convert back to numpy array
- image = np.frombuffer(input_data["image"], dtype=np.float32)
- image = image.reshape(input_data["shape"])
-
- # Convert to uint8 for display
- image = (image * 255).astype(np.uint8)
-
- # Draw detections
- for det in input_data["detections"]:
- bbox = det["bbox"]
- cv2.rectangle(image, (bbox[0], bbox[1]), (bbox[2], bbox[3]), (0, 255, 0), 2)
- cv2.putText(image, f"{det['class']} {det['confidence']:.2f}",
- (bbox[0], bbox[1] - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
-
- # Display image
- cv2.imshow("Detection Results", image)
- cv2.waitKey(1)
-
- return None
- ```
-
- ### 6. Graph Configuration (dora.yml)
- ```yaml
- nodes:
- camera:
- inputs: []
- outputs: ["image", "shape"]
- python: nodes/camera.py
-
- preprocessor:
- inputs: ["image", "shape"]
- outputs: ["image", "shape"]
- python: nodes/preprocessor.py
-
- detector:
- inputs: ["image", "shape"]
- outputs: ["detections", "image", "shape"]
- python: nodes/detector.py
-
- visualizer:
- inputs: ["detections", "image", "shape"]
- outputs: []
- python: nodes/visualizer.py
-
- edges:
- - from: camera
- to: preprocessor
- data: [image, shape]
-
- - from: preprocessor
- to: detector
- data: [image, shape]
-
- - from: detector
- to: visualizer
- data: [detections, image, shape]
- ```
-
- ## Running the Example
-
- 1. Install dependencies:
- ```bash
- pip install -r requirements.txt
- ```
-
- 2. Start the application:
- ```bash
- dora run
- ```
-
- 3. You should see a window showing the camera feed with detected objects.
-
- ## Understanding the Example
-
- ### Advanced Concepts
- - **Async Processing**: Camera node uses async/await for non-blocking I/O
- - **Binary Data Handling**: Efficient image data transfer between nodes
- - **Error Handling**: Proper cleanup and error propagation
- - **External Libraries**: Integration with OpenCV
- - **Complex Data Flow**: Multi-stage processing pipeline
-
- ### Performance Considerations
- - Zero-copy data transfer where possible
- - Efficient image format conversions
- - Proper resource cleanup
- - Async processing for I/O operations
-
- ## Next Steps
- - Add real ML model integration
- - Implement frame rate control
- - Add configuration options
- - Implement error recovery
- - Add performance monitoring
|