Event accumulation

Events are a sparse representation of brightness changes measured by the pixels on a camera sensor. Accumulation is a method applied to events to generate a frame representation of events. The dv-processing library provides a few highly optimized algorithm implementations to perform event accumulation and achieve different frame-like representation of the events.

Definitions

Certain definitions are used in this chapter and within accumulator API. Following is a list of specific definitions and their meaning within context of the accumulator:

  • Potential - normalized pixel brightness value in some floating point range; it is an internal representation range for brightness that can be scaled into other brightness representations;

  • Contribution - a numeric value that an event contributes to the brightness of pixel;

  • Decay - pixel brightness correction applied over time when no events contribute to its brightness;

  • Neutral potential - a default pixel brightness without any contribution;

  • Minimum / maximum potential - limits for potential value representation.

Accumulator

The dv::Accumulator is a generalized implementation of a few accumulation algorithms that can be configured using class methods. Following chapter will describe the configuration options available for the accumulator. Final chapter of this section will provide a code sample that shows the use of all these configuration options for accumulation of events from a live camera.

Decay function and decay param

One of None, Linear, Exponential, Step. Defines the data degradation function that should be applied to the image. For each function, the Decay param setting assumes a different function:

Function

Enum value

Decay param function

Explanation

None

Decay::NONE

No function

Does not apply any decay

Linear

Decay::LINEAR

The slope a of the linear function, in intensity per microsecond

Assume intensity of a pixel is I0 at time 0, this function applies a linear decay of the form of
I0 - (t * decayparam) or
I0 + (t * decayparam)until the value hits the value specified in neutral potential

Exponential

Decay::EXPONENTIAL

The time constant tau of the exponential function in microseconds

Assume intensity of a pixel is I0 at time 0, this function applies an exponential decay of the form
I0 * exp(-(t/decayparam)). The decay approaches a value of neutralPotential over time.

Step

Decay::STEP

No function

Set all pixel values to neutral potential after a frame is extracted.

The decay function can be set using dv::Accumulator::setDecayFunction() method.

Event Contribution

The contribution an event has onto the image. If an event arrives at a position x, y, the pixel value in the frame at x, y gets increased / decreased by the value of Event contribution, based on the events polarity.

Except:

  • The resulting pixel value would be higher than Max potential, the value gets set to Max potential instead

  • The resulting pixel value would be lower than Min potential, the value gets set to Min potential instead

  • The event polarity is negative, and Ignore polarity is enabled, then the event is counted positively

Event contribution can be set using the dv::Accumulator::setEventContribution() method.

Min potential / Max potential

Sets the minimum and maximum values a pixel can achieve. If the value of the pixel would reach higher or lower, it is capped at these values. These values are also used for normalization at the output. The frame the module generates is an unsigned 8-bit grayscale image, normalized between Min potential and Max potential. A pixel with the value Min potential corresponds to a pixel with the value 0 in the output frame. A pixel with the value Max potential corresponds to a pixel with the value 255 in the output frame.

Min potential and Max potential can be set using dv::Accumulator::setMinPotential() and dv::Accumulator::setMaxPotential() methods.

Neutral potential

This setting has different effects depending on the decay function:

Function

Neutral potential function

None

No function.

Linear

Pixel brightness value decays linearly into the neutral potential value.

Exponential

No function.

Step

Each pixel value is set to neutral potential after generating a frame.

Neutral potential can be set using dv::Accumulator::setNeutralPotential().

Ignore polarity

If this value is set, all events act as if they had positive polarity. In this case, Event contribution is always taken positively. This can be used to generate edge images instead of an actual image reconstruction. This can be done by setting Neutral potential and Min potential to zeros.

This feature can be enabled using the dv::Accumulator::setIgnorePolarity() method.

Synchronous Decay

If this value is set, decay happens in continuous time for all pixels. In every frame, each pixel will be eagerly decayed to the time the image gets generated. If this value is not set, decay at the individual pixel only happens when the pixel receives an event. Decay is lazily evaluated at the pixel.

Note

Both decay regimes yield the same overall decay over time, just the time at which it is applied changes. This parameter does not have an effect for Step decay. Step decay is always synchronous at generation time.

Synchronous decay can be enabled using the dv::Accumulator::setSynchronousDecay() method.

