Source code for QVideo.filters.blob
'''Blob-coloring filter using connected-component labeling.'''
from qtpy import QtWidgets
from QVideo.lib.AsyncVideoFilter import AsyncVideoFilter
from QVideo.lib.QVideoFilter import QVideoFilter
from QVideo.lib.videotypes import Image
import cv2
import numpy as np
__all__ = ['BlobFilter', 'QBlobFilter']
[docs]
class BlobFilter(AsyncVideoFilter):
'''Blob-coloring filter.
Labels connected foreground regions in a binary frame and renders each
blob in a distinct hue using OpenCV's HSV color space. The labeling
runs in a background thread so that large frames do not stall the GUI.
Notes
-----
The input frame is expected to be a binary (uint8) image where non-zero
pixels form the foreground. :func:`cv2.connectedComponents` assigns an
integer label to each connected region; label 0 is background.
Labels are mapped linearly to the hue channel (0–179 in OpenCV) and
merged with a full-saturation, full-value channel to produce an HSV
image that is then converted to BGR. Background pixels (label 0) are
forced to black after the color conversion.
The returned frame is always three-channel BGR uint8, with the same
spatial dimensions as the input. If the input contains no foreground
pixels a black BGR frame is returned.
'''
[docs]
def process(self, image: Image) -> Image:
'''Label connected components and render each blob in a distinct hue.
Parameters
----------
image : Image
Binary (uint8) input frame.
Returns
-------
Image
BGR image with each connected foreground region rendered in a
distinct hue. Background pixels are black.
'''
_, labels = cv2.connectedComponents(image)
max_label = int(np.max(labels))
if max_label == 0:
return np.zeros((*image.shape, 3), dtype=np.uint8)
hues = np.uint8(179 * labels / max_label)
blank = 255 * np.ones_like(hues)
img = cv2.merge([hues, blank, blank])
img = cv2.cvtColor(img, cv2.COLOR_HSV2BGR)
img[hues == 0] = 0
return img
[docs]
class QBlobFilter(QVideoFilter):
'''Widget wrapper for :class:`BlobFilter`.
Displays the filter as a checkable group box. No adjustable
parameters are exposed; checking the box enables the filter.
Parameters
----------
parent : QtWidgets.QWidget or None
Parent widget.
'''
display_name = 'Blob'
display_category = 'Segmentation'
def __init__(self, parent: QtWidgets.QWidget | None = None) -> None:
super().__init__(parent, 'Blob', BlobFilter())
if __name__ == '__main__': # pragma: no cover
QBlobFilter.example()