[PYTHON] Draw shapes with OpenCV and PIL

Introduction

If you make a cheat sheet, you will understand it in the process. This is an article for myself. I intended to write an article specializing in OpenCV, but for some reason I had to write about PIL as well. Some arguments are omitted.

OpenCV

Create a base monochromatic image numpy.full (shape, fill_value, dtype)

Since the image data of OpenCV is a numpy array, you can create a monochrome image with np.full (). In the case of a black background, it is possible to write a little simpler, but it is not enough to remember individually, so it is omitted.

-* Shape * The shape of the array. If you want to make an RGB image, (height, width, 3). -* Fill_value * The value to fill. If you want to make an RGB image, (b, g, r). -* Dtype * Data type. If you want to make an image, you need to specify dtype = numpy.uint8.

Draw a figure

The following function processes the specified image. What does that mean? img = cv2.line(img, ...) And the return value does not have to be reassigned to itself cv2.line(img, ...) The value of ʻimg changes just by writing. If you don't want that to happen, you need to copy the original image as a separate object. This is why I set ʻimg_rect = img_origin.copy () in this article.

Draw a line cv2.line (img, pt1, pt2, color, thickness)

-* img * Image. -* pt1 , * pt2 * Specify the coordinates of the two points with (x, y). integer. It is possible outside the image. - Color * Line color. -* Thickness * Line thickness. Optional and default value is 1.

Draw a rectangle cv2.rectangle (img, pt1, pt2, color, thickness)

-* img * Image. -* Pt1 , * pt2 * Diagonal coordinates. Specify with (x, y). integer. It is possible outside the image. - Color * Line color. -* Thickness * Line thickness. Optional and default value is 1. If -1 is specified, it will be filled.

Draw a circle cv2.circle (img, center, radius, color, thickness)

Use cv2.ellipse () to draw an arc.

-* img * Image. -* Center * Center coordinates. Specify with (x, y). integer. -* Radius * Radius. integer. -* Color * Line color. -* Thickness * Line thickness. Optional and default value is 1. If -1 is specified, it will be filled.

Draw an ellipse or arc cv2.ellipse (img, center, axes, angle, startAngle, endAngle, color, thickness)

