Lab 02

You might also like

Download as pdf or txt
Download as pdf or txt
You are on page 1of 22

`

505060 – Digital Image Processing

LAB02

Prepared by Dang Minh Thang

dangminhthang@tdtu.edu.vn

October 2021
1

Table of Content
I – Objectives ........................................................................................................................................ 2
II – Drawing........................................................................................................................................... 2
2.1 Lines and Rectangles .................................................................................................................. 2
2.2 Circles ......................................................................................................................................... 6
III – Basic image processing .................................................................................................................. 8
3.1 Translation .................................................................................................................................. 8
3.2 Rotation .................................................................................................................................... 10
3.3 Resizing ..................................................................................................................................... 12
3.4 Flipping ..................................................................................................................................... 14
3.5 Bitwise operations .................................................................................................................... 15
3.6 Masking .................................................................................................................................... 18
IV – Excercises .................................................................................................................................... 19
2

I – Objectives
Using NumPy array slices in Lab01 we were able to draw a green square on our image.
But what if we wanted to draw a single line? Or a circle? NumPy does not provide that
type of functionality — it’s only a numerical processing library, after all!

Luckily, OpenCV provides convenient, easy-to-use methods to draw shapes on an


image. In this lesson, we’ll review the three most basic methods to draw shapes:
cv2.line, cv2.rectangle, and cv2.circle. We’ll also get to know how to translate, rotate,
resize, flip, and apply masking an image using OpenCV.

II – Drawing
2.1 Lines and Rectangles
Before we start exploring the drawing capabilities of OpenCV, let’s first define the
canvas in which we will draw our masterpieces.

Up until this point, we have only loaded images off of disk. However, we can also
define our images manually using NumPy arrays. Given that OpenCV interprets an
image as a NumPy array, there is no reason why we can’t manually define the image
ourselves!

In order to initialize our image, let’s examine the code below:

drawing.py
1 # import the necessary packages
2 import numpy as np
3 import cv2
4
5 # initialize our canvas as a 300x300 with 3 channels, Red, Green,
6 # and Blue, with a black background
7 canvas = np.zeros((300, 300, 3), dtype="uint8")

Lines 1 and 2 import the packages we will be using. As a shortcut, we’ll create an
alias for numpy as np. We’ll continue this convention throughout the rest of the course.
In fact, you’ll commonly see this convention in the Python community as well! We’ll
also import cv2 so we can have access to the OpenCV library.
3

Initializing our image is handled on Line 7. We construct a NumPy array using the
np.zeros method with 300 rows and 300 columns, yielding a 300 x 300 pixel image.
We also allocate space for 3 channels — one for Red, Green, and Blue, respectively.
As the name suggests, the zeros method fills every element in the array with an initial
value of zero.

It’s important to draw your attention to the second argument of the np.zeros method:
the data type, dtype. Since we are representing our image as a RGB image with pixels
in the range [0, 255], it’s important that we use an 8-bit unsigned integer, or uint8.
There are many other data types that we can use (common ones include 32-bit integers,
and 32-bit, and 64-bit floats), but we’ll mainly be using uint8 for the majority of the
examples in this lesson.

Now that we have our canvas initialized, we can do some drawing:

drawing.py
9 # draw a green line from the top-left corner of our canvas to the
10 # bottom-right
11 green = (0, 255, 0)
12 cv2.line(canvas, (0, 0), (300, 300), green)
13 cv2.imshow("Canvas", canvas)
14 cv2.waitKey(0)
15
16 # now, draw a 3 pixel thick red line from the top-right corner to the
17 # bottom-left
18 red = (0, 0, 255)
19 cv2.line(canvas, (300, 0), (0, 300), red, 3)
20 cv2.imshow("Canvas", canvas)
21 cv2.waitKey(0)

The first thing we do on Line 11 is define a tuple used to represent the color “green.”
Then, we draw a green line from point (0, 0), the top-left corner of the image, to point
(300, 300), the bottom-right corner of the image, on Line 12.

In order to draw the line, we make use of the cv2.line method. The first argument to
this method is the image we are going to draw on. In this case, it’s our canvas. The
second argument is the starting point of the line. We choose to start our line from the
top-left corner of the image, at point (0, 0) — again, remember that the Python
language is zero-index. We also need to supply an ending point for the line (the third
argument). We define our ending point to be (300, 300), the bottom-right corner of the
4

image. The last argument is the color of our line: in this case, green. Lines 13 and 14
show our image and then wait for a keypress.
As you can see, using the cv2.line function is quite simple! But there is one other
important argument to consider in the cv2.line method: the thickness.

On Lines 18-21 we define the color red as a tuple (again, in BGR rather than RGB
format). We then draw a red line from the top-right corner of the image to the bottom
left. The last parameter to the method controls the thickness of the line — we decide
to make the thickness 3 pixels. Again, we show our image and wait for a keypress. The
result is shown Figure below:

Drawing a line was simple enough. Now we can move on to drawing rectangles. Check out
the code below for more details:

drawing.py
23 # draw a green 50x50 pixel square, starting at 10x10 and ending at 60x60
24 cv2.rectangle(canvas, (10, 10), (60, 60), green)
25 cv2.imshow("Canvas", canvas)
26 cv2.waitKey(0)
27
28 # draw another rectangle, this time we'll make it red and 5 pixels thick
29 cv2.rectangle(canvas, (50, 200), (200, 225), red, 5)
30 cv2.imshow("Canvas", canvas)
31 cv2.waitKey(0)
32
33 # let's draw one last rectangle: blue and filled in
34 blue = (255, 0, 0)
35 cv2.rectangle(canvas, (200, 50), (225, 125), blue, -1)
5

36 cv2.imshow("Canvas", canvas)
37 cv2.waitKey(0)

On Line 24 we make use of the cv2.rectangle method. The signature of this method is
identical to the cv2.line method above.

The first argument is the image in which we want to draw our rectangle on. We want to draw
on our canvas, so we pass it into the method. The second argument is the starting (x, y)
position of our rectangle — here we are starting our rectangle at point (10, 10). Then, we must
provide an ending (x, y) point for the rectangle. We decide to end our rectangle at (60, 60),
defining a region of 50 x 50 pixels. Finally, the last argument is the color of the rectangle we
want to draw.

Just as we can control the thickness of a line, we can also control the thickness of a rectangle.
Line 29 provides that thickness argument Here, we draw a red rectangle that is 5 pixels thick,
starting from point (50, 200) and ending at (200, 225).

Line 35 demonstrates how to draw a rectangle of a solid color. We draw a blue rectangle,
starting from (200, 50) and ending at (225, 125). By specifying -1 as the thickness, our
rectangle is drawn as a solid blue.

The output of drawing our lines and rectangles can be seen below:
6

2.2 Circles
Drawing circles is just as simple as drawing rectangles, but the function arguments are a little
different. Let’s go ahead and get started:

drawing.py
39 # reset our canvas and draw a white circle at the center of the canvas with
40 # increasing radii - from 25 pixels to 150 pixels
41 canvas = np.zeros((300, 300, 3), dtype="uint8")
42 (centerX, centerY) = (canvas.shape[1] // 2, canvas.shape[0] // 2)
43 white = (255, 255, 255)
44
45 for r in range(0, 175, 25):
46 cv2.circle(canvas, (centerX, centerY), r, white)
47
48 # show our work of art
49 cv2.imshow("Canvas", canvas)
50 cv2.waitKey(0)

Line 41 creates a new blank canvas. Line 42 calculates two variables: centerX and centerY.
These two variables represent the (x, y) coordinates of the center of the image. We calculate
the center by examining the shape of our NumPy array, and then dividing by two. The height
of the image can be found in canvas.shape[0] and the width in canvas.shape[1]. Finally,
Line 43 defines a white pixel.

On Line 45 we loop over a number of radius values, starting from 0 and ending at 150,
incrementing by 25 at each step. The range function is exclusive; therefore, we specify a
stopping value of 175 rather than 150.

Line 46 handles the actual drawing of the circle. The first parameter is our canvas, the image
we want to draw the circle on. We then need to supply the point in which our circle will be
drawn around. We pass in a tuple of (centerX, centerY) so that our circles will be centered at
the center of the image. The third argument is the radius of the circle we wish to draw. Finally,
we pass in the color of our circle: in this case, white.

Lines 49 and 50 then show our image and wait for a keypress.

So what does our image look like? Take a look below to see:
7

Up until this point we have only explored to draw shapes on a blank canvas. But what if we
wanted to draw shapes on an existing image?

It turns out that the code to draw shapes on an existing image is exactly the same as if you
were drawing on a blank canvas generated from NumPy.

To demonstrate this, take a look at some code:

52 # load the image of Thay Thang in California


53 image = cv2.imread("california.jpg")
54
55 # draw a circle around my face
56 cv2.circle(image, (672, 420), 90, (0, 0, 255), 2)
57
58 # show the output image
59 cv2.imshow("Output", image)
60 cv2.waitKey(0)

We can see our results here:


8

III – Basic image processing


3.1 Translation
Translation is the shifting of an image along the x and y axis. Using translation, we
can shift an image up, down, left, or right, along with any combination of the above.

Mathematically, we define a translation matrix M that we can use to translate an image:

The function warpAffine transforms the source image using the specified matrix:

This concept is better explained through some code:

translation.py
1 # import the necessary packages
2 import numpy as np
3 import argparse
4 import cv2
5
6 # construct the argument parser and parse the arguments
7 ap = argparse.ArgumentParser()
8 ap.add_argument("-i", "--image", required=True, help="Path to the image")
9 args = vars(ap.parse_args())
9

10
11 # load the image and show it
12 image = cv2.imread(args["image"])
13 cv2.imshow("Original", image)
14
15 # NOTE: Translating (shifting) an image is given by a NumPy matrix in
16 # the form:
17 # [[1, 0, shiftX], [0, 1, shiftY]]
18 # You simply need to specify how many pixels you want to shift the image
19 # in the X and Y direction -- let's translate the image 50 pixels to the
20 # right and 100 pixels down
21 M = np.float32([[1, 0, 50], [0, 1, 100]])
22 shifted = cv2.warpAffine(image, M, (image.shape[1], image.shape[0]))
23 cv2.imshow("Shifted Down and Right", shifted)
24 cv2.waitKey(0)
25
26 # now, let's shift the image 50 pixels to the left and 90 pixels up, we
27 # accomplish this using negative values
28 M = np.float32([[1, 0, -50], [0, 1, -90]])
29 shifted = cv2.warpAffine(image, M, (image.shape[1], image.shape[0]))
30 cv2.imshow("Shifted Up and Left", shifted)
31 cv2.waitKey(0)

On Lines 1-5 we simply import the packages we will make use of. At this point, using
numpy, argparse, and cv2 should feel commonplace.

Anyway, after we have the necessary packages imported, we construct our argument
parser and load our image on Lines 8-13.

The first actual translation takes place on Lines 21-23, where we start by defining our
translation matrix M. This matrix tells us how many pixels to the left or right our image
will be shifted, and then how many pixels up or down the image will be shifted.

Our translation matrix M is defined as a floating-point array — this is important


because OpenCV expects this matrix to be of floating-point type. The first row of the
matrix is [1, 0, shiftX], where shiftX is the number of pixels we will shift the image
left or right. Negative values of shiftX will shift the image to the left and positive
values will shift the image to the right.

Then, we define the second row of the matrix as [0, 1, shiftY], where shiftY is the
number of pixels we will shift the image up or down. Negative values of shiftY will
shift the image up and positive values will shift the image down.
10

Using this notation, on Line 21 we can see that shiftX=25 and shiftY=50, indicating
that we are shifting the image 50 pixels to the right and 100 pixels down.

Now that we have our translation matrix defined, the actual translation takes place on
Line 22 using the cv2.warpAffine function. The first argument is the image we wish to
shift and the second argument is our translation matrix M. Finally, we manually supply
the dimensions (width and height) of our image as the third argument.

Line 23 displays the results of the translation which we can see below. Notice how the
image has clearly be “shifted” down and to the right.

3.2 Rotation
In the last section we reviewed how to translate (i.e. shift) an image up, down, left, and
right (or any combination). Now we are going to move on to our next image processing
topic — rotation.

Rotation is exactly what it sounds like: rotating an image by some angle ϴ. We’ll use
ϴ to represent by how many degrees we are rotating the image.

Open up a new file, name it rotate.py, and let’s get started:

rotate.py
1 # import the necessary packages
2 import numpy as np
3 import argparse
11

4 import cv2
5
6 # construct the argument parser and parse the arguments
7 ap = argparse.ArgumentParser()
8 ap.add_argument("-i", "--image", required=True, help="Path to the image")
9 args = vars(ap.parse_args())
10
11 # load the image and show it
12 image = cv2.imread(args["image"])
13 cv2.imshow("Original", image)
14 cv2.waitKey(0)
15
16 # grab the dimensions of the image and calculate the center of the image
17 (h, w) = image.shape[:2]
18 (cX, cY) = (w / 2, h / 2)
19
20 # rotate our image by 45 degrees
21 M = cv2.getRotationMatrix2D((cX, cY), 45, 1.0)
22 rotated = cv2.warpAffine(image, M, (w, h))
23 cv2.imshow("Rotated by 45 Degrees", rotated)
24 cv2.waitKey(0)
25
26 # rotate our image by -90 degrees
27 M = cv2.getRotationMatrix2D((cX, cY), -90, 1.0)
28 rotated = cv2.warpAffine(image, M, (w, h))
29 cv2.imshow("Rotated by -90 Degrees", rotated)
30 cv2.waitKey(0)

When we rotate an image, we need to specify which point we want to rotate about. In
most cases, you will want to rotate around the center of an image; however, OpenCV
allows you to specify any arbitrary point you want to rotate around (as detailed above).
Let’s just go ahead and rotate about the center of the image. Lines 17 and 18 grab the
width and height of the image, then proceed to divide each component by 2 to
determine the center of the image.

Just as we defined a matrix to translate an image, we also define a matrix to rotate the
image. Instead of manually constructing the matrix using NumPy (which can be a bit
tedious), we’ll just make a call to the cv2.getRotationMatrix2D method on Line 21.

The cv2.getRotationMatrix2D function takes three arguments. The first argument is


the point in which we want to rotate the image about (in this case, the center of the
image). We then specify ϴ, the number of (counter-clockwise) degrees we are going
to rotate the image by. In this case, we are going to rotate the image 45 degrees. The
12

last argument is the scale of the image. We haven’t discussed resizing an image yet,
but here you can specify a floating-point value, where 1.0 means the same, original
dimensions of the image are used. However, if you specified a value of 2.0, the image
would be doubled in size. Similarly, a value of 0.5 halve the size of the image.

Once we have our rotation matrix M from the cv2.getRotationMatrix2D function, we


can apply the rotation to our image using the cv2.warpAffine method on Line 22. The
first argument to this function is the image we want to rotate. We then specify our
rotation matrix M along with the output dimensions (width and height) of our image.
Line 23 then shows our image rotated by 45 degrees.

We can see the output of our 45-degree rotation below:

As you can see, our image has been rotated. But also take a second to note that OpenCV does
not automatically allocate space for our entire rotated image to fit into the frame. In fact, this
is the intended behavior! If you want the entire image to fit into view after the rotation, you’ll
need to modify the width and height, denoted as (w, h) in the cv2.warpAffine function.

3.3 Resizing
Scaling, or simply resizing, is the process of increasing or decreasing the size of an image in
terms of width and height.

When resizing an image, it’s important to keep in mind the aspect ratio — which is the ratio
of the width of the image to the height of an image. Ignoring the aspect ratio can lead to
resized images that look compressed and distorted.
13

Perhaps, not surprisingly, we will be using the cv2.resize function to resize our images. As I
mentioned above, we’ll need to keep in mind the aspect ratio of the image when we are using
this function. But before we get too deep into the details, let’s jump right into an example:

resize.py
1 # import the necessary packages
2 import argparse
3 import cv2
4
5 # construct the argument parser and parse the arguments
6 ap = argparse.ArgumentParser()
7 ap.add_argument("-i", "--image", required=True, help="Path to the image")
8 args = vars(ap.parse_args())
9
10 # load the image and show it
11 image = cv2.imread(args["image"])
12 cv2.imshow("Original", image)
13
14 # we need to keep in mind aspect ratio so the image does not look skewed
15 # or distorted -- therefore, we calculate the ratio of the new image to
16 # the old image. Let's make our new image have a width of 150 pixels
17 r = 150.0 / image.shape[1]
18 dim = (150, int(image.shape[0] * r))
19
20 # perform the actual resizing of the image
21 resized = cv2.resize(image, dim, interpolation=cv2.INTER_AREA)
22 cv2.imshow("Resized (Width)", resized)

The actual interesting code doesn’t start until Lines 17 and 18. When resizing an image, we
need to keep in mind the aspect ratio of the image. The aspect ratio is the proportional
relationship of the width and the height of the image:

The actual resizing of the image takes place on Line 21. The first argument is the image we
wish to resize and the second is our computed dimensions for the new image. The last
parameter is our interpolation method, which is the algorithm working behind the scenes to
handle how the actual image is resized.

When increasing (upsampling) the size of an image, consider using cv2.INTER_LINEAR and
cv2.INTER_CUBIC. The cv2.INTER_LINEAR method tends to be slightly faster than the
cv2.INTER_CUBIC method, but go with whichever one gives you the best results for your
images. When decreasing (downsampling) the size of an image, the OpenCV documentation
suggests using cv2.INTER_AREA.
14

3.4 Flipping
Similar to rotation, OpenCV also provides methods to flip an image across its x or y axis.
Though flipping operations are used less often, they are still very valuable to learn — and for
reasons that you may not think of off the top of your head.

For example, let’s imagine that we are working for a tiny startup company that wants to build
a machine learning classifier to detect faces in images. We would need some sort of dataset
of example faces that our algorithm could use to “learn” what a face is. But unfortunately, the
company has only provided us with a tiny dataset of 20 faces and we don’t have the means to
acquire more data.

So what do we do?

We apply flipping operations!

Let see how to flip by exploring the code:

flipping.py
1 # import the necessary packages
2 import argparse
3 import cv2
4
5 # construct the argument parser and parse the arguments
6 ap = argparse.ArgumentParser()
7 ap.add_argument("-i", "--image", required=True, help = "Path to the image")
8 args = vars(ap.parse_args())
9
10 # load the image and show it
11 image = cv2.imread(args["image"])
12 cv2.imshow("Original", image)
13
14 # flip the image horizontally
15 flipped = cv2.flip(image, 1)
16 cv2.imshow("Flipped Horizontally", flipped)
17
18 # flip the image vertically
19 flipped = cv2.flip(image, 0)
20 cv2.imshow("Flipped Vertically", flipped)
21
22 # flip the image along both axes
23 flipped = cv2.flip(image, -1)
24 cv2.imshow("Flipped Horizontally & Vertically", flipped)
25 cv2.waitKey(0)
15

Flipping an image horizontally is accomplished by making a call to the cv2.flip function on


Line 15. The cv2.flip method requires two arguments: the image we want to flip and a specific
code/flag that is used to determine how we are going to flip the image.

Using a flip code value of 1 indicates that we are going to flip the image horizontally, around
the y-axis (Line 15). Specifying a flip code of 0 indicates that we want to flip the image
vertically, around the x-axis (Line 19). Finally, using a negative flip code (Line 20) flips the
image around both axes.

Take look at Figure below to see our flipped images:

3.5 Bitwise operations


In this section we will review four bitwise operations: AND, OR, XOR, and NOT. These four
operations, while very basic and low level, are paramount to image processing — especially
when we start working with masks in the next section.

Bitwise operations operate in a binary manner and are represented as grayscale images. A
given pixel is turned “off” if it has a value of zero and it is turned “on” if the pixel has a value
greater than zero.

Let’s go ahead and jump into some code:

bitwise.py
1 # import the necessary packages
2 import numpy as np
3 import cv2
4
5 # first, let's draw a rectangle
16

6 rectangle = np.zeros((300, 300), dtype = "uint8")


7 cv2.rectangle(rectangle, (25, 25), (275, 275), 255, -1)
8 cv2.imshow("Rectangle", rectangle)
9
10 # secondly, let's draw a circle
11 circle = np.zeros((300, 300), dtype = "uint8")
12 cv2.circle(circle, (150, 150), 150, 255, -1)
13 cv2.imshow("Circle", circle)

Figure 1 below displays our two shapes:

If we consider these input images, we’ll see that they only have two-pixel intensity values —
either the pixel is 0 (black) or the pixel is greater than zero (white). We call images that only
have two-pixel intensity values binary images.

Let’s demonstrate bitwise operations:

bitwise.py
15 # A bitwise 'AND' is only True when both rectangle and circle have
16 # a value that is 'ON.' Simply put, the bitwise AND function
17 # examines every pixel in rectangle and circle. If both pixels
18 # have a value greater than zero, that pixel is turned 'ON' (i.e
19 # set to 255 in the output image). If both pixels are not greater
20 # than zero, then the output pixel is left 'OFF' with a value of 0.
21 bitwiseAnd = cv2.bitwise_and(rectangle, circle)
22 cv2.imshow("AND", bitwiseAnd)
23 cv2.waitKey(0)
24
25 # A bitwise 'OR' examines every pixel in rectangle and circle. If
26 # EITHER pixel in rectangle or circle is greater than zero, then
27 # the output pixel has a value of 255, otherwise it is 0.
28 bitwiseOr = cv2.bitwise_or(rectangle, circle)
29 cv2.imshow("OR", bitwiseOr)
17

30 cv2.waitKey(0)
31
32 # The bitwise 'XOR' is identical to the 'OR' function, with one
33 # exception: both rectangle and circle are not allowed to BOTH
34 # have values greater than 0.
35 bitwiseXor = cv2.bitwise_xor(rectangle, circle)
36 cv2.imshow("XOR", bitwiseXor)
37 cv2.waitKey(0)
38
39 # Finally, the bitwise 'NOT' inverts the values of the pixels. Pixels
40 # with a value of 255 become 0, and pixels with a value of 0 become
41 # 255.
42 bitwiseNot = cv2.bitwise_not(circle)
43 cv2.imshow("NOT", bitwiseNot)
44 cv2.waitKey(0)

Let’s quickly review our binary operations:

• AND: A bitwise AND is true if and only if both pixels are greater than zero.
• OR: A bitwise OR is true if either of the two pixels are greater than zero.
• XOR: A bitwise XOR is true if and only if one of the two pixels is greater than zero,
but not both.
• NOT: A bitwise NOT inverts the “on” and “off” pixels in an image.

Now look at the results:

Overall, bitwise functions are extremely simple, yet very powerful. Take the time to practice
and become familiar with bitwise operations now before proceeding with the lessons. We will
be making heavy use of these operations throughout the rest of this course.
18

3.6 Masking
In the previous section we explored bitwise operations, a very common technique that is
heavily used in computer vision and image processing.

And as I hinted at previously, we can use a combination of both bitwise operations and masks
to construct ROIs that are non-rectangular. This allows us to extract regions from images that
are of completely arbitrary shape.

Put more simply, a mask allows us to focus only on the portions of the image that interests us.

Let’s look at an example:

masking.py
1 # import the necessary packages
2 import numpy as np
3 import argparse
4 import cv2
5
6 # construct the argument parser and parse the arguments
7 ap = argparse.ArgumentParser()
8 ap.add_argument("-i", "--image", required=True, help="Path to the image")
9 args = vars(ap.parse_args())
10
11 # load the image and display it
12 image = cv2.imread(args["image"])
13 cv2.imshow("Original", image)
14
15 # Masking allows us to focus only on parts of an image that interest us.
16 # A mask is the same size as our image, but has only two pixel values,
17 # 0 and 255. Pixels with a value of 0 are ignored in the orignal image,
18 # and mask pixels with a value of 255 are allowed to be kept. For example,
19 # let's construct a rectangular mask that displays only the person in
20 # the image
21 mask = np.zeros(image.shape[:2], dtype="uint8")
22 cv2.circle(mask, (672, 420), 100, 255, -1)
23 cv2.imshow("Mask", mask)
24
25 # Apply our mask -- notice how only the person in the image is cropped out
26 masked = cv2.bitwise_and(image, image, mask=mask)
27 cv2.imshow("Mask Applied to Image", masked)
28 cv2.waitKey(0)

The results of our circular mask can be seen in the Figure below:
19

IV – Excercises
• Exercise 1: Write a program to draw a rectangle around the face of Messi in the file
messi.jpg.
• Exercise 2: Write a program to display as below:

• Exercise 3: write a program to rotate the image messi.jpg 45-degree counter-clockwise


and keep the entire image fit in the frame as follows (hint:
https://stackoverflow.com/questions/11764575/python-2-7-3-opencv-2-4-after-rotation-
window-doesnt-fit-image):
20

• Exercise 4: write a program to overlay the face of messi (using the images messi.jpg and
mask.jpg) on my face (using the image california.jpg) as follows (hint:
https://docs.opencv.org/3.0-
beta/doc/py_tutorials/py_core/py_image_arithmetics/py_image_arithmetics.html).
21

• Exercise 5: write a program to detect the skin region of an image, for example:

You might also like