Accumulating event from a camera

The following sample code show how to use dv::Accumulator together with dv::EventStreamSlicer and dv::io::CameraCapture to implement a pipeline that generates continuous stream of accumulated frames:

 1#include <dv-processing/core/frame.hpp>
 2#include <dv-processing/io/camera_capture.hpp>
 3
 4#include <opencv2/highgui.hpp>
 5
 6int main() {
 7    using namespace std::chrono_literals;
 8
 9    // Open any camera
10    dv::io::CameraCapture capture;
11
12    // Make sure it supports event stream output, throw an error otherwise
13    if (!capture.isEventStreamAvailable()) {
14        throw dv::exceptions::RuntimeError("Input camera does not provide an event stream.");
15    }
16
17    // Initialize an accumulator with some resolution
18    dv::Accumulator accumulator(*capture.getEventResolution());
19
20    // Apply configuration, these values can be modified to taste
21    accumulator.setMinPotential(0.f);
22    accumulator.setMaxPotential(1.f);
23    accumulator.setNeutralPotential(0.5f);
24    accumulator.setEventContribution(0.15f);
25    accumulator.setDecayFunction(dv::Accumulator::Decay::EXPONENTIAL);
26    accumulator.setDecayParam(1e+6);
27    accumulator.setIgnorePolarity(false);
28    accumulator.setSynchronousDecay(false);
29
30    // Initialize a preview window
31    cv::namedWindow("Preview", cv::WINDOW_NORMAL);
32
33    // Initialize a slicer
34    dv::EventStreamSlicer slicer;
35
36    // Register a callback every 33 milliseconds
37    slicer.doEveryTimeInterval(33ms, [&accumulator](const dv::EventStore &events) {
38        // Pass events into the accumulator and generate a preview frame
39        accumulator.accept(events);
40        dv::Frame frame = accumulator.generateFrame();
41
42        // Show the accumulated image
43        cv::imshow("Preview", frame.image);
44        cv::waitKey(2);
45    });
46
47    // Run the event processing while the camera is connected
48    while (capture.isRunning()) {
49        // Receive events, check if anything was received
50        if (const auto events = capture.getNextEventBatch()) {
51            // If so, pass the events into the slicer to handle them
52            slicer.accept(*events);
53        }
54    }
55
56    return 0;
57}
_images/accumulator.png

Frame generated using dv::Accumulator class.

Edge accumulation

The dv-processing library provides a highly-optimized variant of accumulator for generating edge maps - dv::EdgeMapAccumulator. It was specifically optimizes for speed of execution, so it has only a minimal set of settings and supported features compared to dv::Accumulator.

Below is a table providing available parameters for the dv::EdgeMapAccumulator:

Parameter

Default value

Accepted values

Comment

Contribution

0.25

[0.0; 1.0]

Contribution potential for a single event.

Ignore polarity

true

boolean

All events are considered positive if enabled.

Neutral potential

0.0

[0.0; 1.0]

Neutral potential is the default pixel value when decay is disabled and the value that pixels decay into when decay is enabled.

Decay param

1.0

[0.0; 1.0]

This value defines how fast pixel values decay to neutral value. The bigger the value the faster the pixel value will reach neutral value. Decay is applied before each frame generation. The range for decay value is [0.0; 1.0], where 0.0 will not apply any decay and 1.0 will apply maximum decay value resetting a pixel to neutral potential at each generation (default behavior).

