How to: receive an image
The primary objective of ifm3d
is to make it as simple and performant as possible to acquire pixel data from an ifm 3D camera of the O3xxxx series.
Additionally, the data should be encoded in a useful format for performing computer vision and/or robotics perception tasks.
A typical ifm3d
client program follows the structure of a control loop whereby images are continuously acquired from the camera and acted upon in some application-specific way.
At the end of this ‘how to’, you should be able to receive images and know the basic usage of the O3R
, FrameGrabber
and Frame
classes.
Note: for O3D or O3X devices, simply use the
O3D
/O3X
class in place of theO3R
in the following code.
O3R, FrameGrabber and Frame
ifm3d provides three main classes:
O3R
holds the configuration of the camera heads, handles the connection, etc;FrameGrabber
receives frames (images);Frame
stores the image buffers.
Instantiating these objects is as follows:
o3r = O3R()
fg = FrameGrabber(o3r, pcic_port=50012)
auto o3r = std::make_shared<ifm3d::O3R>();
auto fg = std::make_shared<ifm3d::FrameGrabber>(o3r, 50012);
Note: The example above assumes that an O3R camera head is connected to the VPU at the physical port 2, which has the PCIC TCP port 50012. The PCIC TCP port number can be retrieved via
o3r.get([f"/ports/portX/data/pcicTCPPort"])
withportX
being a valid port (for exampleport0
,port1
, etc).
The O3R
class, counter-intuitively, refers to the computing unit (the VPU). It inherits its name from previous ifm 3D devices that only used one camera, with no distinction between sensing and computing units.
You can input:
ip
: the IP address of the device;xmlrpc_port
: the XML_RPC port (it is fixed at the moment);password
: the password to connect to the device (unused for the O3R).
The FrameGrabber
stores a reference to the passed in camera shared pointer and starts a worker thread to stream in pixel data from the device.
Its inputs:
o3r
: The O3R instance (the image processing platform) that handles the connection to the camera heads;port
: PCIC port number of the camera head to grab data from (not the physical port number);
Note: instantiating the objects is done the same way for any imager type (2D, 3D, different resolutions, etc).
Note that ifm3d
encourages the use of std::shared_ptr
as a means to manage the ownership and lifetimes of the core actors in an ifm3d
program.
Indeed, you will notice that there is no need to explicitly allocate and deallocate memory.
To instantiate the ifm3d::O3R
object (or ifm3d::O3D
/ifm3d::O3X
), a call to ifm3d::Device::MakeShared()
is made, rather than calling std::make_shared
directly.
This wrapper function is used to handle direct hardware probing to determine the type of camera that is connected.
For example, this may be an O3R, O3D303, O3X, or something else. Regardless, a std::shared_ptr
is returned from this call.
Set the schema and start the FrameGrabber
To start the data stream, the Start
function must be used. This Start
needs a schema which is a std::vector<ifm3d::buffer_id>
and contains the buffer_id
of the data needed for the application:
fg.start([buffer_id.NORM_AMPLITUDE_IMAGE,buffer_id.RADIAL_DISTANCE_IMAGE,buffer_id.XYZ])
fg->Start({ifm3d::buffer_id::AMPLITUDE_IMAGE, ifm3d::buffer_id::RADIAL_DISTANCE_IMAGE,ifm3d::buffer_id::XYZ});
The device provides many types of buffers. All the buffers might not be required for application. Setting the schema provides a way to enable only buffer_id
s required for the application.
This also reduce the bandwidth required to get the data from the device.
Receive an image
Register a callback
The recommended way to receive a frame is to use the callback function. You can register a callback function that will be executed for every received frame, until the program exits.
def callback(frame):
# Read the distance image and display a pixel in the center
dist = frame.get_buffer(buffer_id.RADIAL_DISTANCE_IMAGE)
(width, height) = dist.shape
print(dist[width//2,height//2])
pass
...
fg.start([buffer_id.RADIAL_DISTANCE_IMAGE])
fg.on_new_frame(callback)
...
fg.stop()
void Callback(ifm3d::Frame::Ptr frame){
auto dist = frame->GetBuffer(ifm3d::buffer_id::RADIAL_DISTANCE_IMAGE);
std::cout << dist.height() << " " << dist.width() << std::endl;
}
int
main()
{
...
fg->Start({ifm3d::buffer_id::RADIAL_DISTANCE_IMAGE});
fg->OnNewFrame(&Callback);
...
fg->Stop();
}
Alternatively: wait for a frame
You just need to call the WaitForFrame
function. This Framegrabber
method returns std::Future<ifm3d::Frame>
. The Frame
class stores all the received data. Make sure the camera head is in “RUN” mode.
frame = fg.wait_for_frame().wait() # wait without timeout
<!-- # OR -->
[ok, frame] = fg.wait_for_frame().wait_for(500) # wait with 500ms timeout
<!-- # OR -->
frame = await fg.wait_for_frame() # using asyncio
auto frame = fg->WaitForFrame().get();
Access the data
Accessing the received data is done through the Frame
. Different data types are available depending on whether the camera is a 2D or a 3D camera.
Simply access the image by calling get_buffer
passing the buffer_id
of the required image as a parameter.
# For 3D data:
dist = frame.get_buffer(buffer_id.RADIAL_DISTANCE_IMAGE)
# For 2D data:
rgb = frame.get_buffer(buffer_id.JPEG_IMAGE)
// For 3D data:
auto dist = frame->get_buffer(ifm3d::buffer_id::RADIAL_DISTANCE_IMAGE);
// For 2D data:
auto rgb = frame->get_buffer(ifm3d::buffer_id::JPEG_IMAGE);
The full example
Using a callback
#############################################
# Copyright 2023-present ifm electronic, gmbh
# SPDX-License-Identifier: Apache-2.0
#############################################
import time
from ifm3dpy.device import O3R
from ifm3dpy.framegrabber import FrameGrabber, buffer_id
def callback(frame):
# Read the distance image and display a pixel in the center
dist = frame.get_buffer(buffer_id.RADIAL_DISTANCE_IMAGE)
(width, height) = dist.shape
print(dist[width // 2, height // 2])
def main(ip, port):
# Initialize the objects
o3r = O3R(ip=ip)
pcic_port = o3r.port(port).pcic_port
fg = FrameGrabber(o3r, pcic_port=pcic_port)
# set schema and start Grabber
fg.start(
[buffer_id.NORM_AMPLITUDE_IMAGE, buffer_id.RADIAL_DISTANCE_IMAGE, buffer_id.XYZ]
)
fg.on_new_frame(callback)
# Sleep to avoid exiting the program too soon
time.sleep(1)
fg.stop()
if __name__ == "__main__":
try:
# If the example python package was build, import the configuration
from ovp8xxexamples import config
IP = config.IP
PORT = config.PORT_3D
except ImportError:
# Otherwise, use default values
print(
"Unable to import the configuration.\nPlease run 'pip install -e .' from the python root directory"
)
print("Defaulting to the default configuration.")
IP = "192.168.0.69"
PORT = "port2"
main(IP, PORT)
/*
* Copyright 2021-present ifm electronic, gmbh
* SPDX-License-Identifier: Apache-2.0
*/
#include <chrono>
#include <ifm3d/common/json_impl.hpp>
#include <ifm3d/device/err.h>
#include <ifm3d/device/o3r.h>
#include <ifm3d/fg.h>
#include <iostream>
#include <string>
#include <thread>
using namespace std::chrono_literals;
using namespace ifm3d::literals;
void Callback(ifm3d::Frame::Ptr frame) {
auto dist = frame->GetBuffer(ifm3d::buffer_id::RADIAL_DISTANCE_IMAGE);
std::cout << "Distance image dimensions:" << std::endl;
std::cout << dist.height() << " " << dist.width() << std::endl;
}
int main() {
// Get the IP from the environment if defined
const char *IP = std::getenv("IFM3D_IP") ? std::getenv("IFM3D_IP") : ifm3d::DEFAULT_IP.c_str();
std::clog << "IP: " << IP << std::endl;
//////////////////////////
// Declare the objects
//////////////////////////
// Declare the device object (one object only, corresponding to the VPU)
auto o3r = std::make_shared<ifm3d::O3R>(IP);
//////////////////////////
// Select the first available
// 3D port from the configuration
//////////////////////////
uint16_t pcic_port = 0;
for (const auto &port : o3r->Ports()) {
if (port.type == "3D") {
std::cout << "Using first available 3D port: " << port.port << std::endl;
pcic_port = port.pcic_port;
break;
}
}
/////////////////////////////////////////////////////////
// Alternatively, manually pick the port corresponding
// to your 3D camera (uncomment the line below and comment
// the block above)
/////////////////////////////////////////////////////////
// std::string port_nb = "port2";
// if (o3r->Port(port_nb).type != "3D") {
// std::cerr << "Please provide a 3D port number." << std::endl;
// return -1;
// }
// uint16_t pcic_port = o3r->Port(port_nb).pcic_port;
// std::cout << "Using 3D port: " << port_nb << std::endl;
//////////////////////////////////////////////////
// Verify that a correct port number was provided
// and create the framegrabber object
//////////////////////////////////////////////////
if (pcic_port == 0) {
std::cerr << "No 3D port found in the configuration," << std::endl;
return -1;
}
auto fg = std::make_shared<ifm3d::FrameGrabber>(o3r, pcic_port);
//////////////////////////
// Start the framegrabber
// and register the callback
//////////////////////////
// Set Schema and start the grabber
fg->Start({ifm3d::buffer_id::AMPLITUDE_IMAGE,
ifm3d::buffer_id::RADIAL_DISTANCE_IMAGE, ifm3d::buffer_id::XYZ});
// Register callback function
fg->OnNewFrame(&Callback);
// This sleep is to prevent the program from before the
// callback has time to execute.
std::this_thread::sleep_for(1s);
fg->Stop();
return 0;
}
Using the polling mode
#############################################
# Copyright 2023-present ifm electronic, gmbh
# SPDX-License-Identifier: Apache-2.0
#############################################
from ifm3dpy.device import O3R
from ifm3dpy.framegrabber import FrameGrabber, buffer_id
def main(ip, port):
# Initialize the objects
o3r = O3R(ip)
pcic_port = o3r.port(port).pcic_port
fg = FrameGrabber(o3r, pcic_port)
# Set schema and start Grabber
fg.start(
[buffer_id.NORM_AMPLITUDE_IMAGE, buffer_id.RADIAL_DISTANCE_IMAGE, buffer_id.XYZ]
)
# Get a frame
[ok, frame] = fg.wait_for_frame().wait_for(1500) # wait with 1500ms timeout
# Check that a frame was received
if not ok:
raise RuntimeError("Timeout while waiting for a frame.")
# Read the distance image and display a pixel in the center
dist = frame.get_buffer(buffer_id.RADIAL_DISTANCE_IMAGE)
(width, height) = dist.shape
print(dist[width // 2, height // 2])
fg.stop()
if __name__ == "__main__":
try:
# If the example python package was build, import the configuration
from ovp8xxexamples import config
IP = config.IP
PORT = config.PORT_3D
except ImportError:
# Otherwise, use default values
print(
"Unable to import the configuration.\nPlease run 'pip install -e .' from the python root directory"
)
print("Defaulting to the default configuration.")
IP = "192.168.0.69"
PORT = "port2"
main(ip=IP, port=PORT)
/*
* Copyright 2021-present ifm electronic, gmbh
* SPDX-License-Identifier: Apache-2.0
*/
#include <chrono>
#include <ifm3d/device/o3r.h>
#include <ifm3d/fg.h>
#include <iostream>
using namespace std::chrono_literals;
using namespace ifm3d::literals;
int main() {
//////////////////////////
// Declare the objects
//////////////////////////
// Declare the device object (one object only, corresponding to the VPU)
auto o3r = std::make_shared<ifm3d::O3R>();
//////////////////////////
// Select the first available
// 3D port from the configuration
//////////////////////////
uint16_t pcic_port = 0;
for (const auto &port : o3r->Ports()) {
if (port.type == "3D") {
std::cout << "Using first available 3D port: " << port.port << std::endl;
pcic_port = port.pcic_port;
break;
}
}
/////////////////////////////////////////////////////////
// Alternatively, manually pick the port corresponding
// to your 3D camera (uncomment the line below and comment
// the block above)
/////////////////////////////////////////////////////////
// std::string port_nb = "port2";
// if (o3r->Port(port_nb).type != "3D") {
// std::cerr << "Please provide a 3D port number." << std::endl;
// return -1;
// }
// uint16_t pcic_port = o3r->Port(port_nb).pcic_port;
// std::cout << "Using 3D port: " << port_nb << std::endl;
//////////////////////////////////////////////////
// Verify that a correct port number was provided
// and create the framegrabber object
//////////////////////////////////////////////////
if (pcic_port == 0) {
std::cerr << "No 3D port found in the configuration," << std::endl;
return -1;
}
auto fg = std::make_shared<ifm3d::FrameGrabber>(o3r, pcic_port);
//////////////////////////
// Start the framegrabber
// and run the callback
//////////////////////////
// Set Schema and start the grabber
fg->Start({ifm3d::buffer_id::AMPLITUDE_IMAGE,
ifm3d::buffer_id::RADIAL_DISTANCE_IMAGE, ifm3d::buffer_id::XYZ});
//////////////////////////
// Get a frame:
//////////////////////////
auto future = fg->WaitForFrame();
if (future.wait_for(3s) != std::future_status::ready) {
std::cerr << "Timeout waiting for camera!" << std::endl;
return -1;
}
auto frame = future.get();
//////////////////////////
// Example for 3D data:
//////////////////////////
auto dist = frame->GetBuffer(ifm3d::buffer_id::RADIAL_DISTANCE_IMAGE);
std::cout << dist.height() << " " << dist.width() << std::endl;
fg->Stop();
return 0;
}