Source code for QVideo.filters.unsharp

'''Unsharp masking filter and companion Qt widget.'''
from qtpy import QtCore, QtWidgets
from pyqtgraph import SpinBox
from QVideo.lib.QVideoFilter import VideoFilter, QVideoFilter
from QVideo.lib.videotypes import Image
import cv2

__all__ = ['UnsharpFilter', 'QUnsharpFilter']


[docs] class UnsharpFilter(VideoFilter): '''Unsharp mask sharpening. Computes a Gaussian-blurred copy of the frame and blends it with the original to emphasise high-frequency detail: .. math:: \\text{output} = (1 + \\alpha)\\,\\text{image} - \\alpha\\,\\text{blur}(\\text{image},\\,\\sigma) where *σ* (*radius*) sets the blur width and *α* (*amount*) controls sharpening strength. ``cv2.addWeighted`` is used so the result is clipped to ``[0, 255]`` and returned as ``uint8``. Parameters ---------- radius : float Standard deviation of the Gaussian blur [pixels] (≥ 0.1). Larger values sharpen broader features. Default: ``2.0``. amount : float Sharpening strength (≥ 0.0). ``0`` is a no-op; ``1`` gives a standard unsharp mask; higher values over-sharpen. Default: ``1.0``. ''' def __init__(self, radius: float = 2.0, amount: float = 1.0) -> None: super().__init__() self.radius = radius self.amount = amount @property def radius(self) -> float: '''Gaussian blur σ [pixels] (≥ 0.1).''' return self._radius @radius.setter def radius(self, value: float) -> None: self._radius = max(0.1, float(value)) @property def amount(self) -> float: '''Sharpening strength (≥ 0.0).''' return self._amount @amount.setter def amount(self, value: float) -> None: self._amount = max(0.0, float(value))
[docs] def get(self) -> Image | None: '''Return the sharpened frame. Returns ------- Image or None uint8 sharpened image, or ``None`` if no frame has been added. ''' if self.data is None: return None blurred = cv2.GaussianBlur(self.data, (0, 0), self._radius) return cv2.addWeighted(self.data, 1 + self._amount, blurred, -self._amount, 0)
[docs] def to_code(self) -> 'FilterCode': from QVideo.lib.QVideoFilter import FilterCode return FilterCode( imports=frozenset({'import cv2'}), lines=[ f'_blurred = cv2.GaussianBlur(image, (0, 0), {self._radius})', f'image = cv2.addWeighted(image, {1 + self._amount:.4g}, ' f'_blurred, {-self._amount:.4g}, 0)', ], comment=f'unsharp mask, σ={self._radius}, amount={self._amount}', )
[docs] class QUnsharpFilter(QVideoFilter): '''Widget for :class:`UnsharpFilter` with radius and amount spinboxes. Parameters ---------- parent : QtWidgets.QWidget or None Parent widget. ''' display_name = 'Unsharp Mask' display_category = 'Preprocessing' def __init__(self, parent: QtWidgets.QWidget | None = None) -> None: super().__init__(parent, 'Unsharp Mask', UnsharpFilter()) def _setupUi(self) -> None: super()._setupUi() self._radiusBox = SpinBox(value=self.filter.radius, bounds=(0.1, 20.0), step=0.5, prefix='σ ') self._layout.addWidget(self._radiusBox) self._amountBox = SpinBox(value=self.filter.amount, bounds=(0.0, 10.0), step=0.1, prefix='amount ') self._layout.addWidget(self._amountBox) def _connectSignals(self) -> None: super()._connectSignals() self._radiusBox.valueChanged.connect(self._setRadius) self._amountBox.valueChanged.connect(self._setAmount) @QtCore.Slot(object) def _setRadius(self, value: float) -> None: self.filter.radius = value @QtCore.Slot(object) def _setAmount(self, value: float) -> None: self.filter.amount = value
if __name__ == '__main__': # pragma: no cover QUnsharpFilter.example()