-* img * Image. -* Center * Center coordinates. Specify with (x, y). integer. -* axes * The major and minor diameters are represented by (a, b). Which is vertical and which is horizontal depends on * angle . - Angle * The rotation angle of the ellipse. The unit is degrees. If ʻangle = 0` and a> b, then it is a horizontally long ellipse. -* startAngle * The start angle of the arc. The unit is degrees. The standard is not horizontal but * angle * value. -* EndAngle * The end angle of the arc. You need to specify 0 and 360 when drawing a closed circle or a normal ellipse. It's a hassle. -* Thickness * Line thickness. Optional and default value is 1. If -1 is specified, it will be filled.

The angle can be a decimal. Also, the direction of rotation from the x-axis to the y-axis is positive. In mathematical xy coordinates, it is counterclockwise, but in the computer coordinate system, the y-axis points downward, so counterclockwise is positive. In other words, this kind of thing. ellipse.png

Well, even if I write so far, I will not use it anyway.

Draw a polygon cv2.polylines (img, pts, isClosed, color, thickness)

-* img * Image. -* Pts * Polygonal ** array . A list of numpy arrays. The individual polygons are np.array ([(x1, y1), (x2, y2), ...]), and they are enclosed in brackets to make a list. Even when drawing one polygon, it is a list with one element. - isClosed * Specify True to make a closed polygon by connecting the first point and the last point, and specify False to make a polygonal line. - Color * Line color. -* Thickness * Line thickness. Optional and default value is 1. It cannot be filled by specifying -1.

Writing * pts * is complicated, but you need to master it in order to play with contour detection, so let's do our best to remember it. In the sample program below, a self-made function is incorporated to draw the same figure multiple times. Did you get arrested by the PEP police if you used the space to align them?

polylines_test.py


import numpy as np
import cv2

###Function to offset the coordinates
# [(x1,y1), (x2,y2)]When(a,b)From[(x1+a,y1+b), (x2+a, y2+b)]make
def position_offset(pos, offset):
    np_newpos = np.array(pos) + np.array(offset)
    list_newpos = list(map(tuple, np_newpos.tolist()))
    return list_newpos


###Create a base monochromatic image
imgCV = np.full((180,200,3), (128,128,128), dtype = np.uint8)

###Make 3 same polygonal lines
pos = [(10,10), (80,60), (80,10), (60,30)]
offset1 = (  0,  0)
offset2 = (100, 40)
offset3 = (100,100)
pts1 = np.array(position_offset(pos, offset1))
pts2 = np.array(position_offset(pos, offset2))
pts3 = np.array(position_offset(pos, offset3))

###Draw a polygon
#Draw one polygon
cv2.polylines(imgCV, [pts1]      , True, (255,0,0), 3)

#Draw multiple polygons at once
cv2.polylines(imgCV, [pts2, pts3], True, (0,0,255), 3)

cv2.imshow("polylines", imgCV)
cv2.waitKey(0)
cv2.destroyAllWindows()

The result is this. CV_test.png

Fill the polygon cv2.fillPoly (img, pts, color)

Accurately fill polygons with recesses. Wikipedia has an item called Concave Rectangle In the children's encyclopedia I read a long time ago, it was called "Arrow's Root".

Fill a convex polygon cv2.fillConvexPoly (img, points, color)

If you know that it is a convex polygon, it is faster to use this. If you use this with a concave polygon, it will not cause an error, but it will leave unpainted areas.

-* Points * Coordinate group. Unlike pts, only one polygon can be specified in the numpy array. Why is this guy so different?

Draw characters cv2.putText (img, text, org, fontFace, fontScale, color, thickness)

-* img * Image. -* Text * Text. ** Japanese is not available . It is not possible to start a new line with a line feed code. - org * Specify the ** coordinates at the bottom left of the text with (x, y). - FontFace * font. Not everything. Details will be described later. -* FontScale * Font size. Essential for troublesome things. The size of "1" depends on the font. -* Color * Text color. -* Thickness * Line thickness. Optional and default value is 1.

Fonts that can be used with cv2.putText ()

The following 8 types can be used. HERSHEY is not chocolate, but a vector font developed by Dr. Hershey.

Italics can be created by combining this with cv2.FONT_ITALIC (adding it because it is a built-in constant).

Find the width and height of the text cv2.getTextSize (text, fontFace, fontScale, thickness)

Use this function when you want to place the text in the center of the shape. There are two outputs. The size of the text represented by (width, height) and the y coordinate of the baseline as seen from the lowest point of the string. The latter is the one that pops out below such as "y" and "p".

Character depiction sample

putText_test.py


import numpy as np
import cv2

fonts=[cv2.FONT_HERSHEY_SIMPLEX,
       cv2.FONT_HERSHEY_PLAIN,
       cv2.FONT_HERSHEY_DUPLEX,
       cv2.FONT_HERSHEY_COMPLEX,
       cv2.FONT_HERSHEY_TRIPLEX,
       cv2.FONT_HERSHEY_COMPLEX_SMALL,
       cv2.FONT_HERSHEY_SCRIPT_SIMPLEX,
       cv2.FONT_HERSHEY_SCRIPT_COMPLEX]

#Create a base monochromatic image
img = np.full((360,600,3), (255,255,255), dtype = np.uint8)

for i, font in enumerate(fonts):
    for j in range(2):
        for k in range(2):
            x, y = 200*(j+1)*k+10, i*40+50
            fnt = font if j == 0 else font + cv2.FONT_ITALIC
            text = "sample_{}".format(fnt)
            cv2.putText(img,
                        text = text,
                        org = (x, y),
                        fontFace = fnt,
                        fontScale = 1,
                        color = (0, 0, 255*j),
                        thickness=1)

            #Baseline unused
            (w, h), _ = cv2.getTextSize(text = text,
                                        fontFace = fnt,
                                        fontScale = 1,
                                        thickness = 1)
            cv2.rectangle(img, (x, y), (x+w, y-h), (255, 0, 0))
            cv2.circle(img, (x, y), 3, (255, 0, 0))

cv2.imshow("hershey fonts",img)
cv2.waitKey(0)
cv2.destroyAllWindows()

The center column is a normal font and the right column is an italic font. The left is a composite of the two. There are some fonts that don't have italic effects.

cv_fonts.png

PIL

Create a base monochromatic image Image.new (mode, size, color)

-* Mode * Mode. " RGB ", " RGBA ", " L " (8-bit grayscale), etc. -* Size * Size. Specify (width, height). -* Color * Color. If mode =" RGB ", specify with (r, g, b). Not required, default value is black.

Draw a figure

To draw a figure on an image, create an ImageDraw.Draw object instead of the Image itself, and draw on it.

Draw a line (line) ImageDraw.line (xy, fill, width, joint)

-* Xy * Coordinate group. 3 points or more is possible. Write [(x1, y1), (x2, y2), ...] or [x1, y1, x2, y2, ...] in the list. -* Fill * Line color. The default value when in color is white. The default value at grayscale is black. -* Width * Line width. integer. -* Joint * The default value is None. If " curve " is specified, the connecting part of the polygonal line will be rounded.

Probably a bug, but when using joint =" curve ", the description of the coordinate group must bexy = [(x1, y1), (x2, y2), ...]. If xy = [x1, y1, x2, y2, ...], an error will occur. Also, if you draw a polygonal line with the default color without setting * fill *, the curve processing will be halfway.

Draw a rectangle ImageDraw.rectangle (xy, fill, outline, width)

-* xy * Specify 2 points. Write [(x0, y0), (x1, y1)] or [x0, y0, x1, y1] in the list. -* Fill * Fill color. The default value is None (no fill). -* Outline * Line color. -* Width * Line width.

Draw a circle (ellipse) ImageDraw.ellipse (xy, fill, outline, width)

-* xy * ** Specify two surrounding points instead of the center. ** Write [(x0, y0), (x1, y1)] or [x0, y0, x1, y1] in the list. An image of a circle (ellipse) inscribed in a quadrangle. At this time, x0 <= x1 and y0 <= y1 must be satisfied. -* Fill * Fill color. -* Outline * Line color. -* Width * Line width.

I was wondering why it had such specifications, but when I thought about it, there were many drawing software with such specifications.

Draw a polygon ImageDraw.polygon (xy, fill, outline)

If you know the line and rectangle, you can master it. At the moment, the line width cannot be specified and is fixed at 1.

-* Xy * Coordinate group. The first point and the last point are automatically connected. -* Fill * Fill color. -* Outline * Line color.

Other

PIL.ImageDraw.ImageDraw.point (* xy *, * fill ) to make (plural) points ImageDraw.arc ( xy *, * start *, * end *, * fill *, * width ) ImageDraw.chord ( xy *, * start *, * end *, * fill *, * outline *, * width ) to create an arc + string ImageDraw.pieslice ( xy *, * start *, * end *, * fill *, * outline *, * width *) can be used to draw a fan shape (arc + radius). The unit of the angle is degrees, and it increases clockwise from 3 o'clock like cv2.ellipse () of OpenCV.

Draw letters

The method for drawing PIL characters uses the font in the PC, so Japanese can also be used.

Draw characters ImageDraw.text (xy, text, fill, font)

-* xy * Specify the coordinates of ** upper left ** of the text with (x, y). -* Text * Text. Line breaks using \ n are also OK. -* Fill * Text color. -* Font * Font. Details will be described later. It can be omitted, and in that case it will be a very small font that cannot be Japanese. --There are settings such as the amount of line breaks when drawing multiple lines, but they are omitted.

Specify the character font ImageFont.truetype (font, size)

-* Font * Truetype font file name. It will automatically search for the appropriate location, such as C: \ Windows \ Fonts for Windows and / usr / share / fonts for Raspberry Pi. -* Size * Font size. The default value is 10 points. --There are settings such as encoding (default value is Unicode), but they are omitted.

Find the width and height of the text ImageDraw.textsize (text, font)

Unlike cv2.getTextSize (), the output is only (width, height).

PIL sample

It has become a lot of things. Here, too, the self-made function position_offset () is used.

PIL_test.py


import numpy as np
from PIL import Image, ImageDraw, ImageFont

###Function to offset the coordinates
# [(x1,y1), (x2,y2)]When(a,b)From[(x1+a,y1+b), (x2+a, y2+b)]make
def position_offset(pos, offset):
    np_newpos = np.array(pos) + np.array(offset)
    list_newpos = list(map(tuple, np_newpos.tolist()))
    return list_newpos


###Create a base monochromatic image
imgPIL = Image.new("RGB", (640,480), (128,128,128))

###Create a Draw object
draw = ImageDraw.Draw(imgPIL)

###Line
pos = [(10,10), (80,60), (80,10), (60,30)]

### line
for i in range(6):
    # y=0 : joint =Line chart when None is set, and fill is omitted.
    draw.line(xy = position_offset(pos,(100*i,0)),
              width = i*5,
              joint = None)

    # y=100 : joint = "curve"Line when
    draw.line(xy = position_offset(pos,(100*i,100)),
              fill = (0,255,255),
              width = i*5,
              joint = "curve")

    # y=Right end of 200:joint without specifying fill= "curve"Behavior when
    #It doesn't have to be inside a for loop, but I've put it here for ease of comparison.
    draw.line(xy = position_offset(pos,(100*5,200)),
              width = 25,
              joint = "curve")
    

###Even if the same coordinate group as polygon line is used, it becomes a closed polygon.
#Specify fill
draw.polygon(xy = position_offset(pos,(0,200)),
             fill = (255,0,0))

#Specify outline
draw.polygon(xy = position_offset(pos,(100,200)),
             outline = (0,255,0))

#Specify fill and outline
draw.polygon(xy = position_offset(pos,(200,200)),
             fill = (255,0,0),
             outline = (0,255,0))


###rectangle and ellipse
pos=[(10,0), (200,70)] #Let's change the order of the two points
for i in range(3):
    draw.rectangle(xy = position_offset(pos,(200*i,300)),
                   fill = None,
                   outline = (0,0,255),
                   width = i*5) #width can be 0 even if it is 0

    draw.ellipse(xy = position_offset(pos,(200*i,300)),
                 fill = (255,255,0),
                 outline = (0,255,0),
                 width = i*5)

###arc, chord and pieslice
start, end = 45, 300
#arc arc outline is abbreviated
draw.arc(xy = position_offset(pos,(0,400)),
         start = start,
         end = end,
         width = 4)

#chord string
draw.chord(xy = position_offset(pos,(200,400)),
           start = start,
           end = end,
           fill = (255,0,0),
           outline = (0,255,0))

#pieslice fan
draw.pieslice(xy = position_offset(pos,(400,400)),
              start = start,
              end = end,
              fill = (255,0,0),
              outline = (0,255,0))


###Draw letters
#Draw letters with minimal settings
draw.text(xy = (520,190),
          text = "bug? -> ")

#Draw characters in the specified font
#The font was DF Reiga Song
font = ImageFont.truetype(font="Dflgs9.TTC",
                          size=30)
draw.text(xy = (440,250),
          text = "bug? →",
          fill = (0,0,0),          
          font = font)

###Display image
imgPIL.show()

The result is this. PIL_test.png

At the end

Thank you to teratail for creating my own function position_offset (). Thank you very much. https://teratail.com/questions/234513

Next time preview

I want to display Japanese in OpenCV. For that purpose, PIL should be used only for that part. There are many such articles on the web, but I'm not happy with them. I'm not going to dismiss my predecessors, but I want to create a more practical function.

Recommended Posts

Draw shapes with OpenCV and PIL
Neural network with OpenCV 3 and Python 3
Draw an illustration with Python + OpenCV
Draw arrows (vectors) with opencv / python
Draw a watercolor illusion with edge detection in Python3 and openCV3
Capturing images with Pupil, python and OpenCV
Draw shapes on feature layers with ArcPy
Hello World and face detection with OpenCV 4.3 + Python
Install OpenCV 4.0 and Python 3.7 on Windows 10 with Anaconda
Wavelet transform of images with PyWavelets and OpenCV
Feature matching with OpenCV 3 and Python 3 (A-KAZE, KNN)
PIL installation with pip
Detect stoop with OpenCV
Draw an Earth-like flow animation with matplotlib and cartopy
Image capture / OpenCV speed comparison with and without GPU
Detect mask wearing status with OpenCV and Raspberry Pi
How to loop and play gif video with openCV
Rotate sprites with OpenCV
Ubuntu 20.04 on raspberry pi 4 with OpenCV and use with python
RaspberryPi Zero and OpenCV
Data Augmentation with openCV
[Python] Webcam frame size and FPS settings with OpenCV
Reading, displaying and speeding up gifs with python [OpenCV]
Troubleshoot with installing OpenCV on Raspberry Pi and capturing
With and without WSGI
Easy TopView with OpenCV
Stumble with homebrew opencv3
Image Processing with PIL
Separate color images into RGB images and display them with OpenCV
Create a web surveillance camera with Raspberry Pi and OpenCV
The story of displaying images with OpenCV or PIL (only)
Convert video to black and white with ffmpeg + python + opencv
Programming with Python and Tkinter
Draw Bezier curves with Go
Encryption and decryption with Python
Working with tkinter and mouse
Image processing with PIL (Pillow)
Face recognition with Python's OpenCV
Draw a graph with NetworkX
"Apple processing" with OpenCV3 + Python3
Python and hardware-Using RS232C with Python-
Draw hello world with mod_wsgi
Try edge detection with OpenCV
Image editing with python OpenCV
Camera capture with Python + OpenCV
[Python] Using OpenCV with Python (Basic)
Binarize photo data with OpenCV
Real-time edge detection with OpenCV
Face detection with Python + OpenCV
Get image features with OpenCV
Super-resolution with SRGAN and ESRGAN
group_by with sqlalchemy and sum
Face recognition / cutting with OpenCV
python with pyenv and venv
Try OpenCV with Google Colaboratory
Draw a graph with networkx
With me, NER and Flair
Using OpenCV with Python @Mac
Applications with PyOpenCL (PIL & PyOpenGL)
Image recognition with Keras + OpenCV
Works with Python and R