Following sample show the use of dv::EdgeMapAccumulator with dv::EventStreamSlicer and dv::io::CameraCapture to generate stream of edge images:

 1#include <dv-processing/core/frame.hpp>
 2#include <dv-processing/io/camera_capture.hpp>
 3
 4#include <opencv2/highgui.hpp>
 5
 6int main() {
 7    using namespace std::chrono_literals;
 8
 9    // Open any camera
10    dv::io::CameraCapture capture;
11
12    // Make sure it supports event stream output, throw an error otherwise
13    if (!capture.isEventStreamAvailable()) {
14        throw dv::exceptions::RuntimeError("Input camera does not provide an event stream.");
15    }
16
17    // Initialize an accumulator with some resolution
18    dv::EdgeMapAccumulator accumulator(*capture.getEventResolution());
19
20    // Apply configuration, these values can be modified to taste
21    accumulator.setNeutralPotential(0.0f);
22    accumulator.setEventContribution(0.25f);
23    accumulator.setDecay(1.0);
24    accumulator.setIgnorePolarity(true);
25
26    // Initialize a preview window
27    cv::namedWindow("Preview", cv::WINDOW_NORMAL);
28
29    // Initialize a slicer
30    dv::EventStreamSlicer slicer;
31
32    // Register a callback every 33 milliseconds
33    slicer.doEveryTimeInterval(33ms, [&accumulator](const dv::EventStore &events) {
34        // Pass events into the accumulator and generate a preview frame
35        accumulator.accept(events);
36        dv::Frame frame = accumulator.generateFrame();
37
38        // Show the accumulated image
39        cv::imshow("Preview", frame.image);
40        cv::waitKey(2);
41    });
42
43    // Run the event processing while the camera is connected
44    while (capture.isRunning()) {
45        // Receive events, check if anything was received
46        if (const auto events = capture.getNextEventBatch()) {
47            // If so, pass the events into the slicer to handle them
48            slicer.accept(*events);
49        }
50    }
51
52    return 0;
53}
_images/edge_map.png

Frame generated using dv::EdgeMapAccumulator class.

Event visualization

Accumulators, described in previous chapters are useful when image or edge representation of events is needed, and they are mostly useful to process events using typical image processing algorithms. dv::visualization::EventVisualizer class serves a purpose to perform simple event visualization. Instead of increasing or decreasing pixel brightness, the dv::visualization::EventVisualizer just performs color coding of pixel coordinates where an event was registered.

Following sample show the use of dv::visualization::EventVisualizer class to generate colored previews of events using dv::EventStreamSlicer and dv::io::CameraCapture:

 1#include <dv-processing/io/camera_capture.hpp>
 2#include <dv-processing/visualization/event_visualizer.hpp>
 3
 4#include <opencv2/highgui.hpp>
 5
 6int main() {
 7    using namespace std::chrono_literals;
 8
 9    // Open any camera
10    dv::io::CameraCapture capture;
11
12    // Make sure it supports event stream output, throw an error otherwise
13    if (!capture.isEventStreamAvailable()) {
14        throw dv::exceptions::RuntimeError("Input camera does not provide an event stream.");
15    }
16
17    // Initialize an accumulator with some resolution
18    dv::visualization::EventVisualizer visualizer(*capture.getEventResolution());
19
20    // Apply color scheme configuration, these values can be modified to taste
21    visualizer.setBackgroundColor(dv::visualization::colors::white);
22    visualizer.setPositiveColor(dv::visualization::colors::iniBlue);
23    visualizer.setNegativeColor(dv::visualization::colors::darkGrey);
24
25    // Initialize a preview window
26    cv::namedWindow("Preview", cv::WINDOW_NORMAL);
27
28    // Initialize a slicer
29    dv::EventStreamSlicer slicer;
30
31    // Register a callback every 33 milliseconds
32    slicer.doEveryTimeInterval(33ms, [&visualizer](const dv::EventStore &events) {
33        // Generate a preview frame
34        cv::Mat image = visualizer.generateImage(events);
35
36        // Show the accumulated image
37        cv::imshow("Preview", image);
38        cv::waitKey(2);
39    });
40
41    // Run the event processing while the camera is connected
42    while (capture.isRunning()) {
43        // Receive events, check if anything was received
44        if (const auto events = capture.getNextEventBatch()) {
45            // If so, pass the events into the slicer to handle them
46            slicer.accept(*events);
47        }
48    }
49
50    return 0;
51}
_images/visualizer.png

Frame generated using dv::visualization::EventVisualizer class.

Time surface

Time surface is an event representation in an image frame, except instead of representing event in pixel brightness, it represents event in a 2D image structure, but pixel value contains the latest event timestamp.

The timestamps representations can be normalized to retrieve an image representation of the time surface. It will represent the latest timestamps with the brightest pixel values.

