Source code for machinevisiontoolbox.base.graphics

import cv2 as cv
from ansitable import ANSITable, Column
from machinevisiontoolbox.base import color_bgr
import matplotlib.pyplot as plt
import numpy as np
import spatialmath.base as smb
from collections.abc import Iterable


[docs] def draw_box( image, l=None, r=None, t=None, b=None, w=None, h=None, lb=None, lt=None, rb=None, rt=None, wh=None, centre=None, ax=None, bbox=None, ltrb=None, color=None, thickness=1, antialias=False, ): """ Draw a box in an image :param image: image to draw into, greyscale or color :type image: ndarray(H,W), ndarray(H,W,P) :param l: left side coordinate :type l: int, optional :param r: right side coordinate :type r: int, optional :param t: top side coordinate :type t: int, optional :param b: bottom side coordinate :type b: int, optional :param bbox: bounding box [xmin, xmax, ymin, ymax] :type bbox: array_like(4), optional :param ltrb: bounding box [xmin, ymin, xmax, ymax] :type ltrb: array_like(4), optional :param lb: left-bottom corner [x,y] :type lb: array_like(2), optional :param lt: left-top corner [x,y] :type lt: array_like(2), optional :param rb: right-bottom corner (x,y) :type rb: array_like(2), optional :param rt: right-top corner (x,y) :type rt: array_like(2), optional :param wh: width and height :type wh: array_like(2), optional :param centre: box centre (x,y) :type centre: array_like(2), optional :param color: color of line :type color: scalar or array_like :param thickness: line thickness, -1 to fill, defaults to 1 :type thickness: int, optional :param antialias: use antialiasing, defaults to False :type antialias: bool, optional :param ax: axes to draw into :type: Matplotlib axes :return: passed image as modified :rtype: ndarray(H,W), ndarray(H,W,P) Draws a box into the specified image using OpenCV. The input ``image`` is modified. The box can be specified in many ways, any combination of inputs is allowed so long as the box is fully specified: - bounding box [xmin, xmax; ymin, ymax] - left-top-right-bottom [xmin, ymin, xmax, ymax] - left side - right side - top side - bottom side - centre and width+height - left-bottom and right-top corners - left-bottom corner and width+height - right-top corner and width+height - left-top corner and width+height where left-bottom is (xmin, ymin), left-top is (xmax, ymax) Example:: >>> from machinevisiontoolbox import draw_box, idisp >>> import numpy as np >>> img = np.zeros((1000, 1000), dtype='uint8') >>> draw_box(img, ltrb=[100, 300, 700, 500], thickness=2, color=200) >>> draw_box(img, ltrb=[100, 300, 700, 500], thickness=-1, color=50) >>> idisp(img) .. plot:: from machinevisiontoolbox import draw_box, idisp import numpy as np img = np.zeros((1000, 1000), dtype='uint8') draw_box(img, ltrb=[100, 300, 700, 500], thickness=2, color=200) draw_box(img, ltrb=[100, 300, 700, 500], thickness=-1, color=50) idisp(img) .. note:: - For images y increases downwards so :math:`y_{top} < y_{bottom}` - if ``image`` is a 3-plane image then ``color`` should be a 3-vector or colorname string and the corresponding elements are used in each plane. :seealso: :func:`~smtb.base.graphics.plot_box` `opencv.rectangle <https://docs.opencv.org/4.x/d6/d6e/group__imgproc__draw.html#ga07d2f74cadcf8e305e810ce8eed13bc9>`_ """ # if not isinstance(color, (int, float)) and len(image.shape) == 2: # raise TypeError("can't draw color into a greyscale image") if bbox is not None: if isinstance(bbox, np.ndarray) and bbox.ndims > 1: # case of [l r; t b] bbox = bbox.ravel() l, r, t, b = bbox elif ltrb is not None: l, t, r, b = ltrb else: if lt is not None: l, t = lt if rt is not None: r, t = rt if lb is not None: l, b = lb if rb is not None: r, b = rb if wh is not None: if isinstance(wh, Iterable): w, h = wh else: w = wh h = wh if centre is not None: cx, cy = centre if l is None: try: l = r - w except: pass if l is None: try: l = cx - w / 2 except: pass if r is None: try: r = l + w except: pass if r is None: try: r = cx + w / 2 except: pass if t is None: try: t = b + h except: pass if t is None: try: t = cy + h / 2 except: pass if b is None: try: b = t - h except: pass if b is None: try: b = cy - h / 2 except: pass if l >= r: raise ValueError("left must be less than right") if b >= t: # raise ValueError("bottom must be less than top") b, t = t, b # TODO need to do this? bl = tuple([int(x) for x in (l, b)]) tr = tuple([int(x) for x in (r, t)]) if isinstance(color, str): color = color_bgr(color) if antialias: lt = cv.LINE_AA else: lt = cv.LINE_8 cv.rectangle(image, bl, tr, color, thickness, lt) return bl, tr
[docs] def plot_labelbox(text, textcolor=None, labelcolor=None, **boxargs): """ Plot a labelled box using matplotlib :param text: text label :type text: str :param textcolor: text color, defaults to None :type textcolor: str, array_like(3), optional :param labelcolor: label background color :type labelcolor: str, array_like(3), optional :param boxargs: arguments passed to :func:`plot_box` Plot a box with a label above it. The position of the box is specified using the same arguments as for ``plot_box``. The label font is specified using the same arguments as for ``plot_text``. If ``labelcolor`` is specified it is used as the background color for the text label, otherwise the box color is used. Example:: >>> from machinevisiontoolbox import plot_labelbox >>> plot_labelbox('labelled box', bbox=[100, 150, 300, 350], color='r') .. plot:: from machinevisiontoolbox import plot_labelbox from spatialmath.base import plotvol2 plotvol2([0, 1000]) plot_labelbox('labelled box', bbox=[100, 150, 300, 350], color='r' .. note:: The label is drawn at the top of the box assuming that axes are drawn with the y-axis downward (image convention). :seealso: :func:`~spatialmath.base.plot_box`, :func:`~spatialmath.base.plot_text` """ rect = smb.plot_box(**boxargs) bbox = rect.get_bbox() if labelcolor is None: labelcolor = boxargs.get("color") smb.plot_text( (bbox.xmin, bbox.ymin), text, color=textcolor, verticalalignment="bottom", bbox=dict(facecolor=labelcolor, linewidth=0, edgecolor=None), )
_fontdict = { "simplex": cv.FONT_HERSHEY_SIMPLEX, "plain": cv.FONT_HERSHEY_PLAIN, "duplex": cv.FONT_HERSHEY_DUPLEX, "complex": cv.FONT_HERSHEY_COMPLEX, "triplex": cv.FONT_HERSHEY_TRIPLEX, "complex-small": cv.FONT_HERSHEY_COMPLEX_SMALL, "script-simplex": cv.FONT_HERSHEY_SCRIPT_SIMPLEX, "script-complex": cv.FONT_HERSHEY_SCRIPT_COMPLEX, "italic": cv.FONT_ITALIC, }
[docs] def draw_labelbox( image, text, textcolor=None, labelcolor=None, font="simplex", fontsize=0.9, fontthickness=2, **boxargs, ): """ Draw a labelled box in an image :param text: text label :type text: str :param textcolor: text color, defaults to black :type textcolor: str, array_like(3), optional :param labelcolor: label background color :type labelcolor: str, array_like(3), optional :param font: OpenCV font, defaults to cv.FONT_HERSHEY_SIMPLEX :type font: str, optional :param fontsize: OpenCV font scale, defaults to 0.3 :type fontsize: float, optional :param fontthickness: font thickness in pixels, defaults to 2 :type fontthickness: int, optional :param boxargs: arguments passed to :func:`draw_box` :raises TypeError: can't draw color into a greyscale image :return: passed image as modified :rtype: ndarray(H,W), ndarray(H,W,P) The position of the box is specified using the same arguments as for :func:`draw_box`. The label font is specified using the same arguments as for :func:`draw_text`. If ``labelcolor`` is specified it is used as the background color for the text label, otherwise the box color is used. Example:: >>> from machinevisiontoolbox import draw_labelbox, idisp >>> import numpy as np >>> img = np.zeros((500, 500)) >>> draw_labelbox(img, 'labelled box', bbox=[100, 500, 300, 600], textcolor=0, labelcolor=100, color=200, thickness=2, fontsize=1) >>> idisp(img) .. plot:: from machinevisiontoolbox import draw_labelbox, idisp import numpy as np img = np.zeros((1000, 1000), dtype='uint8') draw_labelbox(img, 'labelled box', bbox=[100, 500, 300, 600], textcolor=0, labelcolor=100, color=200, thickness=2, fontsize=1) idisp(img) .. note:: - if ``image`` is a 3-plane image then ``color`` should be a 3-vector or colorname string and the corresponding elements are used in each plane. :seealso: :func:`draw_box`, :func:`draw_text` """ # get size of text: ((w,h), baseline) twh = cv.getTextSize(text, _fontdict[font], fontsize, fontthickness) # draw the box bl, tr = draw_box(image, **boxargs) # a bit of margin, 1/2 the text height h = round(twh[0][1] / 2) h2 = round(twh[0][1] / 4) # draw background of the label if labelcolor is None: labelcolor = boxargs.get("color") draw_box( image, lt=bl, wh=(twh[0][0] + h, twh[0][1] + h), color=labelcolor, thickness=-1 ) # draw the text over the background draw_text( image, (bl[0] + h2, bl[1] - h2), text, color=textcolor, font=font, fontsize=fontsize, fontthickness=fontthickness, ) return image
[docs] def draw_text( image, pos, text=None, color=None, font="simplex", fontsize=0.3, fontthickness=2, antialias=False, ): """ Draw text in image :param image: image to draw into, greyscale or color :type image: ndarray(H,W), ndarray(H,W,P) :param pos: position of text (u,v) :type pos: array_like(2) :param text: text :type text: str :param color: color of text :type color: scalar, array_like(3), str :param font: font name, defaults to "simplex" :type font: str, optional :param fontsize: OpenCV font scale, defaults to 0.3 :type fontsize: float, optional :param fontthickness: font thickness in pixels, defaults to 2 :type fontthickness: int, optional :param antialias: use antialiasing, defaults to False :type antialias: bool, optional :return: passed image as modified :rtype: ndarray(H,W), ndarray(H,W,P) The position corresponds to the bottom-left corner of the text box as seen in the image. The font is specified by a string which selects a Hershey vector (stroke) font. ==================== ============================================= Font name OpenCV font name ==================== ============================================= ``'simplex'`` Hershey Roman simplex ``'plain'`` Hershey Roman plain ``'duplex'`` Hershey Roman duplex (double stroke) ``'complex'`` Hershey Roman complex ``'triplex'`` Hershey Romantriplex ``'complex-small'`` Hershey Roman complex (small) ``'script-simplex'`` Hershey script ``'script-complex'`` Hershey script complex ``'italic'`` Hershey italic ==================== ============================================= Example: .. runblock:: pycon >>> from machinevisiontoolbox import draw_text, idisp >>> import numpy as np >>> img = np.zeros((1000, 1000), dtype='uint8') >>> draw_text(img, (100, 150), 'hello world!', color=200, fontsize=2) >>> idisp(img) .. plot:: from machinevisiontoolbox import draw_text, idisp import numpy as np img = np.zeros((1000, 1000), dtype='uint8') draw_text(img, (100, 150), 'hello world!', color=200, fontsize=2) idisp(img) .. note:: - if ``image`` is a 3-plane image then ``color`` should be a 3-vector or colorname string and the corresponding elements are used in each plane. :seealso: :func:`~spatialmath.base.graphics.plot_text` `opencv.putText <https://docs.opencv.org/4.x/d6/d6e/group__imgproc__draw.html#ga5126f47f883d730f633d74f07456c576>`_ """ if not isinstance(color, int) and len(image.shape) == 2: raise TypeError("can't draw color into a greyscale image") if isinstance(color, str): color = color_bgr(color) if antialias: lt = cv.LINE_AA else: lt = cv.LINE_8 cv.putText(image, text, pos, _fontdict[font], fontsize, color, fontthickness, lt) return image
[docs] def draw_point( image, pos, marker="+", text=None, color=None, font="simplex", fontsize=0.3, fontthickness=2, ): r""" Draw a marker in image :param image: image to draw into, greyscale or color :type image: ndarray(H,W), ndarray(H,W,P) :param pos: position of marker :type pos: array_like(2), ndarray(2,n), list of 2-tuples :param marker: marker character, defaults to '+' :type marker: str, optional :param text: text label, defaults to None :type text: str, optional :param color: text color, defaults to None :type color: str or array_like(3), optional :param font: OpenCV font, defaults to cv.FONT_HERSHEY_SIMPLEX :type font: str, optional :param fontsize: OpenCV font scale, defaults to 0.3 :type fontsize: float, optional :param fontthickness: font thickness in pixels, defaults to 2 :type fontthickness: int, optional :raises TypeError: can't draw color into a greyscale image :return: passed image as modified :rtype: ndarray(H,W), ndarray(H,W,P) The ``text`` label is placed to the right of the marker, and vertically centred. The color of the marker can be different to the color of the text, the marker color is specified by a single letter in the marker string, eg. 'b+'. Multiple points can be marked if ``pos`` is a :math:`2 \times n` array or a list of coordinate pairs. In this case: * if ``text`` is a string it is processed with ``text.format(i)`` where ``i`` is the point index (starting at zero). "{0}" within text will be substituted by the point index. * if ``text`` is a list, its elements are used to label the points The font is specified by a string which selects a Hershey vector (stroke) font. ==================== ============================================= Font name OpenCV font name ==================== ============================================= ``'simplex'`` Hershey Roman simplex ``'plain'`` Hershey Roman plain ``'duplex'`` Hershey Roman duplex (double stroke) ``'complex'`` Hershey Roman complex ``'triplex'`` Hershey Romantriplex ``'complex-small'`` Hershey Roman complex (small) ``'script-simplex'`` Hershey script ``'script-complex'`` Hershey script complex ``'italic'`` Hershey italic ==================== ============================================= Example:: >>> from machinevisiontoolbox import draw_point, idisp >>> import numpy as np >>> img = np.zeros((1000, 1000), dtype='uint8') >>> draw_point(img, (100, 300), '*', fontsize=1, color=200) >>> draw_point(img, (500, 300), '*', 'labelled point', fontsize=1, color=200) >>> draw_point(img, np.random.randint(1000, size=(2,10)), '+', 'point {0}', 100, fontsize=0.8) >>> idisp(img) .. plot:: from machinevisiontoolbox import draw_point, idisp import numpy as np img = np.zeros((1000, 1000), dtype='uint8') draw_point(img, (100, 300), '*', fontsize=1, color=200) draw_point(img, (500, 300), '*', 'labelled point', fontsize=1, color=200) draw_point(img, np.random.randint(1000, size=(2,10)), '+', 'point {0}', 100, fontsize=0.8) idisp(img) .. note:: - if ``image`` is a 3-plane image then ``color`` should be a 3-vector or colorname string and the corresponding elements are used in each plane. :seealso: :func:`~spatialmath.base.graphics.plot_point` `opencv.putText <https://docs.opencv.org/4.x/d6/d6e/group__imgproc__draw.html#ga5126f47f883d730f633d74f07456c576>`_ """ fontdict = { "simplex": cv.FONT_HERSHEY_SIMPLEX, "plain": cv.FONT_HERSHEY_PLAIN, "duplex": cv.FONT_HERSHEY_DUPLEX, "complex": cv.FONT_HERSHEY_COMPLEX, "triplex": cv.FONT_HERSHEY_TRIPLEX, "complex-small": cv.FONT_HERSHEY_COMPLEX_SMALL, "script-simplex": cv.FONT_HERSHEY_SCRIPT_SIMPLEX, "script-complex": cv.FONT_HERSHEY_SCRIPT_COMPLEX, "italic": cv.FONT_ITALIC, } if not isinstance(color, int) and len(image.shape) == 2: raise TypeError("can't draw color into a greyscale image") if isinstance(pos, np.ndarray) and pos.shape[0] == 2: x = pos[0, :] y = pos[1, :] elif isinstance(pos, (tuple, list)): if smb.islistof(pos, (tuple, list)): x = [z[0] for z in pos] y = [z[1] for z in pos] else: x = [pos[0]] y = [pos[1]] newmarker = "" markercolor = "" for m in marker: if m in "rgbcmykw": markercolor += m else: newmarker += m marker = newmarker if color is None: color = markercolor if isinstance(color, str): color = color_bgr(color)[::-1] for i, xy in enumerate(zip(x, y)): if isinstance(text, str): label = text.format(i) elif isinstance(text, Iterable): label = text[i] else: label = "" xy = [int(_) for _ in xy] cv.putText( image, f"{marker} {label}", xy, fontdict[font], fontsize, color, fontthickness, ) return image
[docs] def draw_line(image, start, end, color, thickness=1, antialias=False): """ Draw line in image :param image: image to draw into, greyscale or color :type image: ndarray(H,W), ndarray(H,W,P) :param start: start coordinate (u,v) :type start: array_like(2) int :param end: end coordinate (u,v) :type end: array_like(2) int :param color: color of line :type color: scalar, array_like(3) :param thickness: width of line in pixels, defaults to 1 :type thickness: int, optional :param antialias: use antialiasing, defaults to False :type antialias: bool, optional :raises TypeError: can't draw color into a greyscale image :return: passed image as modified :rtype: ndarray(H,W), ndarray(H,W,P) Example: .. runblock:: pycon >>> from machinevisiontoolbox import draw_line, idisp >>> import numpy as np >>> img = np.zeros((1000, 1000), dtype='uint8') >>> draw_line(img, (100, 300), (700, 900), color=200, thickness=10) >>> idisp(img) .. plot:: from machinevisiontoolbox import draw_line, idisp import numpy as np img = np.zeros((1000, 1000), dtype='uint8') draw_line(img, (100, 300), (700, 900), color=200, thickness=10) idisp(img) .. note:: - if ``image`` is a 3-plane image then ``color`` should be a 3-vector or colorname string and the corresponding elements are used in each plane. :seealso: :func:`~spatialmath.base.graphics.plot_line` `opencv.line <https://docs.opencv.org/4.x/d6/d6e/group__imgproc__draw.html#ga7078a9fae8c7e7d13d24dac2520ae4a2>`_ """ if not isinstance(color, int) and len(image.shape) == 2: raise TypeError("can't draw color into a greyscale image") if antialias: lt = cv.LINE_AA else: lt = cv.LINE_8 cv.line(image, start, end, color, thickness, lt) return image
[docs] def draw_circle(image, centre, radius, color, thickness=1, antialias=False): """ Draw line in image :param image: image to draw into, greyscale or color :type image: ndarray(H,W), ndarray(H,W,P) :param centre: centre coordinate :type start: array_like(2) int :param radius: radius in pixels :type end: int :param color: color of circle :type color: scalar, array_like(3) :param thickness: width of line in pixels, -1 to fill, defaults to 1 :type thickness: int, optional :param antialias: use antialiasing, defaults to False :type antialias: bool, optional :raises TypeError: can't draw color into a greyscale image :return: passed image as modified :rtype: ndarray(H,W), ndarray(H,W,P) Example:: >>> from machinevisiontoolbox import draw_circle, idisp >>> import numpy as np >>> img = np.zeros((1000, 1000), dtype='uint8') >>> draw_circle(img, (400,600), 150, thickness=2, color=200) >>> draw_circle(img, (400,600), 150, thickness=-1, color=50) >>> idisp(img) .. plot:: from machinevisiontoolbox import draw_circle, idisp import numpy as np img = np.zeros((1000, 1000), dtype='uint8') draw_circle(img, (400,600), 150, thickness=2, color=200) draw_circle(img, (400,600), 150, thickness=-1, color=50) idisp(img) .. note:: - if ``image`` is a 3-plane image then ``color`` should be a 3-vector or colorname string and the corresponding elements are used in each plane. :seealso: :func:`~spatialmath.base.graphics.plot_circle` `opencv.circle <https://docs.opencv.org/4.x/d6/d6e/group__imgproc__draw.html#gaf10604b069374903dbd0f0488cb43670>`_ """ if not isinstance(color, int) and len(image.shape) == 2: raise TypeError("can't draw color into a greyscale image") if antialias: lt = cv.LINE_AA else: lt = cv.LINE_8 cv.circle(image, centre, radius, color, thickness, lt) return image
# def plot_histogram(c, n, clip=False, ax=None, block=False, xlabel=None, ylabel=None, grid=False, **kwargs): # if ax is None: # plt.figure() # ax = plt.gca() # # n = hist.h # number of pixels per class # # c = hist.x # class value # if clip: # nz, _ = np.where(n > 0) # start = nz[0] # end = nz[-1] + 1 # n = n[start:end] # c = c[start:end] # ax.bar(c, n, **kwargs) # if xlabel is not None: # ax.set_xlabel(xlabel) # if ylabel is not None: # ax.set_ylabel(ylabel) # ax.grid(grid) # plt.show(block=block) # if __name__ == "__main__": # import numpy as np # from machinevisiontoolbox import idisp, iread, Image # from machinevisiontoolbox import draw_labelbox # import numpy as np # img = np.zeros((1000, 1000), dtype='uint8') # draw_labelbox(img, 'labelled box', bbox=[100, 500, 300, 600], # textcolor=0, labelcolor=100, color=200, thickness=2, fontsize=1) # idisp(img, block=True) # im = np.zeros((100,100,3), 'uint8') # im, file = iread('flowers1.png') # draw_box(im, color=(255,0,0), centre=(50,50), wh=(20,20)) # draw_point(im, [(200,200), (300, 300), (400,400)], color='blue') # draw_labelbox(im, "box", thickness=3, centre=(100,100), wh=(100,30), color='red', textcolor='white') # idisp(im, block=True)