Skip to main content

Grabbing Images

In order to analyze images with Groundlight, you first need to capture images from a camera or other image source. This guide will show you how to capture images from different sources and formats.

Framegrab

For a unified interface to many different kinds of image sources, see framegrab, an open-source python library maintained by Groundlight.

Capturing Images

Framegrab has many useful features for working with cameras and other image sources. It provides a single interface for extracting images from many different image sources, including generic USB cameras (such as webcams), RTSP streams, HTTP live streams, YouTube live streams, Basler USB cameras, Basler GigE cameras, and Intel RealSense depth cameras.

Installation is straightforward:

pip install framegrab[all]

To capture frames, first configure a FrameGrabber object, specifying the image source. Then call the grab() method to capture a frame:

from framegrab import FrameGrabber

# Create a FrameGrabber for a generic USB camera (e.g., a webcam)
config = {
'input_type': 'generic_usb',
}
grabber = FrameGrabber.create_grabber(config)

frame = grabber.grab()

Framegrab returns images as numpy arrays in BGR format, which is the standard format for OpenCV. This makes it easy to use the images with other image processing libraries, such as OpenCV.

See the framegrab documentation for more information on configuring different image sources.

Motion Detection

Framegrab also includes a motion detection module, which can be used to detect motion in a video stream. This can be useful for detecting when something changes in a scene, such as when a person enters a room or a car pulls into a parking space.

To use the built-in motion detection functionality, first create a MotionDetector object, specifying the percentage threshold for motion detection. Then, use the motion_detected() method with every captured frame to check if motion has been detected:

from framegrab import FrameGrabber, MotionDetector

config = {'input_type': 'generic_usb'}
grabber = FrameGrabber.create_grabber(config)

motion_threshold = 1.0
mdet = MotionDetector(pct_threshold=motion_threshold)

while True:
frame = grabber.grab()
if frame is None:
print("No frame captured!")
continue

if mdet.motion_detected(frame):
print("Motion detected!")

In this example, motion_threshold specifies the sensitivity level for detecting motion based on the percentage of pixels that have changed. By default, this is set to 1.0, indicating a 1% change. To increase the sensitivity, set the threshold to a lower value, such as 0.5%. Likewise, to decrease the sensitivity, set the threshold to a higher value, such as 2%.

Image Formats

Groundlight's SDK accepts images in many popular formats, including PIL, OpenCV, and numpy arrays.

PIL

The Groundlight SDK can accept PIL images directly in submit_image_query. Here's an example:

from groundlight import Groundlight
from PIL import Image

gl = Groundlight()
det = gl.get_or_create_detector(name="path-clear", query="Is the path clear?")
pil_img = Image.open("./docs/static/img/doorway.jpg")
gl.submit_image_query(det, pil_img)

OpenCV

OpenCV is a popular image processing library, with many utilities for working with images. OpenCV images are stored as numpy arrays. (Note they are stored in BGR order, not RGB order, but as of Groundlight SDK v0.8 this is the expected order.) OpenCV's images can be send directly to submit_image_query as follows:

import cv2

cam = cv2.VideoCapture(0) # Initialize camera (0 is the default index)

_, frame = cam.read() # Capture one frame
gl.submit_image_query(detector, frame) # Send the frame to Groundlight
cam.release() # Release the camera

Numpy

The Groundlight SDK can accept images as numpy arrays. They should be in the standard HWN format in BGR color order, matching OpenCV standards. Pixel values should be from 0-255 (not 0.0-1.0 as floats). So uint8 data type is preferable since it saves memory.

Here's sample code to create an 800x600 random image in numpy:

import numpy as np

np_img = np.random.uniform(low=0, high=255, size=(600, 800, 3)).astype(np.uint8)
# Note: channel order is interpretted as BGR not RGB
gl.submit_image_query(detector, np_img)

Channel order: BGR vs RGB

Groundlight expects images in BGR order, because this is standard for OpenCV, which uses numpy arrays as image storage. (OpenCV uses BGR because it was originally developed decades ago for compatibility with the BGR color format used by many cameras and image processing hardware at the time of its creation.) Most other image libraries use RGB order, so if you are using images as numpy arrays which did not originate from OpenCV you likely need to reverse the channel order before sending the images to Groundlight. Note this change was made in v0.8 of the Groundlight SDK - in previous versions, RGB order was expected.

If you have an RGB array, you must reverse the channel order before sending it to Groundlight, like:

# Convert numpy image in RGB channel order to BGR order
bgr_img = rgb_img[:, :, ::-1]

The difference can be surprisingly subtle when red and blue get swapped. Often images just look a little off, but sometimes they look very wrong.

Here's an example of a natural-scene image where you might think the color balance is just off: Correct color order Swapped color channels

In industrial settings, the difference can be almost impossible to detect without prior knowledge of the scene: Correct color order Swapped color channels