Following sample show the use of dv::TimeSurface class to generate time surface previews of events using dv::EventStreamSlicer and dv::io::CameraCapture:

 1#include <dv-processing/core/frame.hpp>
 2#include <dv-processing/io/camera_capture.hpp>
 3
 4#include <opencv2/highgui.hpp>
 5
 6int main() {
 7    using namespace std::chrono_literals;
 8
 9    // Open any camera
10    dv::io::CameraCapture capture;
11
12    // Make sure it supports event stream output, throw an error otherwise
13    if (!capture.isEventStreamAvailable()) {
14        throw dv::exceptions::RuntimeError("Input camera does not provide an event stream.");
15    }
16
17    // Initialize an accumulator with camera sensor resolution
18    dv::TimeSurface surface(*capture.getEventResolution());
19
20    // Initialize a preview window
21    cv::namedWindow("Preview", cv::WINDOW_NORMAL);
22
23    // Initialize a slicer
24    dv::EventStreamSlicer slicer;
25
26    // Register a callback every 33 milliseconds
27    slicer.doEveryTimeInterval(33ms, [&surface](const dv::EventStore &events) {
28        // Pass the events to update the time surface
29        surface.accept(events);
30
31        // Generate a preview frame
32        dv::Frame frame = surface.generateFrame();
33
34        // Show the accumulated image
35        cv::imshow("Preview", frame.image);
36        cv::waitKey(2);
37    });
38
39    // Run the event processing while the camera is connected
40    while (capture.isRunning()) {
41        // Receive events, check if anything was received
42        if (const auto events = capture.getNextEventBatch()) {
43            // If so, pass the events into the slicer to handle them
44            slicer.accept(*events);
45        }
46    }
47
48    return 0;
49}
_images/time_surface.png

Frame generated using dv::TimeSurface class.

Speed invariant time surface

Speed invariant time surface is a specific time surface variant that is more suitable for feature extraction, the implementation follows this paper: https://arxiv.org/pdf/1903.11332.pdf.

Following sample show the use of dv::SpeedInvariantTimeSurface class to generate time surface previews of events using dv::EventStreamSlicer and dv::io::CameraCapture:

 1#include <dv-processing/io/camera_capture.hpp>
 2
 3#include <opencv2/highgui.hpp>
 4
 5int main() {
 6    using namespace std::chrono_literals;
 7
 8    // Open any camera
 9    dv::io::CameraCapture capture;
10
11    // Make sure it supports event stream output, throw an error otherwise
12    if (!capture.isEventStreamAvailable()) {
13        throw dv::exceptions::RuntimeError("Input camera does not provide an event stream.");
14    }
15
16    // Initialize an accumulator with camera sensor resolution
17    dv::SpeedInvariantTimeSurface surface(*capture.getEventResolution());
18
19    // Initialize a preview window
20    cv::namedWindow("Preview", cv::WINDOW_NORMAL);
21
22    // Initialize a slicer
23    dv::EventStreamSlicer slicer;
24
25    // Register a callback every 33 milliseconds
26    slicer.doEveryTimeInterval(33ms, [&surface](const dv::EventStore &events) {
27        // Pass the events to update the time surface
28        surface.accept(events);
29
30        // Generate a preview frame
31        dv::Frame frame = surface.generateFrame();
32
33        // Show the accumulated image
34        cv::imshow("Preview", frame.image);
35        cv::waitKey(2);
36    });
37
38    // Run the event processing while the camera is connected
39    while (capture.isRunning()) {
40        // Receive events, check if anything was received
41        if (const auto events = capture.getNextEventBatch()) {
42            // If so, pass the events into the slicer to handle them
43            slicer.accept(*events);
44        }
45    }
46
47    return 0;
48}
_images/speed_invariant_time_surface.png

Frame generated using dv::SpeedInvariantTimeSurface class.

Performance of available accumulators

The library performs benchmarking of available accumulation algorithms to ensure their best performance. Accumulators are also benchmarked on two metrics:

  • Event throughput - measured in millions of (mega) events per second;

  • Framerate - measured in generated frames per second.

The benchmarks are performed by generating a batch of events at uniformly random pixel coordinates on a VGA (640x480) resolution. Below are the results of running the benchmark on AMD Ryzen 7 3800X 8-Core Processor:

Accumulator type

Framerate (FPS)

Throughput (MegaEvent/s)

Accumulator

668

66.5

EdgeMapAccumulator

1767

149.1

EventVisualizer

785

78.3

TimeSurface

910

91.5

SpeedInvariantTimeSurface

370

36.8