Source code for QVideo.filters.artistic

'''Artistic non-photorealistic filters and companion Qt widgets.'''
from qtpy import QtCore, QtWidgets
from pyqtgraph import SpinBox
from QVideo.lib.AsyncVideoFilter import AsyncVideoFilter
from QVideo.lib.QVideoFilter import VideoFilter, QVideoFilter, FilterCode
from QVideo.lib.videotypes import Image
import cv2


__all__ = ['PencilSketchFilter', 'QPencilSketchFilter',
           'CartoonFilter', 'QCartoonFilter']


def _ensure_bgr(image: Image) -> Image:
    return cv2.cvtColor(image, cv2.COLOR_GRAY2BGR) if image.ndim == 2 else image


[docs] class PencilSketchFilter(AsyncVideoFilter): '''Pencil-drawing effect via OpenCV non-photorealistic rendering. Applies ``cv2.pencilSketch`` in a background thread to produce a hand-drawn pencil look without blocking the GUI. Grayscale input is promoted to BGR before processing. Parameters ---------- sigma_s : float Spatial extent of the filter in pixels. Range 1–200. Default: ``60.0``. sigma_r : float How much color variation is treated as part of the same region. Range 0–1. Default: ``0.07``. shade_factor : float Darkness of pencil strokes. Range 0–0.1. Default: ``0.05``. gray : bool If ``True``, return the single-channel grayscale sketch. If ``False``, return the three-channel color sketch. Default: ``False``. ''' def __init__(self, sigma_s: float = 60., sigma_r: float = 0.07, shade_factor: float = 0.05, gray: bool = False) -> None: self.sigma_s = float(sigma_s) self.sigma_r = float(sigma_r) self.shade_factor = float(shade_factor) self.gray = bool(gray) super().__init__()
[docs] def process(self, image: Image) -> Image: '''Apply the pencil-sketch transform in the background thread. Parameters ---------- image : Image Input frame (grayscale or BGR uint8). Returns ------- Image uint8 pencil-sketch frame. ''' bgr = _ensure_bgr(image) gray_out, color_out = cv2.pencilSketch( bgr, sigma_s=self.sigma_s, sigma_r=self.sigma_r, shade_factor=self.shade_factor, ) return gray_out if self.gray else color_out
[docs] def to_code(self) -> FilterCode: ss = self.sigma_s sr = self.sigma_r sf = self.shade_factor preamble = [ 'if image.ndim == 2:', ' image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)', ] if self.gray: body = [ f'image, _ = cv2.pencilSketch(image,' f' sigma_s={ss}, sigma_r={sr}, shade_factor={sf})', ] else: body = [ f'_, image = cv2.pencilSketch(image,' f' sigma_s={ss}, sigma_r={sr}, shade_factor={sf})', ] return FilterCode( imports=frozenset({'import cv2'}), lines=preamble + body, comment=f'pencil sketch, σ_s={ss}, σ_r={sr}, shade={sf}', )
[docs] class CartoonFilter(AsyncVideoFilter): '''Cartoon/painterly stylization via OpenCV non-photorealistic rendering. Applies ``cv2.stylization`` in a background thread, smoothing low-contrast regions while preserving edges to produce a watercolor/cartoon look. Grayscale input is promoted to BGR before processing. Parameters ---------- sigma_s : float Spatial extent of the filter in pixels. Range 1–200. Default: ``150.0``. sigma_r : float How much color variation is treated as part of the same region. Range 0–1. Default: ``0.45``. ''' def __init__(self, sigma_s: float = 150., sigma_r: float = 0.45) -> None: self.sigma_s = float(sigma_s) self.sigma_r = float(sigma_r) super().__init__()
[docs] def process(self, image: Image) -> Image: '''Apply cartoon stylization in the background thread. Parameters ---------- image : Image Input frame (grayscale or BGR uint8). Returns ------- Image uint8 BGR stylized frame. ''' bgr = _ensure_bgr(image) return cv2.stylization(bgr, sigma_s=self.sigma_s, sigma_r=self.sigma_r)
[docs] def to_code(self) -> FilterCode: ss = self.sigma_s sr = self.sigma_r return FilterCode( imports=frozenset({'import cv2'}), lines=[ 'if image.ndim == 2:', ' image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)', f'image = cv2.stylization(image, sigma_s={ss}, sigma_r={sr})', ], comment=f'cartoon stylization, σ_s={ss}, σ_r={sr}', )
[docs] class QPencilSketchFilter(QVideoFilter): '''Widget for :class:`PencilSketchFilter` with parameter spinboxes. Parameters ---------- parent : QtWidgets.QWidget or None Parent widget. ''' display_name = 'Pencil Sketch' display_category = 'Artistic' def __init__(self, parent: QtWidgets.QWidget | None = None) -> None: super().__init__(parent, 'Pencil Sketch', PencilSketchFilter()) def _setupUi(self) -> None: super()._setupUi() f = self.filter self._sigmaS = SpinBox(value=f.sigma_s, bounds=(1., 200.), step=5., prefix='σ_s ') self._sigmaR = SpinBox(value=f.sigma_r, bounds=(0.01, 1.0), step=0.01, prefix='σ_r ') self._shade = SpinBox(value=f.shade_factor, bounds=(0., 0.1), step=0.005, prefix='shade ') self._grayBox = QtWidgets.QCheckBox('Gray') self._grayBox.setChecked(f.gray) for w in (self._sigmaS, self._sigmaR, self._shade, self._grayBox): self._layout.addWidget(w) def _connectSignals(self) -> None: super()._connectSignals() self._sigmaS.valueChanged.connect(self._setSigmaS) self._sigmaR.valueChanged.connect(self._setSigmaR) self._shade.valueChanged.connect(self._setShade) self._grayBox.toggled.connect(self._setGray) @QtCore.Slot(object) def _setSigmaS(self, value: float) -> None: self.filter.sigma_s = float(value) @QtCore.Slot(object) def _setSigmaR(self, value: float) -> None: self.filter.sigma_r = float(value) @QtCore.Slot(object) def _setShade(self, value: float) -> None: self.filter.shade_factor = float(value) @QtCore.Slot(bool) def _setGray(self, checked: bool) -> None: self.filter.gray = checked
[docs] class QCartoonFilter(QVideoFilter): '''Widget for :class:`CartoonFilter` with sigma spinboxes. Parameters ---------- parent : QtWidgets.QWidget or None Parent widget. ''' display_name = 'Cartoon' display_category = 'Artistic' def __init__(self, parent: QtWidgets.QWidget | None = None) -> None: super().__init__(parent, 'Cartoon', CartoonFilter()) def _setupUi(self) -> None: super()._setupUi() f = self.filter self._sigmaS = SpinBox(value=f.sigma_s, bounds=(1., 200.), step=5., prefix='σ_s ') self._sigmaR = SpinBox(value=f.sigma_r, bounds=(0.01, 1.0), step=0.01, prefix='σ_r ') self._layout.addWidget(self._sigmaS) self._layout.addWidget(self._sigmaR) def _connectSignals(self) -> None: super()._connectSignals() self._sigmaS.valueChanged.connect(self._setSigmaS) self._sigmaR.valueChanged.connect(self._setSigmaR) @QtCore.Slot(object) def _setSigmaS(self, value: float) -> None: self.filter.sigma_s = float(value) @QtCore.Slot(object) def _setSigmaR(self, value: float) -> None: self.filter.sigma_r = float(value)
if __name__ == '__main__': # pragma: no cover QCartoonFilter.example()