Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 21 additions & 20 deletions neural-networks/README.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# Barcode Detection on Conveyor Belt

This example demonstrates how to detect and decode barcodes in real-time using computer vision. The application is designed for conveyor belt applications where barcodes need to be detected and decoded from video streams. It uses a [barcode detection model](https://models.luxonis.com/luxonis/barcode-detection/75edea0f-79c9-4091-a48c-f81424b3ccab) for detecting barcode regions and combines multiple decoding strategies (pyzbar and zxing-cpp) to ensure robust barcode recognition across various formats and conditions.

The system processes high-resolution camera input, intelligently crops detected barcode regions, and applies multiple fallback decoding strategies including rotation and color inversion to maximize recognition success rates.

## ⚠️ Important Notice

**This application will work poorly on fixed focus devices.** The barcode detection and decoding algorithms require clear, focused images to function effectively. Fixed focus cameras may struggle to capture sharp barcode images at varying distances, leading to:

- Reduced detection accuracy
- Failed barcode decoding attempts
- Inconsistent performance

For optimal results, use devices with autofocus capabilities or ensure barcodes are positioned at the camera's fixed focal distance.

## Recommended Devices

We recommend using **OAK4-CS** for this example. Its **global-shutter** color sensor is best for fast-moving conveyor belts, reducing motion blur and rolling-shutter artifacts that can cause missed or incorrect decodes.

The application will also run on **OAK4-S** and **OAK4-D** devices. For rolling-shutter or fixed-focus variants, keep barcodes near the best-focus distance and ensure good lighting to maximize decoding reliability. See the notice above regarding fixed-focus cameras.

## Demo

![Demo](media/barcode_demo.gif)

> **Note:** The stream may appear purplish because the OAK4-CS lacks an IR-cut filter.

## Usage

Running this example requires a **Luxonis device** connected to your computer. Refer to the [documentation](https://docs.luxonis.com/software-v3/) to setup your device if you haven't done it already.

You can run the example fully on device ([`STANDALONE` mode](#standalone-mode-rvc4-only)) or using your computer as host ([`PERIPHERAL` mode](#peripheral-mode)).

Here is a list of all available parameters:

```
-d DEVICE, --device DEVICE
Optional name, DeviceID or IP of the camera to connect to. (default: None)
-fps FPS_LIMIT, --fps_limit FPS_LIMIT
FPS limit for the model runtime. (default: 10 for RVC2, 30 for RVC4)
--media_path MEDIA_PATH
Optional path to video file for processing instead of live camera feed. (default: None)
```

## Peripheral Mode

### Installation

Install libraries:

**Ubuntu:**

```bash
sudo apt-get update && apt-get install -y libzbar0 libzbar-dev
```

**macOS:**

```bash
brew install zbar
```

You need to first prepare a **Python 3.10** environment with the following packages installed:

- [DepthAI](https://pypi.org/project/depthai/),
- [DepthAI Nodes](https://pypi.org/project/depthai-nodes/).

You can simply install them by running:

```bash
pip install -r requirements.txt
```

Running in peripheral mode requires a host computer and there will be communication between device and host which could affect the overall speed of the app. Below are some examples of how to run the example.

### Examples

Start the demo:

```bash
python3 main.py
```

This will run the example with default arguments.

```bash
python3 main.py --device 192.168.1.100 --fps_limit 15
```

This will connect to a specific device and set the FPS limit to 15.

```bash
python3 main.py --media_path test_video.mp4
```

This will process a video file instead of live camera feed.

## Standalone Mode (RVC4 only)

Running the example in the standalone mode, app runs entirely on the device.
To run the example in this mode, first install the `oakctl` tool using the installation instructions [here](https://docs.luxonis.com/software-v3/oak-apps/oakctl).

The app can then be run with:

```bash
oakctl connect <DEVICE_IP>
oakctl app run .
```

This will run the example with default argument values. If you want to change these values you need to edit the `oakapp.toml` file (refer [here](https://docs.luxonis.com/software-v3/oak-apps/configuration/) for more information about this configuration file).
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
model: luxonis/barcode-detection:768x576
platform: RVC2
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
model: luxonis/barcode-detection:768x576
platform: RVC4
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
from pathlib import Path
import depthai as dai
from depthai_nodes.node import ParsingNeuralNetwork

from utils.arguments import initialize_argparser
from utils.simple_barcode_overlay import SimpleBarcodeOverlay
from utils.barcode_decoder import BarcodeDecoder
from utils.host_crop_config_creator import CropConfigsCreator

_, args = initialize_argparser()

visualizer = dai.RemoteConnection(httpPort=8082)
device = dai.Device(dai.DeviceInfo(args.device)) if args.device else dai.Device()
platform = device.getPlatform().name
print(f"Platform: {platform}")

frame_type = (
dai.ImgFrame.Type.BGR888i if platform == "RVC4" else dai.ImgFrame.Type.BGR888p
)

if not args.fps_limit:
args.fps_limit = 10 if platform == "RVC2" else 30
print(
f"\nFPS limit set to {args.fps_limit} for {platform} platform. If you want to set a custom FPS limit, use the --fps_limit flag.\n"
)

with dai.Pipeline(device) as pipeline:
print("Creating pipeline...")

model_description = dai.NNModelDescription.fromYamlFile(
f"barcode-detection.{platform}.yaml"
)
nn_archive = dai.NNArchive(
dai.getModelFromZoo(
model_description,
)
)

if args.media_path:
replay = pipeline.create(dai.node.ReplayVideo)
replay.setReplayVideoFile(Path(args.media_path))
replay.setOutFrameType(frame_type)
replay.setLoop(True)
if args.fps_limit:
replay.setFps(args.fps_limit)
else:
cam = pipeline.create(dai.node.Camera).build()

cam_out = cam.requestOutput((2592, 1944), frame_type, fps=args.fps_limit)
input_node = replay.out if args.media_path else cam_out

resize_node = pipeline.create(dai.node.ImageManip)
resize_node.setMaxOutputFrameSize(
nn_archive.getInputWidth() * nn_archive.getInputHeight() * 3
)
resize_node.initialConfig.setOutputSize(
nn_archive.getInputWidth(),
nn_archive.getInputHeight(),
mode=dai.ImageManipConfig.ResizeMode.STRETCH,
)
resize_node.initialConfig.setFrameType(frame_type)
input_node.link(resize_node.inputImage)

detection_nn: ParsingNeuralNetwork = pipeline.create(ParsingNeuralNetwork).build(
resize_node.out, nn_archive
)

crop_code = pipeline.create(CropConfigsCreator).build(
detection_nn.out,
source_size=(2592, 1944),
target_size=(640, 480),
resize_mode=dai.ImageManipConfig.ResizeMode.LETTERBOX,
)

crop_manip = pipeline.create(dai.node.ImageManip)
crop_manip.inputConfig.setReusePreviousMessage(False)
crop_manip.setMaxOutputFrameSize(640 * 480 * 5)
input_node.link(crop_manip.inputImage)
crop_code.config_output.link(crop_manip.inputConfig)

decoder = pipeline.create(BarcodeDecoder)
crop_manip.out.link(decoder.input)

barcode_overlay = pipeline.create(SimpleBarcodeOverlay).build(
decoder.output, resize_node.out, detection_nn.out
)

visualizer.addTopic("Barcode Overlay", barcode_overlay.output)

pipeline.run()

while True:
key = visualizer.waitKey(1)
if key == ord("q"):
break
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
identifier = "com.example.object-detection.conveyor-application-barcodes"
app_version = "1.0.0"

prepare_container = [
{ type = "RUN", command = "apt-get update" },
{ type = "RUN", command = "apt-get install -y python3 python3-pip libzbar0 libzbar-dev" },
{ type = "COPY", source = "requirements.txt", target = "requirements.txt" },
{ type = "RUN", command = "pip3 install -r /app/requirements.txt --break-system-packages"},
]

prepare_build_container = []

build_steps = []

depthai_models = { yaml_path = "./depthai_models" }

entrypoint = ["bash", "-c", "python3 -u /app/main.py"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
depthai>=3.0.0
depthai-nodes==0.3.4
numpy>=1.22
opencv-python-headless~=4.10.0
pyzbar==0.1.9
Pillow==12.0.0
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
from typing import List
import depthai as dai

from depthai_nodes import ImgDetectionsExtended, SECONDARY_COLOR
from depthai_nodes.utils import AnnotationHelper


class AnnotationNode(dai.node.ThreadedHostNode):
def __init__(self) -> None:
super().__init__()

self.input = self.createInput()
self.input.setPossibleDatatypes([(dai.DatatypeEnum.Buffer, True)])

self.out = self.createOutput()
self.out.setPossibleDatatypes([(dai.DatatypeEnum.Buffer, True)])

def build(
self,
gather_data_msg: dai.Node.Output,
) -> "AnnotationNode":
gather_data_msg.link(self.input)
return self

def run(self) -> None:
while self.isRunning():
gather_data_msg: dai.Buffer = self.input.get()

img_detections_extended_msg: ImgDetectionsExtended = (
gather_data_msg.reference_data
)

msg_group_list: List[dai.MessageGroup] = gather_data_msg.gathered

annotations = AnnotationHelper()

for img_detection_extended_msg, msg_group in zip(
img_detections_extended_msg.detections, msg_group_list
):
xmin, ymin, xmax, ymax = (
img_detection_extended_msg.rotated_rect.getOuterRect()
)

try:
xmin = float(xmin)
ymin = float(ymin)
xmax = float(xmax)
ymax = float(ymax)
except Exception:
pass

xmin = max(0.0, min(1.0, xmin))
ymin = max(0.0, min(1.0, ymin))
xmax = max(0.0, min(1.0, xmax))
ymax = max(0.0, min(1.0, ymax))

annotations.draw_rectangle((xmin, ymin), (xmax, ymax))

barcode_text = ""
if "0" in msg_group:
buf_msg: dai.Buffer = msg_group["0"]
barcode_text = buf_msg.getData().decode("utf-8", errors="ignore")

if barcode_text:
annotations.draw_text(
text=barcode_text,
position=(xmin + 0.01, ymin + 0.03),
size=20,
color=SECONDARY_COLOR,
)

annotations_msg = annotations.build(
timestamp=img_detections_extended_msg.getTimestamp(),
sequence_num=img_detections_extended_msg.getSequenceNum(),
)

self.out.send(annotations_msg)
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import argparse


def initialize_argparser():
"""Initialize the argument parser for the script."""
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter
)

parser.add_argument(
"-d",
"--device",
help="Optional name, DeviceID or IP of the camera to connect to.",
required=False,
default=None,
type=str,
)

parser.add_argument(
"-fps",
"--fps_limit",
help="FPS limit for the model runtime.",
required=False,
default=None,
type=int,
)

parser.add_argument(
"-media",
"--media_path",
help="Path to the media file you aim to run the model on. If not set, the model will run on the camera input.",
required=False,
default=None,
type=str,
)
args = parser.parse_args()

return parser, args
Loading