import numpy as np
try:
import cv2
except ImportError as error:
raise ImportError('Use of the image feeder classes requires that opencv-python is installed.')\
from error
from .base import Feeder
from ..base import Property
from ..buffered_generator import BufferedGenerator
from ..types.sensordata import ImageFrame
[docs]
class CFAR(Feeder):
"""Cell-averaging (CA) Constant False Alarm Rate (CFAR) image data feeder
The CFAR feeder reads grayscale frames from an appropriate :class:`~.FrameReader`or
:class:`~.Feeder` and outputs binary frames whose pixel values are either 0 or 255,
indicating the lack or presence of a detection, respectively.
See `here <https://en.wikipedia.org/wiki/Constant_false_alarm_rate#Cell-averaging_CFAR>`__ for
more information on CA-CFAR.
.. note::
The frames forwarded by the :attr:`~.CFAR.reader` must be grayscale :class:`~.ImageFrame`
objects. As such :attr:`~.ImageFrame.pixels` for all frames must be 2-D arrays, containing
grayscale intensity values.
"""
train_size: int = Property(doc="The number of train pixels", default=10)
guard_size: int = Property(doc="The number of guard pixels", default=4)
alpha: float = Property(doc="The threshold value", default=1.)
squared: bool = Property(doc="If set to True, the threshold will be computed as a function of "
"the sum of squares. The default is False, in which case a "
"simple sum will be evaluated.", default=False)
@BufferedGenerator.generator_method
def data_gen(self):
for timestamp, frame in self.reader:
img = frame.pixels.copy()
output_img = self.cfar(img, self.train_size, self.guard_size, self.alpha, self.squared)
new_frame = ImageFrame(output_img, frame.timestamp)
yield timestamp, new_frame
[docs]
@staticmethod
def cfar(input_img, train_size=10, guard_size=4, alpha=1., squared=False):
""" Perform Constant False Alarm Rate (CFAR) detection on an input image
Parameters
----------
input_img: numpy.ndarray
The input grayscale image.
train_size: int
The number of train pixels.
guard_size: int
The number of guard pixels.
alpha: float
The threshold value.
squared: bool
If set to True, the threshold will be computed as a function of the sum of squares.
The default is False, in which case a simple sum will be evaluated.
Returns
-------
numpy.ndarray
Output image containing 255 for pixels where a target is detected and 0 otherwise.
"""
# Get width and height of image
width, height = input_img.shape
# Compute the CFAR window size
window_size = 1 + 2*guard_size + 2*train_size
# Initialise empty output image
output_img = np.zeros(input_img.shape, np.uint8)
# Iterate through all pixels
for i in range(height-window_size):
for j in range(width-window_size):
# Compute coordinates of test pixel
c_i = i + guard_size + train_size
c_j = j + guard_size + train_size
# Select the pixels inside the window
v = input_img[i:i + window_size, j:j + window_size].copy()
# Exclude pixels inside guard zone
v[train_size:train_size + 2 * guard_size + 1,
train_size:train_size + 2 * guard_size + 1] = 0
# # The above should be equivalent to the code below
# v = np.zeros((window_size, window_size))
# for k in range(window_size):
# for l in range(window_size):
# if (k >= train_size) and (k < (window_size - train_size)) \
# and (l >= train_size) and (l < (window_size - train_size)):
# continue
# v[k, l] += input_img[i+k,j+l]
# Compute the threshold
if squared:
v = v**2
threshold = np.sum(v) / (window_size**2 - (2*guard_size + 1)**2)
# Populate the output image
input_value = input_img[c_i, c_j]
if squared:
input_value = input_value**2
if input_value/threshold > alpha:
output_img[c_i, c_j] = 255
return output_img
[docs]
class CCL(Feeder):
"""Connected Component Labelling (CCL) image data feeder
The CCL feeder reads binary frames from an appropriate :class:`~.FrameReader`or
:class:`~.Feeder` and outputs labelled frames whose pixel values contain the label of the
connected component to which each pixel is has been assigned.
See `here <https://en.wikipedia.org/wiki/Connected-component_labeling#Graphical_example_of_
two-pass_algorithm>`__ for more information on and example applications of CCL.
.. note::
The frames forwarded by the :attr:`~.CCL.reader` must be binary :class:`~.ImageFrame`
objects. As such :attr:`~.ImageFrame.pixels` for all frames must be 2-D arrays, where each
element can take only 1 of 2 possible values (e.g. [0 or 1], [0 or 255], etc.).
"""
@BufferedGenerator.generator_method
def data_gen(self):
for timestamp, frame in self.reader:
img = frame.pixels.copy()
_, labels_img = cv2.connectedComponents(img)
new_frame = ImageFrame(labels_img, frame.timestamp)
yield timestamp, new_frame