1
0
mirror of https://github.com/opencv/opencv.git synced 2026-07-30 15:53:03 +04:00

python: 'cv2.' -> 'cv.' via 'import cv2 as cv'

This commit is contained in:
Alexander Alekhin
2017-12-11 12:55:03 +03:00
parent 9665dde678
commit 5560db73bf
162 changed files with 2083 additions and 2084 deletions
@@ -7,7 +7,7 @@ Goal
In this chapter, we will learn about
- Concept of Canny edge detection
- OpenCV functions for that : **cv2.Canny()**
- OpenCV functions for that : **cv.Canny()**
Theory
------
@@ -72,19 +72,19 @@ So what we finally get is strong edges in the image.
Canny Edge Detection in OpenCV
------------------------------
OpenCV puts all the above in single function, **cv2.Canny()**. We will see how to use it. First
OpenCV puts all the above in single function, **cv.Canny()**. We will see how to use it. First
argument is our input image. Second and third arguments are our minVal and maxVal respectively.
Third argument is aperture_size. It is the size of Sobel kernel used for find image gradients. By
default it is 3. Last argument is L2gradient which specifies the equation for finding gradient
magnitude. If it is True, it uses the equation mentioned above which is more accurate, otherwise it
uses this function: \f$Edge\_Gradient \; (G) = |G_x| + |G_y|\f$. By default, it is False.
@code{.py}
import cv2
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv2.imread('messi5.jpg',0)
edges = cv2.Canny(img,100,200)
img = cv.imread('messi5.jpg',0)
edges = cv.Canny(img,100,200)
plt.subplot(121),plt.imshow(img,cmap = 'gray')
plt.title('Original Image'), plt.xticks([]), plt.yticks([])
@@ -7,7 +7,7 @@ Goal
- In this tutorial, you will learn how to convert images from one color-space to another, like
BGR \f$\leftrightarrow\f$ Gray, BGR \f$\leftrightarrow\f$ HSV etc.
- In addition to that, we will create an application which extracts a colored object in a video
- You will learn following functions : **cv2.cvtColor()**, **cv2.inRange()** etc.
- You will learn following functions : **cv.cvtColor()**, **cv.inRange()** etc.
Changing Color-space
--------------------
@@ -15,15 +15,15 @@ Changing Color-space
There are more than 150 color-space conversion methods available in OpenCV. But we will look into
only two which are most widely used ones, BGR \f$\leftrightarrow\f$ Gray and BGR \f$\leftrightarrow\f$ HSV.
For color conversion, we use the function cv2.cvtColor(input_image, flag) where flag determines the
For color conversion, we use the function cv.cvtColor(input_image, flag) where flag determines the
type of conversion.
For BGR \f$\rightarrow\f$ Gray conversion we use the flags cv2.COLOR_BGR2GRAY. Similarly for BGR
\f$\rightarrow\f$ HSV, we use the flag cv2.COLOR_BGR2HSV. To get other flags, just run following
For BGR \f$\rightarrow\f$ Gray conversion we use the flags cv.COLOR_BGR2GRAY. Similarly for BGR
\f$\rightarrow\f$ HSV, we use the flag cv.COLOR_BGR2HSV. To get other flags, just run following
commands in your Python terminal :
@code{.py}
>>> import cv2
>>> flags = [i for i in dir(cv2) if i.startswith('COLOR_')]
>>> import cv2 as cv
>>> flags = [i for i in dir(cv) if i.startswith('COLOR_')]
>>> print( flags )
@endcode
@note For HSV, Hue range is [0,179], Saturation range is [0,255] and Value range is [0,255].
@@ -44,10 +44,10 @@ a blue colored object. So here is the method:
Below is the code which are commented in detail :
@code{.py}
import cv2
import cv2 as cv
import numpy as np
cap = cv2.VideoCapture(0)
cap = cv.VideoCapture(0)
while(1):
@@ -55,26 +55,26 @@ while(1):
_, frame = cap.read()
# Convert BGR to HSV
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
hsv = cv.cvtColor(frame, cv.COLOR_BGR2HSV)
# define range of blue color in HSV
lower_blue = np.array([110,50,50])
upper_blue = np.array([130,255,255])
# Threshold the HSV image to get only blue colors
mask = cv2.inRange(hsv, lower_blue, upper_blue)
mask = cv.inRange(hsv, lower_blue, upper_blue)
# Bitwise-AND mask and original image
res = cv2.bitwise_and(frame,frame, mask= mask)
res = cv.bitwise_and(frame,frame, mask= mask)
cv2.imshow('frame',frame)
cv2.imshow('mask',mask)
cv2.imshow('res',res)
k = cv2.waitKey(5) & 0xFF
cv.imshow('frame',frame)
cv.imshow('mask',mask)
cv.imshow('res',res)
k = cv.waitKey(5) & 0xFF
if k == 27:
break
cv2.destroyAllWindows()
cv.destroyAllWindows()
@endcode
Below image shows tracking of the blue object:
@@ -90,12 +90,12 @@ How to find HSV values to track?
--------------------------------
This is a common question found in [stackoverflow.com](http://www.stackoverflow.com). It is very simple and
you can use the same function, cv2.cvtColor(). Instead of passing an image, you just pass the BGR
you can use the same function, cv.cvtColor(). Instead of passing an image, you just pass the BGR
values you want. For example, to find the HSV value of Green, try following commands in Python
terminal:
@code{.py}
>>> green = np.uint8([[[0,255,0 ]]])
>>> hsv_green = cv2.cvtColor(green,cv2.COLOR_BGR2HSV)
>>> hsv_green = cv.cvtColor(green,cv.COLOR_BGR2HSV)
>>> print( hsv_green )
[[[ 60 255 255]]]
@endcode
@@ -16,17 +16,17 @@ Image moments help you to calculate some features like center of mass of the obj
object etc. Check out the wikipedia page on [Image
Moments](http://en.wikipedia.org/wiki/Image_moment)
The function **cv2.moments()** gives a dictionary of all moment values calculated. See below:
The function **cv.moments()** gives a dictionary of all moment values calculated. See below:
@code{.py}
import cv2
import numpy as np
import cv2 as cv
img = cv2.imread('star.jpg',0)
ret,thresh = cv2.threshold(img,127,255,0)
im2,contours,hierarchy = cv2.findContours(thresh, 1, 2)
img = cv.imread('star.jpg',0)
ret,thresh = cv.threshold(img,127,255,0)
im2,contours,hierarchy = cv.findContours(thresh, 1, 2)
cnt = contours[0]
M = cv2.moments(cnt)
M = cv.moments(cnt)
print( M )
@endcode
From this moments, you can extract useful data like area, centroid etc. Centroid is given by the
@@ -40,18 +40,18 @@ cy = int(M['m01']/M['m00'])
2. Contour Area
---------------
Contour area is given by the function **cv2.contourArea()** or from moments, **M['m00']**.
Contour area is given by the function **cv.contourArea()** or from moments, **M['m00']**.
@code{.py}
area = cv2.contourArea(cnt)
area = cv.contourArea(cnt)
@endcode
3. Contour Perimeter
--------------------
It is also called arc length. It can be found out using **cv2.arcLength()** function. Second
It is also called arc length. It can be found out using **cv.arcLength()** function. Second
argument specify whether shape is a closed contour (if passed True), or just a curve.
@code{.py}
perimeter = cv2.arcLength(cnt,True)
perimeter = cv.arcLength(cnt,True)
@endcode
4. Contour Approximation
@@ -68,8 +68,8 @@ you can use this function to approximate the shape. In this, second argument is
which is maximum distance from contour to approximated contour. It is an accuracy parameter. A wise
selection of epsilon is needed to get the correct output.
@code{.py}
epsilon = 0.1*cv2.arcLength(cnt,True)
approx = cv2.approxPolyDP(cnt,epsilon,True)
epsilon = 0.1*cv.arcLength(cnt,True)
approx = cv.approxPolyDP(cnt,epsilon,True)
@endcode
Below, in second image, green line shows the approximated curve for epsilon = 10% of arc length.
Third image shows the same for epsilon = 1% of the arc length. Third argument specifies whether
@@ -81,7 +81,7 @@ curve is closed or not.
--------------
Convex Hull will look similar to contour approximation, but it is not (Both may provide same results
in some cases). Here, **cv2.convexHull()** function checks a curve for convexity defects and
in some cases). Here, **cv.convexHull()** function checks a curve for convexity defects and
corrects it. Generally speaking, convex curves are the curves which are always bulged out, or
at-least flat. And if it is bulged inside, it is called convexity defects. For example, check the
below image of hand. Red line shows the convex hull of hand. The double-sided arrow marks shows the
@@ -91,7 +91,7 @@ convexity defects, which are the local maximum deviations of hull from contours.
There is a little bit things to discuss about it its syntax:
@code{.py}
hull = cv2.convexHull(points[, hull[, clockwise[, returnPoints]]
hull = cv.convexHull(points[, hull[, clockwise[, returnPoints]]
@endcode
Arguments details:
@@ -104,7 +104,7 @@ Arguments details:
So to get a convex hull as in above image, following is sufficient:
@code{.py}
hull = cv2.convexHull(cnt)
hull = cv.convexHull(cnt)
@endcode
But if you want to find convexity defects, you need to pass returnPoints = False. To understand it,
we will take the rectangle image above. First I found its contour as cnt. Now I found its convex
@@ -119,10 +119,10 @@ You will see it again when we discuss about convexity defects.
6. Checking Convexity
---------------------
There is a function to check if a curve is convex or not, **cv2.isContourConvex()**. It just return
There is a function to check if a curve is convex or not, **cv.isContourConvex()**. It just return
whether True or False. Not a big deal.
@code{.py}
k = cv2.isContourConvex(cnt)
k = cv.isContourConvex(cnt)
@endcode
7. Bounding Rectangle
@@ -133,25 +133,25 @@ There are two types of bounding rectangles.
### 7.a. Straight Bounding Rectangle
It is a straight rectangle, it doesn't consider the rotation of the object. So area of the bounding
rectangle won't be minimum. It is found by the function **cv2.boundingRect()**.
rectangle won't be minimum. It is found by the function **cv.boundingRect()**.
Let (x,y) be the top-left coordinate of the rectangle and (w,h) be its width and height.
@code{.py}
x,y,w,h = cv2.boundingRect(cnt)
cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)
x,y,w,h = cv.boundingRect(cnt)
cv.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)
@endcode
### 7.b. Rotated Rectangle
Here, bounding rectangle is drawn with minimum area, so it considers the rotation also. The function
used is **cv2.minAreaRect()**. It returns a Box2D structure which contains following detals - (
used is **cv.minAreaRect()**. It returns a Box2D structure which contains following detals - (
center (x,y), (width, height), angle of rotation ). But to draw this rectangle, we need 4 corners of
the rectangle. It is obtained by the function **cv2.boxPoints()**
the rectangle. It is obtained by the function **cv.boxPoints()**
@code{.py}
rect = cv2.minAreaRect(cnt)
box = cv2.boxPoints(rect)
rect = cv.minAreaRect(cnt)
box = cv.boxPoints(rect)
box = np.int0(box)
cv2.drawContours(img,[box],0,(0,0,255),2)
cv.drawContours(img,[box],0,(0,0,255),2)
@endcode
Both the rectangles are shown in a single image. Green rectangle shows the normal bounding rect. Red
rectangle is the rotated rect.
@@ -161,13 +161,13 @@ rectangle is the rotated rect.
8. Minimum Enclosing Circle
---------------------------
Next we find the circumcircle of an object using the function **cv2.minEnclosingCircle()**. It is a
Next we find the circumcircle of an object using the function **cv.minEnclosingCircle()**. It is a
circle which completely covers the object with minimum area.
@code{.py}
(x,y),radius = cv2.minEnclosingCircle(cnt)
(x,y),radius = cv.minEnclosingCircle(cnt)
center = (int(x),int(y))
radius = int(radius)
cv2.circle(img,center,radius,(0,255,0),2)
cv.circle(img,center,radius,(0,255,0),2)
@endcode
![image](images/circumcircle.png)
@@ -177,8 +177,8 @@ cv2.circle(img,center,radius,(0,255,0),2)
Next one is to fit an ellipse to an object. It returns the rotated rectangle in which the ellipse is
inscribed.
@code{.py}
ellipse = cv2.fitEllipse(cnt)
cv2.ellipse(img,ellipse,(0,255,0),2)
ellipse = cv.fitEllipse(cnt)
cv.ellipse(img,ellipse,(0,255,0),2)
@endcode
![image](images/fitellipse.png)
@@ -189,10 +189,10 @@ Similarly we can fit a line to a set of points. Below image contains a set of wh
approximate a straight line to it.
@code{.py}
rows,cols = img.shape[:2]
[vx,vy,x,y] = cv2.fitLine(cnt, cv2.DIST_L2,0,0.01,0.01)
[vx,vy,x,y] = cv.fitLine(cnt, cv.DIST_L2,0,0.01,0.01)
lefty = int((-x*vy/vx) + y)
righty = int(((cols-x)*vy/vx)+y)
cv2.line(img,(cols-1,righty),(0,lefty),(0,255,0),2)
cv.line(img,(cols-1,righty),(0,lefty),(0,255,0),2)
@endcode
![image](images/fitline.jpg)
@@ -15,7 +15,7 @@ It is the ratio of width to height of bounding rect of the object.
\f[Aspect \; Ratio = \frac{Width}{Height}\f]
@code{.py}
x,y,w,h = cv2.boundingRect(cnt)
x,y,w,h = cv.boundingRect(cnt)
aspect_ratio = float(w)/h
@endcode
@@ -26,8 +26,8 @@ Extent is the ratio of contour area to bounding rectangle area.
\f[Extent = \frac{Object \; Area}{Bounding \; Rectangle \; Area}\f]
@code{.py}
area = cv2.contourArea(cnt)
x,y,w,h = cv2.boundingRect(cnt)
area = cv.contourArea(cnt)
x,y,w,h = cv.boundingRect(cnt)
rect_area = w*h
extent = float(area)/rect_area
@endcode
@@ -39,9 +39,9 @@ Solidity is the ratio of contour area to its convex hull area.
\f[Solidity = \frac{Contour \; Area}{Convex \; Hull \; Area}\f]
@code{.py}
area = cv2.contourArea(cnt)
hull = cv2.convexHull(cnt)
hull_area = cv2.contourArea(hull)
area = cv.contourArea(cnt)
hull = cv.convexHull(cnt)
hull_area = cv.contourArea(hull)
solidity = float(area)/hull_area
@endcode
@@ -52,7 +52,7 @@ Equivalent Diameter is the diameter of the circle whose area is same as the cont
\f[Equivalent \; Diameter = \sqrt{\frac{4 \times Contour \; Area}{\pi}}\f]
@code{.py}
area = cv2.contourArea(cnt)
area = cv.contourArea(cnt)
equi_diameter = np.sqrt(4*area/np.pi)
@endcode
@@ -62,7 +62,7 @@ equi_diameter = np.sqrt(4*area/np.pi)
Orientation is the angle at which object is directed. Following method also gives the Major Axis and
Minor Axis lengths.
@code{.py}
(x,y),(MA,ma),angle = cv2.fitEllipse(cnt)
(x,y),(MA,ma),angle = cv.fitEllipse(cnt)
@endcode
6. Mask and Pixel Points
@@ -71,9 +71,9 @@ Minor Axis lengths.
In some cases, we may need all the points which comprises that object. It can be done as follows:
@code{.py}
mask = np.zeros(imgray.shape,np.uint8)
cv2.drawContours(mask,[cnt],0,255,-1)
cv.drawContours(mask,[cnt],0,255,-1)
pixelpoints = np.transpose(np.nonzero(mask))
#pixelpoints = cv2.findNonZero(mask)
#pixelpoints = cv.findNonZero(mask)
@endcode
Here, two methods, one using Numpy functions, next one using OpenCV function (last commented line)
are given to do the same. Results are also same, but with a slight difference. Numpy gives
@@ -85,7 +85,7 @@ basically the answers will be interchanged. Note that, **row = x** and **column
We can find these parameters using a mask image.
@code{.py}
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(imgray,mask = mask)
min_val, max_val, min_loc, max_loc = cv.minMaxLoc(imgray,mask = mask)
@endcode
8. Mean Color or Mean Intensity
@@ -94,7 +94,7 @@ min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(imgray,mask = mask)
Here, we can find the average color of an object. Or it can be average intensity of the object in
grayscale mode. We again use the same mask to do it.
@code{.py}
mean_val = cv2.mean(im,mask = mask)
mean_val = cv.mean(im,mask = mask)
@endcode
9. Extreme Points
@@ -6,7 +6,7 @@ Goal
- Understand what contours are.
- Learn to find contours, draw contours etc
- You will see these functions : **cv2.findContours()**, **cv2.drawContours()**
- You will see these functions : **cv.findContours()**, **cv.drawContours()**
What are contours?
------------------
@@ -24,14 +24,14 @@ detection and recognition.
Let's see how to find contours of a binary image:
@code{.py}
import numpy as np
import cv2
import cv2 as cv
im = cv2.imread('test.jpg')
imgray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(imgray, 127, 255, 0)
im2, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
im = cv.imread('test.jpg')
imgray = cv.cvtColor(im, cv.COLOR_BGR2GRAY)
ret, thresh = cv.threshold(imgray, 127, 255, 0)
im2, contours, hierarchy = cv.findContours(thresh, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
@endcode
See, there are three arguments in **cv2.findContours()** function, first one is source image, second
See, there are three arguments in **cv.findContours()** function, first one is source image, second
is contour retrieval mode, third is contour approximation method. And it outputs a modified image, the contours and
hierarchy. contours is a Python list of all the contours in the image. Each individual contour is a
Numpy array of (x,y) coordinates of boundary points of the object.
@@ -42,7 +42,7 @@ the values given to them in code sample will work fine for all images.
How to draw the contours?
-------------------------
To draw the contours, cv2.drawContours function is used. It can also be used to draw any shape
To draw the contours, cv.drawContours function is used. It can also be used to draw any shape
provided you have its boundary points. Its first argument is source image, second argument is the
contours which should be passed as a Python list, third argument is index of contours (useful when
drawing individual contour. To draw all contours, pass -1) and remaining arguments are color,
@@ -50,16 +50,16 @@ thickness etc.
* To draw all the contours in an image:
@code{.py}
cv2.drawContours(img, contours, -1, (0,255,0), 3)
cv.drawContours(img, contours, -1, (0,255,0), 3)
@endcode
* To draw an individual contour, say 4th contour:
@code{.py}
cv2.drawContours(img, contours, 3, (0,255,0), 3)
cv.drawContours(img, contours, 3, (0,255,0), 3)
@endcode
* But most of the time, below method will be useful:
@code{.py}
cnt = contours[4]
cv2.drawContours(img, [cnt], 0, (0,255,0), 3)
cv.drawContours(img, [cnt], 0, (0,255,0), 3)
@endcode
@note Last two methods are same, but when you go forward, you will see last one is more useful.
@@ -67,21 +67,21 @@ cv2.drawContours(img, [cnt], 0, (0,255,0), 3)
Contour Approximation Method
============================
This is the third argument in cv2.findContours function. What does it denote actually?
This is the third argument in cv.findContours function. What does it denote actually?
Above, we told that contours are the boundaries of a shape with same intensity. It stores the (x,y)
coordinates of the boundary of a shape. But does it store all the coordinates ? That is specified by
this contour approximation method.
If you pass cv2.CHAIN_APPROX_NONE, all the boundary points are stored. But actually do we need all
If you pass cv.CHAIN_APPROX_NONE, all the boundary points are stored. But actually do we need all
the points? For eg, you found the contour of a straight line. Do you need all the points on the line
to represent that line? No, we need just two end points of that line. This is what
cv2.CHAIN_APPROX_SIMPLE does. It removes all redundant points and compresses the contour, thereby
cv.CHAIN_APPROX_SIMPLE does. It removes all redundant points and compresses the contour, thereby
saving memory.
Below image of a rectangle demonstrate this technique. Just draw a circle on all the coordinates in
the contour array (drawn in blue color). First image shows points I got with cv2.CHAIN_APPROX_NONE
(734 points) and second image shows the one with cv2.CHAIN_APPROX_SIMPLE (only 4 points). See, how
the contour array (drawn in blue color). First image shows points I got with cv.CHAIN_APPROX_NONE
(734 points) and second image shows the one with cv.CHAIN_APPROX_SIMPLE (only 4 points). See, how
much memory it saves!!!
![image](images/none.jpg)
@@ -10,9 +10,9 @@ Theory
------
In the last few articles on contours, we have worked with several functions related to contours
provided by OpenCV. But when we found the contours in image using **cv2.findContours()** function,
we have passed an argument, **Contour Retrieval Mode**. We usually passed **cv2.RETR_LIST** or
**cv2.RETR_TREE** and it worked nice. But what does it actually mean ?
provided by OpenCV. But when we found the contours in image using **cv.findContours()** function,
we have passed an argument, **Contour Retrieval Mode**. We usually passed **cv.RETR_LIST** or
**cv.RETR_TREE** and it worked nice. But what does it actually mean ?
Also, in the output, we got three arrays, first is the image, second is our contours, and one more
output which we named as **hierarchy** (Please checkout the codes in previous articles). But we
@@ -23,7 +23,7 @@ That is what we are going to deal in this article.
### What is Hierarchy?
Normally we use the **cv2.findContours()** function to detect objects in an image, right ? Sometimes
Normally we use the **cv.findContours()** function to detect objects in an image, right ? Sometimes
objects are in different locations. But in some cases, some shapes are inside other shapes. Just
like nested figures. In this case, we call outer one as **parent** and inner one as **child**. This
way, contours in an image has some relationship to each other. And we can specify how one contour is
@@ -82,8 +82,8 @@ contour-3a. For contour-3a, it is contour-3 and so on.
@note If there is no child or parent, that field is taken as -1
So now we know about the hierarchy style used in OpenCV, we can check into Contour Retrieval Modes
in OpenCV with the help of same image given above. ie what do flags like cv2.RETR_LIST,
cv2.RETR_TREE, cv2.RETR_CCOMP, cv2.RETR_EXTERNAL etc mean?
in OpenCV with the help of same image given above. ie what do flags like cv.RETR_LIST,
cv.RETR_TREE, cv.RETR_CCOMP, cv.RETR_EXTERNAL etc mean?
Contour Retrieval Mode
----------------------
@@ -185,7 +185,7 @@ array([[[ 3, -1, 1, -1],
And this is the final guy, Mr.Perfect. It retrieves all the contours and creates a full family
hierarchy list. **It even tells, who is the grandpa, father, son, grandson and even beyond... :)**.
For examle, I took above image, rewrite the code for cv2.RETR_TREE, reorder the contours as per the
For examle, I took above image, rewrite the code for cv.RETR_TREE, reorder the contours as per the
result given by OpenCV and analyze it. Again, red letters give the contour number and green letters
give the hierarchy order.
@@ -17,11 +17,11 @@ Theory and Code
We saw what is convex hull in second chapter about contours. Any deviation of the object from this
hull can be considered as convexity defect.
OpenCV comes with a ready-made function to find this, **cv2.convexityDefects()**. A basic function
OpenCV comes with a ready-made function to find this, **cv.convexityDefects()**. A basic function
call would look like below:
@code{.py}
hull = cv2.convexHull(cnt,returnPoints = False)
defects = cv2.convexityDefects(cnt,hull)
hull = cv.convexHull(cnt,returnPoints = False)
defects = cv.convexityDefects(cnt,hull)
@endcode
@note Remember we have to pass returnPoints = False while finding convex hull, in order to find
@@ -33,29 +33,29 @@ line joining start point and end point, then draw a circle at the farthest point
three values returned are indices of cnt. So we have to bring those values from cnt.
@code{.py}
import cv2
import cv2 as cv
import numpy as np
img = cv2.imread('star.jpg')
img_gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret,thresh = cv2.threshold(img_gray, 127, 255,0)
im2,contours,hierarchy = cv2.findContours(thresh,2,1)
img = cv.imread('star.jpg')
img_gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
ret,thresh = cv.threshold(img_gray, 127, 255,0)
im2,contours,hierarchy = cv.findContours(thresh,2,1)
cnt = contours[0]
hull = cv2.convexHull(cnt,returnPoints = False)
defects = cv2.convexityDefects(cnt,hull)
hull = cv.convexHull(cnt,returnPoints = False)
defects = cv.convexityDefects(cnt,hull)
for i in range(defects.shape[0]):
s,e,f,d = defects[i,0]
start = tuple(cnt[s][0])
end = tuple(cnt[e][0])
far = tuple(cnt[f][0])
cv2.line(img,start,end,[0,255,0],2)
cv2.circle(img,far,5,[0,0,255],-1)
cv.line(img,start,end,[0,255,0],2)
cv.circle(img,far,5,[0,0,255],-1)
cv2.imshow('img',img)
cv2.waitKey(0)
cv2.destroyAllWindows()
cv.imshow('img',img)
cv.waitKey(0)
cv.destroyAllWindows()
@endcode
And see the result:
@@ -69,7 +69,7 @@ if point is on the contour.
For example, we can check the point (50,50) as follows:
@code{.py}
dist = cv2.pointPolygonTest(cnt,(50,50),True)
dist = cv.pointPolygonTest(cnt,(50,50),True)
@endcode
In the function, third argument is measureDist. If it is True, it finds the signed distance. If
False, it finds whether the point is inside or outside or on the contour (it returns +1, -1, 0
@@ -80,25 +80,25 @@ time consuming process. So, making it False gives about 2-3X speedup.
### 3. Match Shapes
OpenCV comes with a function **cv2.matchShapes()** which enables us to compare two shapes, or two
OpenCV comes with a function **cv.matchShapes()** which enables us to compare two shapes, or two
contours and returns a metric showing the similarity. The lower the result, the better match it is.
It is calculated based on the hu-moment values. Different measurement methods are explained in the
docs.
@code{.py}
import cv2
import cv2 as cv
import numpy as np
img1 = cv2.imread('star.jpg',0)
img2 = cv2.imread('star2.jpg',0)
img1 = cv.imread('star.jpg',0)
img2 = cv.imread('star2.jpg',0)
ret, thresh = cv2.threshold(img1, 127, 255,0)
ret, thresh2 = cv2.threshold(img2, 127, 255,0)
im2,contours,hierarchy = cv2.findContours(thresh,2,1)
ret, thresh = cv.threshold(img1, 127, 255,0)
ret, thresh2 = cv.threshold(img2, 127, 255,0)
im2,contours,hierarchy = cv.findContours(thresh,2,1)
cnt1 = contours[0]
im2,contours,hierarchy = cv2.findContours(thresh2,2,1)
im2,contours,hierarchy = cv.findContours(thresh2,2,1)
cnt2 = contours[0]
ret = cv2.matchShapes(cnt1,cnt2,1,0.0)
ret = cv.matchShapes(cnt1,cnt2,1,0.0)
print( ret )
@endcode
I tried matching shapes with different shapes given below:
@@ -115,7 +115,7 @@ See, even image rotation doesn't affect much on this comparison.
@sa [Hu-Moments](http://en.wikipedia.org/wiki/Image_moment#Rotation_invariant_moments) are seven
moments invariant to translation, rotation and scale. Seventh one is skew-invariant. Those values
can be found using **cv2.HuMoments()** function.
can be found using **cv.HuMoments()** function.
Additional Resources
====================
@@ -123,10 +123,10 @@ Additional Resources
Exercises
---------
-# Check the documentation for **cv2.pointPolygonTest()**, you can find a nice image in Red and
-# Check the documentation for **cv.pointPolygonTest()**, you can find a nice image in Red and
Blue color. It represents the distance from all pixels to the white curve on it. All pixels
inside curve is blue depending on the distance. Similarly outside points are red. Contour edges
are marked with White. So problem is simple. Write a code to create such a representation of
distance.
-# Compare images of digits or letters using **cv2.matchShapes()**. ( That would be a simple step
-# Compare images of digits or letters using **cv.matchShapes()**. ( That would be a simple step
towards OCR )
@@ -15,7 +15,7 @@ As in one-dimensional signals, images also can be filtered with various low-pass
high-pass filters(HPF) etc. LPF helps in removing noises, blurring the images etc. HPF filters helps
in finding edges in the images.
OpenCV provides a function **cv2.filter2D()** to convolve a kernel with an image. As an example, we
OpenCV provides a function **cv.filter2D()** to convolve a kernel with an image. As an example, we
will try an averaging filter on an image. A 5x5 averaging filter kernel will look like below:
\f[K = \frac{1}{25} \begin{bmatrix} 1 & 1 & 1 & 1 & 1 \\ 1 & 1 & 1 & 1 & 1 \\ 1 & 1 & 1 & 1 & 1 \\ 1 & 1 & 1 & 1 & 1 \\ 1 & 1 & 1 & 1 & 1 \end{bmatrix}\f]
@@ -24,14 +24,14 @@ Operation is like this: keep this kernel above a pixel, add all the 25 pixels be
take its average and replace the central pixel with the new average value. It continues this
operation for all the pixels in the image. Try this code and check the result:
@code{.py}
import cv2
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv2.imread('opencv_logo.png')
img = cv.imread('opencv_logo.png')
kernel = np.ones((5,5),np.float32)/25
dst = cv2.filter2D(img,-1,kernel)
dst = cv.filter2D(img,-1,kernel)
plt.subplot(121),plt.imshow(img),plt.title('Original')
plt.xticks([]), plt.yticks([])
@@ -55,23 +55,23 @@ blur the edges too). OpenCV provides mainly four types of blurring techniques.
This is done by convolving image with a normalized box filter. It simply takes the average of all
the pixels under kernel area and replace the central element. This is done by the function
**cv2.blur()** or **cv2.boxFilter()**. Check the docs for more details about the kernel. We should
**cv.blur()** or **cv.boxFilter()**. Check the docs for more details about the kernel. We should
specify the width and height of kernel. A 3x3 normalized box filter would look like below:
\f[K = \frac{1}{9} \begin{bmatrix} 1 & 1 & 1 \\ 1 & 1 & 1 \\ 1 & 1 & 1 \end{bmatrix}\f]
@note If you don't want to use normalized box filter, use **cv2.boxFilter()**. Pass an argument
@note If you don't want to use normalized box filter, use **cv.boxFilter()**. Pass an argument
normalize=False to the function.
Check a sample demo below with a kernel of 5x5 size:
@code{.py}
import cv2
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('opencv-logo-white.png')
img = cv.imread('opencv-logo-white.png')
blur = cv2.blur(img,(5,5))
blur = cv.blur(img,(5,5))
plt.subplot(121),plt.imshow(img),plt.title('Original')
plt.xticks([]), plt.yticks([])
@@ -86,17 +86,17 @@ Result:
### 2. Gaussian Blurring
In this, instead of box filter, gaussian kernel is used. It is done with the function,
**cv2.GaussianBlur()**. We should specify the width and height of kernel which should be positive
**cv.GaussianBlur()**. We should specify the width and height of kernel which should be positive
and odd. We also should specify the standard deviation in X and Y direction, sigmaX and sigmaY
respectively. If only sigmaX is specified, sigmaY is taken as same as sigmaX. If both are given as
zeros, they are calculated from kernel size. Gaussian blurring is highly effective in removing
gaussian noise from the image.
If you want, you can create a Gaussian kernel with the function, **cv2.getGaussianKernel()**.
If you want, you can create a Gaussian kernel with the function, **cv.getGaussianKernel()**.
The above code can be modified for Gaussian blurring:
@code{.py}
blur = cv2.GaussianBlur(img,(5,5),0)
blur = cv.GaussianBlur(img,(5,5),0)
@endcode
Result:
@@ -104,7 +104,7 @@ Result:
### 3. Median Blurring
Here, the function **cv2.medianBlur()** takes median of all the pixels under kernel area and central
Here, the function **cv.medianBlur()** takes median of all the pixels under kernel area and central
element is replaced with this median value. This is highly effective against salt-and-pepper noise
in the images. Interesting thing is that, in the above filters, central element is a newly
calculated value which may be a pixel value in the image or a new value. But in median blurring,
@@ -113,7 +113,7 @@ effectively. Its kernel size should be a positive odd integer.
In this demo, I added a 50% noise to our original image and applied median blur. Check the result:
@code{.py}
median = cv2.medianBlur(img,5)
median = cv.medianBlur(img,5)
@endcode
Result:
@@ -121,7 +121,7 @@ Result:
### 4. Bilateral Filtering
**cv2.bilateralFilter()** is highly effective in noise removal while keeping edges sharp. But the
**cv.bilateralFilter()** is highly effective in noise removal while keeping edges sharp. But the
operation is slower compared to other filters. We already saw that gaussian filter takes the a
neighbourhood around the pixel and find its gaussian weighted average. This gaussian filter is a
function of space alone, that is, nearby pixels are considered while filtering. It doesn't consider
@@ -136,7 +136,7 @@ pixels at edges will have large intensity variation.
Below samples shows use bilateral filter (For details on arguments, visit docs).
@code{.py}
blur = cv2.bilateralFilter(img,9,75,75)
blur = cv.bilateralFilter(img,9,75,75)
@endcode
Result:
@@ -6,35 +6,35 @@ Goals
- Learn to apply different geometric transformation to images like translation, rotation, affine
transformation etc.
- You will see these functions: **cv2.getPerspectiveTransform**
- You will see these functions: **cv.getPerspectiveTransform**
Transformations
---------------
OpenCV provides two transformation functions, **cv2.warpAffine** and **cv2.warpPerspective**, with
which you can have all kinds of transformations. **cv2.warpAffine** takes a 2x3 transformation
matrix while **cv2.warpPerspective** takes a 3x3 transformation matrix as input.
OpenCV provides two transformation functions, **cv.warpAffine** and **cv.warpPerspective**, with
which you can have all kinds of transformations. **cv.warpAffine** takes a 2x3 transformation
matrix while **cv.warpPerspective** takes a 3x3 transformation matrix as input.
### Scaling
Scaling is just resizing of the image. OpenCV comes with a function **cv2.resize()** for this
Scaling is just resizing of the image. OpenCV comes with a function **cv.resize()** for this
purpose. The size of the image can be specified manually, or you can specify the scaling factor.
Different interpolation methods are used. Preferable interpolation methods are **cv2.INTER_AREA**
for shrinking and **cv2.INTER_CUBIC** (slow) & **cv2.INTER_LINEAR** for zooming. By default,
interpolation method used is **cv2.INTER_LINEAR** for all resizing purposes. You can resize an
Different interpolation methods are used. Preferable interpolation methods are **cv.INTER_AREA**
for shrinking and **cv.INTER_CUBIC** (slow) & **cv.INTER_LINEAR** for zooming. By default,
interpolation method used is **cv.INTER_LINEAR** for all resizing purposes. You can resize an
input image either of following methods:
@code{.py}
import cv2
import numpy as np
import cv2 as cv
img = cv2.imread('messi5.jpg')
img = cv.imread('messi5.jpg')
res = cv2.resize(img,None,fx=2, fy=2, interpolation = cv2.INTER_CUBIC)
res = cv.resize(img,None,fx=2, fy=2, interpolation = cv.INTER_CUBIC)
#OR
height, width = img.shape[:2]
res = cv2.resize(img,(2*width, 2*height), interpolation = cv2.INTER_CUBIC)
res = cv.resize(img,(2*width, 2*height), interpolation = cv.INTER_CUBIC)
@endcode
### Translation
@@ -43,25 +43,25 @@ be \f$(t_x,t_y)\f$, you can create the transformation matrix \f$\textbf{M}\f$ as
\f[M = \begin{bmatrix} 1 & 0 & t_x \\ 0 & 1 & t_y \end{bmatrix}\f]
You can take make it into a Numpy array of type np.float32 and pass it into **cv2.warpAffine()**
You can take make it into a Numpy array of type np.float32 and pass it into **cv.warpAffine()**
function. See below example for a shift of (100,50):
@code{.py}
import cv2
import numpy as np
import cv2 as cv
img = cv2.imread('messi5.jpg',0)
img = cv.imread('messi5.jpg',0)
rows,cols = img.shape
M = np.float32([[1,0,100],[0,1,50]])
dst = cv2.warpAffine(img,M,(cols,rows))
dst = cv.warpAffine(img,M,(cols,rows))
cv2.imshow('img',dst)
cv2.waitKey(0)
cv2.destroyAllWindows()
cv.imshow('img',dst)
cv.waitKey(0)
cv.destroyAllWindows()
@endcode
**warning**
Third argument of the **cv2.warpAffine()** function is the size of the output image, which should
Third argument of the **cv.warpAffine()** function is the size of the output image, which should
be in the form of **(width, height)**. Remember width = number of columns, and height = number of
rows.
@@ -84,14 +84,14 @@ where:
\f[\begin{array}{l} \alpha = scale \cdot \cos \theta , \\ \beta = scale \cdot \sin \theta \end{array}\f]
To find this transformation matrix, OpenCV provides a function, **cv2.getRotationMatrix2D**. Check
To find this transformation matrix, OpenCV provides a function, **cv.getRotationMatrix2D**. Check
below example which rotates the image by 90 degree with respect to center without any scaling.
@code{.py}
img = cv2.imread('messi5.jpg',0)
img = cv.imread('messi5.jpg',0)
rows,cols = img.shape
M = cv2.getRotationMatrix2D((cols/2,rows/2),90,1)
dst = cv2.warpAffine(img,M,(cols,rows))
M = cv.getRotationMatrix2D((cols/2,rows/2),90,1)
dst = cv.warpAffine(img,M,(cols,rows))
@endcode
See the result:
@@ -101,20 +101,20 @@ See the result:
In affine transformation, all parallel lines in the original image will still be parallel in the
output image. To find the transformation matrix, we need three points from input image and their
corresponding locations in output image. Then **cv2.getAffineTransform** will create a 2x3 matrix
which is to be passed to **cv2.warpAffine**.
corresponding locations in output image. Then **cv.getAffineTransform** will create a 2x3 matrix
which is to be passed to **cv.warpAffine**.
Check below example, and also look at the points I selected (which are marked in Green color):
@code{.py}
img = cv2.imread('drawing.png')
img = cv.imread('drawing.png')
rows,cols,ch = img.shape
pts1 = np.float32([[50,50],[200,50],[50,200]])
pts2 = np.float32([[10,100],[200,50],[100,250]])
M = cv2.getAffineTransform(pts1,pts2)
M = cv.getAffineTransform(pts1,pts2)
dst = cv2.warpAffine(img,M,(cols,rows))
dst = cv.warpAffine(img,M,(cols,rows))
plt.subplot(121),plt.imshow(img),plt.title('Input')
plt.subplot(122),plt.imshow(dst),plt.title('Output')
@@ -130,20 +130,20 @@ For perspective transformation, you need a 3x3 transformation matrix. Straight l
straight even after the transformation. To find this transformation matrix, you need 4 points on the
input image and corresponding points on the output image. Among these 4 points, 3 of them should not
be collinear. Then transformation matrix can be found by the function
**cv2.getPerspectiveTransform**. Then apply **cv2.warpPerspective** with this 3x3 transformation
**cv.getPerspectiveTransform**. Then apply **cv.warpPerspective** with this 3x3 transformation
matrix.
See the code below:
@code{.py}
img = cv2.imread('sudoku.png')
img = cv.imread('sudoku.png')
rows,cols,ch = img.shape
pts1 = np.float32([[56,65],[368,52],[28,387],[389,390]])
pts2 = np.float32([[0,0],[300,0],[0,300],[300,300]])
M = cv2.getPerspectiveTransform(pts1,pts2)
M = cv.getPerspectiveTransform(pts1,pts2)
dst = cv2.warpPerspective(img,M,(300,300))
dst = cv.warpPerspective(img,M,(300,300))
plt.subplot(121),plt.imshow(img),plt.title('Input')
plt.subplot(122),plt.imshow(dst),plt.title('Output')
@@ -64,24 +64,24 @@ It is illustrated in below image (Image Courtesy: <http://www.cs.ru.ac.za/resear
Demo
----
Now we go for grabcut algorithm with OpenCV. OpenCV has the function, **cv2.grabCut()** for this. We
Now we go for grabcut algorithm with OpenCV. OpenCV has the function, **cv.grabCut()** for this. We
will see its arguments first:
- *img* - Input image
- *mask* - It is a mask image where we specify which areas are background, foreground or
probable background/foreground etc. It is done by the following flags, **cv2.GC_BGD,
cv2.GC_FGD, cv2.GC_PR_BGD, cv2.GC_PR_FGD**, or simply pass 0,1,2,3 to image.
probable background/foreground etc. It is done by the following flags, **cv.GC_BGD,
cv.GC_FGD, cv.GC_PR_BGD, cv.GC_PR_FGD**, or simply pass 0,1,2,3 to image.
- *rect* - It is the coordinates of a rectangle which includes the foreground object in the
format (x,y,w,h)
- *bdgModel*, *fgdModel* - These are arrays used by the algorithm internally. You just create
two np.float64 type zero arrays of size (1,65).
- *iterCount* - Number of iterations the algorithm should run.
- *mode* - It should be **cv2.GC_INIT_WITH_RECT** or **cv2.GC_INIT_WITH_MASK** or combined
- *mode* - It should be **cv.GC_INIT_WITH_RECT** or **cv.GC_INIT_WITH_MASK** or combined
which decides whether we are drawing rectangle or final touchup strokes.
First let's see with rectangular mode. We load the image, create a similar mask image. We create
*fgdModel* and *bgdModel*. We give the rectangle parameters. It's all straight-forward. Let the
algorithm run for 5 iterations. Mode should be *cv2.GC_INIT_WITH_RECT* since we are using
algorithm run for 5 iterations. Mode should be *cv.GC_INIT_WITH_RECT* since we are using
rectangle. Then run the grabcut. It modifies the mask image. In the new mask image, pixels will be
marked with four flags denoting background/foreground as specified above. So we modify the mask such
that all 0-pixels and 2-pixels are put to 0 (ie background) and all 1-pixels and 3-pixels are put to
@@ -89,17 +89,17 @@ that all 0-pixels and 2-pixels are put to 0 (ie background) and all 1-pixels and
segmented image.
@code{.py}
import numpy as np
import cv2
import cv2 as cv
from matplotlib import pyplot as plt
img = cv2.imread('messi5.jpg')
img = cv.imread('messi5.jpg')
mask = np.zeros(img.shape[:2],np.uint8)
bgdModel = np.zeros((1,65),np.float64)
fgdModel = np.zeros((1,65),np.float64)
rect = (50,50,450,290)
cv2.grabCut(img,mask,rect,bgdModel,fgdModel,5,cv2.GC_INIT_WITH_RECT)
cv.grabCut(img,mask,rect,bgdModel,fgdModel,5,cv.GC_INIT_WITH_RECT)
mask2 = np.where((mask==2)|(mask==0),0,1).astype('uint8')
img = img*mask2[:,:,np.newaxis]
@@ -122,14 +122,14 @@ remaining background with gray. Then loaded that mask image in OpenCV, edited or
got with corresponding values in newly added mask image. Check the code below:*
@code{.py}
# newmask is the mask image I manually labelled
newmask = cv2.imread('newmask.png',0)
newmask = cv.imread('newmask.png',0)
# whereever it is marked white (sure foreground), change mask=1
# whereever it is marked black (sure background), change mask=0
mask[newmask == 0] = 0
mask[newmask == 255] = 1
mask, bgdModel, fgdModel = cv2.grabCut(img,mask,None,bgdModel,fgdModel,5,cv2.GC_INIT_WITH_MASK)
mask, bgdModel, fgdModel = cv.grabCut(img,mask,None,bgdModel,fgdModel,5,cv.GC_INIT_WITH_MASK)
mask = np.where((mask==2)|(mask==0),0,1).astype('uint8')
img = img*mask[:,:,np.newaxis]
@@ -7,7 +7,7 @@ Goal
In this chapter, we will learn to:
- Find Image gradients, edges etc
- We will see following functions : **cv2.Sobel()**, **cv2.Scharr()**, **cv2.Laplacian()** etc
- We will see following functions : **cv.Sobel()**, **cv.Scharr()**, **cv.Laplacian()** etc
Theory
------
@@ -38,15 +38,15 @@ Code
Below code shows all operators in a single diagram. All kernels are of 5x5 size. Depth of output
image is passed -1 to get the result in np.uint8 type.
@code{.py}
import cv2
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv2.imread('dave.jpg',0)
img = cv.imread('dave.jpg',0)
laplacian = cv2.Laplacian(img,cv2.CV_64F)
sobelx = cv2.Sobel(img,cv2.CV_64F,1,0,ksize=5)
sobely = cv2.Sobel(img,cv2.CV_64F,0,1,ksize=5)
laplacian = cv.Laplacian(img,cv.CV_64F)
sobelx = cv.Sobel(img,cv.CV_64F,1,0,ksize=5)
sobely = cv.Sobel(img,cv.CV_64F,0,1,ksize=5)
plt.subplot(2,2,1),plt.imshow(img,cmap = 'gray')
plt.title('Original'), plt.xticks([]), plt.yticks([])
@@ -66,26 +66,26 @@ Result:
One Important Matter!
---------------------
In our last example, output datatype is cv2.CV_8U or np.uint8. But there is a slight problem with
In our last example, output datatype is cv.CV_8U or np.uint8. But there is a slight problem with
that. Black-to-White transition is taken as Positive slope (it has a positive value) while
White-to-Black transition is taken as a Negative slope (It has negative value). So when you convert
data to np.uint8, all negative slopes are made zero. In simple words, you miss that edge.
If you want to detect both edges, better option is to keep the output datatype to some higher forms,
like cv2.CV_16S, cv2.CV_64F etc, take its absolute value and then convert back to cv2.CV_8U.
like cv.CV_16S, cv.CV_64F etc, take its absolute value and then convert back to cv.CV_8U.
Below code demonstrates this procedure for a horizontal Sobel filter and difference in results.
@code{.py}
import cv2
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv2.imread('box.png',0)
img = cv.imread('box.png',0)
# Output dtype = cv2.CV_8U
sobelx8u = cv2.Sobel(img,cv2.CV_8U,1,0,ksize=5)
# Output dtype = cv.CV_8U
sobelx8u = cv.Sobel(img,cv.CV_8U,1,0,ksize=5)
# Output dtype = cv2.CV_64F. Then take its absolute and convert to cv2.CV_8U
sobelx64f = cv2.Sobel(img,cv2.CV_64F,1,0,ksize=5)
# Output dtype = cv.CV_64F. Then take its absolute and convert to cv.CV_8U
sobelx64f = cv.Sobel(img,cv.CV_64F,1,0,ksize=5)
abs_sobel64f = np.absolute(sobelx64f)
sobel_8u = np.uint8(abs_sobel64f)
@@ -23,7 +23,7 @@ will be useful in understanding further topics like Histogram Back-Projection.
2D Histogram in OpenCV
----------------------
It is quite simple and calculated using the same function, **cv2.calcHist()**. For color histograms,
It is quite simple and calculated using the same function, **cv.calcHist()**. For color histograms,
we need to convert the image from BGR to HSV. (Remember, for 1D histogram, we converted from BGR to
Grayscale). For 2D histograms, its parameters will be modified as follows:
@@ -34,13 +34,13 @@ Grayscale). For 2D histograms, its parameters will be modified as follows:
Now check the code below:
@code{.py}
import cv2
import numpy as np
import cv2 as cv
img = cv2.imread('home.jpg')
hsv = cv2.cvtColor(img,cv2.COLOR_BGR2HSV)
img = cv.imread('home.jpg')
hsv = cv.cvtColor(img,cv.COLOR_BGR2HSV)
hist = cv2.calcHist([hsv], [0, 1], None, [180, 256], [0, 180, 0, 256])
hist = cv.calcHist([hsv], [0, 1], None, [180, 256], [0, 180, 0, 256])
@endcode
That's it.
@@ -50,12 +50,12 @@ That's it.
Numpy also provides a specific function for this : **np.histogram2d()**. (Remember, for 1D histogram
we used **np.histogram()** ).
@code{.py}
import cv2
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv2.imread('home.jpg')
hsv = cv2.cvtColor(img,cv2.COLOR_BGR2HSV)
img = cv.imread('home.jpg')
hsv = cv.cvtColor(img,cv.COLOR_BGR2HSV)
hist, xbins, ybins = np.histogram2d(h.ravel(),s.ravel(),[180,256],[[0,180],[0,256]])
@endcode
@@ -67,10 +67,10 @@ Now we can check how to plot this color histogram.
Plotting 2D Histograms
----------------------
### Method - 1 : Using cv2.imshow()
### Method - 1 : Using cv.imshow()
The result we get is a two dimensional array of size 180x256. So we can show them as we do normally,
using cv2.imshow() function. It will be a grayscale image and it won't give much idea what colors
using cv.imshow() function. It will be a grayscale image and it won't give much idea what colors
are there, unless you know the Hue values of different colors.
### Method - 2 : Using Matplotlib
@@ -84,13 +84,13 @@ I prefer this method. It is simple and better.
Consider code:
@code{.py}
import cv2
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv2.imread('home.jpg')
hsv = cv2.cvtColor(img,cv2.COLOR_BGR2HSV)
hist = cv2.calcHist( [hsv], [0, 1], None, [180, 256], [0, 180, 0, 256] )
img = cv.imread('home.jpg')
hsv = cv.cvtColor(img,cv.COLOR_BGR2HSV)
hist = cv.calcHist( [hsv], [0, 1], None, [180, 256], [0, 180, 0, 256] )
plt.imshow(hist,interpolation = 'nearest')
plt.show()
@@ -33,82 +33,81 @@ Algorithm in Numpy
-# First we need to calculate the color histogram of both the object we need to find (let it be
'M') and the image where we are going to search (let it be 'I').
@code{.py}
import cv2
import numpy as np
from matplotlib import pyplot as plt
import cv2 as cvfrom matplotlib import pyplot as plt
#roi is the object or region of object we need to find
roi = cv2.imread('rose_red.png')
hsv = cv2.cvtColor(roi,cv2.COLOR_BGR2HSV)
roi = cv.imread('rose_red.png')
hsv = cv.cvtColor(roi,cv.COLOR_BGR2HSV)
#target is the image we search in
target = cv2.imread('rose.png')
hsvt = cv2.cvtColor(target,cv2.COLOR_BGR2HSV)
target = cv.imread('rose.png')
hsvt = cv.cvtColor(target,cv.COLOR_BGR2HSV)
# Find the histograms using calcHist. Can be done with np.histogram2d also
M = cv2.calcHist([hsv],[0, 1], None, [180, 256], [0, 180, 0, 256] )
I = cv2.calcHist([hsvt],[0, 1], None, [180, 256], [0, 180, 0, 256] )
M = cv.calcHist([hsv],[0, 1], None, [180, 256], [0, 180, 0, 256] )
I = cv.calcHist([hsvt],[0, 1], None, [180, 256], [0, 180, 0, 256] )
@endcode
2. Find the ratio \f$R = \frac{M}{I}\f$. Then backproject R, ie use R as palette and create a new image
with every pixel as its corresponding probability of being target. ie B(x,y) = R[h(x,y),s(x,y)]
where h is hue and s is saturation of the pixel at (x,y). After that apply the condition
\f$B(x,y) = min[B(x,y), 1]\f$.
@code{.py}
h,s,v = cv2.split(hsvt)
h,s,v = cv.split(hsvt)
B = R[h.ravel(),s.ravel()]
B = np.minimum(B,1)
B = B.reshape(hsvt.shape[:2])
@endcode
3. Now apply a convolution with a circular disc, \f$B = D \ast B\f$, where D is the disc kernel.
@code{.py}
disc = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(5,5))
cv2.filter2D(B,-1,disc,B)
disc = cv.getStructuringElement(cv.MORPH_ELLIPSE,(5,5))
cv.filter2D(B,-1,disc,B)
B = np.uint8(B)
cv2.normalize(B,B,0,255,cv2.NORM_MINMAX)
cv.normalize(B,B,0,255,cv.NORM_MINMAX)
@endcode
4. Now the location of maximum intensity gives us the location of object. If we are expecting a
region in the image, thresholding for a suitable value gives a nice result.
@code{.py}
ret,thresh = cv2.threshold(B,50,255,0)
ret,thresh = cv.threshold(B,50,255,0)
@endcode
That's it !!
Backprojection in OpenCV
------------------------
OpenCV provides an inbuilt function **cv2.calcBackProject()**. Its parameters are almost same as the
**cv2.calcHist()** function. One of its parameter is histogram which is histogram of the object and
OpenCV provides an inbuilt function **cv.calcBackProject()**. Its parameters are almost same as the
**cv.calcHist()** function. One of its parameter is histogram which is histogram of the object and
we have to find it. Also, the object histogram should be normalized before passing on to the
backproject function. It returns the probability image. Then we convolve the image with a disc
kernel and apply threshold. Below is my code and output :
@code{.py}
import cv2
import numpy as np
import cv2 as cv
roi = cv2.imread('rose_red.png')
hsv = cv2.cvtColor(roi,cv2.COLOR_BGR2HSV)
roi = cv.imread('rose_red.png')
hsv = cv.cvtColor(roi,cv.COLOR_BGR2HSV)
target = cv2.imread('rose.png')
hsvt = cv2.cvtColor(target,cv2.COLOR_BGR2HSV)
target = cv.imread('rose.png')
hsvt = cv.cvtColor(target,cv.COLOR_BGR2HSV)
# calculating object histogram
roihist = cv2.calcHist([hsv],[0, 1], None, [180, 256], [0, 180, 0, 256] )
roihist = cv.calcHist([hsv],[0, 1], None, [180, 256], [0, 180, 0, 256] )
# normalize histogram and apply backprojection
cv2.normalize(roihist,roihist,0,255,cv2.NORM_MINMAX)
dst = cv2.calcBackProject([hsvt],[0,1],roihist,[0,180,0,256],1)
cv.normalize(roihist,roihist,0,255,cv.NORM_MINMAX)
dst = cv.calcBackProject([hsvt],[0,1],roihist,[0,180,0,256],1)
# Now convolute with circular disc
disc = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(5,5))
cv2.filter2D(dst,-1,disc,dst)
disc = cv.getStructuringElement(cv.MORPH_ELLIPSE,(5,5))
cv.filter2D(dst,-1,disc,dst)
# threshold and binary AND
ret,thresh = cv2.threshold(dst,50,255,0)
thresh = cv2.merge((thresh,thresh,thresh))
res = cv2.bitwise_and(target,thresh)
ret,thresh = cv.threshold(dst,50,255,0)
thresh = cv.merge((thresh,thresh,thresh))
res = cv.bitwise_and(target,thresh)
res = np.vstack((target,thresh,res))
cv2.imwrite('res.jpg',res)
cv.imwrite('res.jpg',res)
@endcode
Below is one example I worked with. I used the region inside blue rectangle as sample object and I
wanted to extract the full ground.
@@ -7,7 +7,7 @@ Goal
Learn to
- Find histograms, using both OpenCV and Numpy functions
- Plot histograms, using OpenCV and Matplotlib functions
- You will see these functions : **cv2.calcHist()**, **np.histogram()** etc.
- You will see these functions : **cv.calcHist()**, **np.histogram()** etc.
Theory
------
@@ -57,10 +57,10 @@ intensity values.
### 1. Histogram Calculation in OpenCV
So now we use **cv2.calcHist()** function to find the histogram. Let's familiarize with the function
So now we use **cv.calcHist()** function to find the histogram. Let's familiarize with the function
and its parameters :
<center><em>cv2.calcHist(images, channels, mask, histSize, ranges[, hist[, accumulate]])</em></center>
<center><em>cv.calcHist(images, channels, mask, histSize, ranges[, hist[, accumulate]])</em></center>
-# images : it is the source image of type uint8 or float32. it should be given in square brackets,
ie, "[img]".
@@ -78,8 +78,8 @@ and its parameters :
So let's start with a sample image. Simply load an image in grayscale mode and find its full
histogram.
@code{.py}
img = cv2.imread('home.jpg',0)
hist = cv2.calcHist([img],[0],None,[256],[0,256])
img = cv.imread('home.jpg',0)
hist = cv.calcHist([img],[0],None,[256],[0,256])
@endcode
hist is a 256x1 array, each value corresponds to number of pixels in that image with its
corresponding pixel value.
@@ -118,11 +118,11 @@ Matplotlib comes with a histogram plotting function : matplotlib.pyplot.hist()
It directly finds the histogram and plot it. You need not use calcHist() or np.histogram() function
to find the histogram. See the code below:
@code{.py}
import cv2
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv2.imread('home.jpg',0)
img = cv.imread('home.jpg',0)
plt.hist(img.ravel(),256,[0,256]); plt.show()
@endcode
You will get a plot as below :
@@ -132,14 +132,14 @@ You will get a plot as below :
Or you can use normal plot of matplotlib, which would be good for BGR plot. For that, you need to
find the histogram data first. Try below code:
@code{.py}
import cv2
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv2.imread('home.jpg')
img = cv.imread('home.jpg')
color = ('b','g','r')
for i,col in enumerate(color):
histr = cv2.calcHist([img],[i],None,[256],[0,256])
histr = cv.calcHist([img],[i],None,[256],[0,256])
plt.plot(histr,color = col)
plt.xlim([0,256])
plt.show()
@@ -154,28 +154,28 @@ should be due to the sky)
### 2. Using OpenCV
Well, here you adjust the values of histograms along with its bin values to look like x,y
coordinates so that you can draw it using cv2.line() or cv2.polyline() function to generate same
coordinates so that you can draw it using cv.line() or cv.polyline() function to generate same
image as above. This is already available with OpenCV-Python2 official samples. Check the
code at samples/python/hist.py.
Application of Mask
-------------------
We used cv2.calcHist() to find the histogram of the full image. What if you want to find histograms
We used cv.calcHist() to find the histogram of the full image. What if you want to find histograms
of some regions of an image? Just create a mask image with white color on the region you want to
find histogram and black otherwise. Then pass this as the mask.
@code{.py}
img = cv2.imread('home.jpg',0)
img = cv.imread('home.jpg',0)
# create a mask
mask = np.zeros(img.shape[:2], np.uint8)
mask[100:300, 100:400] = 255
masked_img = cv2.bitwise_and(img,img,mask = mask)
masked_img = cv.bitwise_and(img,img,mask = mask)
# Calculate histogram with mask and without mask
# Check third argument for mask
hist_full = cv2.calcHist([img],[0],None,[256],[0,256])
hist_mask = cv2.calcHist([img],[0],mask,[256],[0,256])
hist_full = cv.calcHist([img],[0],None,[256],[0,256])
hist_mask = cv.calcHist([img],[0],mask,[256],[0,256])
plt.subplot(221), plt.imshow(img, 'gray')
plt.subplot(222), plt.imshow(mask,'gray')
@@ -26,11 +26,11 @@ a very good explanation with worked out examples, so that you would understand a
after reading that. Instead, here we will see its Numpy implementation. After that, we will see
OpenCV function.
@code{.py}
import cv2
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv2.imread('wiki.jpg',0)
img = cv.imread('wiki.jpg',0)
hist,bins = np.histogram(img.flatten(),256,[0,256])
@@ -76,15 +76,15 @@ histogram equalized to make them all with same lighting conditions.
Histograms Equalization in OpenCV
---------------------------------
OpenCV has a function to do this, **cv2.equalizeHist()**. Its input is just grayscale image and
OpenCV has a function to do this, **cv.equalizeHist()**. Its input is just grayscale image and
output is our histogram equalized image.
Below is a simple code snippet showing its usage for same image we used :
@code{.py}
img = cv2.imread('wiki.jpg',0)
equ = cv2.equalizeHist(img)
img = cv.imread('wiki.jpg',0)
equ = cv.equalizeHist(img)
res = np.hstack((img,equ)) #stacking images side-by-side
cv2.imwrite('res.png',res)
cv.imwrite('res.png',res)
@endcode
![image](images/equalization_opencv.jpg)
@@ -122,15 +122,15 @@ applied.
Below code snippet shows how to apply CLAHE in OpenCV:
@code{.py}
import numpy as np
import cv2
import cv2 as cv
img = cv2.imread('tsukuba_l.png',0)
img = cv.imread('tsukuba_l.png',0)
# create a CLAHE object (Arguments are optional).
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
clahe = cv.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
cl1 = clahe.apply(img)
cv2.imwrite('clahe_2.jpg',cl1)
cv.imwrite('clahe_2.jpg',cl1)
@endcode
See the result below and compare it with results above, especially the statue region:
@@ -6,7 +6,7 @@ Goal
In this chapter,
- We will learn to use Hough Transform to find circles in an image.
- We will see these functions: **cv2.HoughCircles()**
- We will see these functions: **cv.HoughCircles()**
Theory
------
@@ -17,29 +17,29 @@ equation, we can see we have 3 parameters, so we need a 3D accumulator for hough
would be highly ineffective. So OpenCV uses more trickier method, **Hough Gradient Method** which
uses the gradient information of edges.
The function we use here is **cv2.HoughCircles()**. It has plenty of arguments which are well
The function we use here is **cv.HoughCircles()**. It has plenty of arguments which are well
explained in the documentation. So we directly go to the code.
@code{.py}
import cv2
import numpy as np
import cv2 as cv
img = cv2.imread('opencv-logo-white.png',0)
img = cv2.medianBlur(img,5)
cimg = cv2.cvtColor(img,cv2.COLOR_GRAY2BGR)
img = cv.imread('opencv-logo-white.png',0)
img = cv.medianBlur(img,5)
cimg = cv.cvtColor(img,cv.COLOR_GRAY2BGR)
circles = cv2.HoughCircles(img,cv2.HOUGH_GRADIENT,1,20,
circles = cv.HoughCircles(img,cv.HOUGH_GRADIENT,1,20,
param1=50,param2=30,minRadius=0,maxRadius=0)
circles = np.uint16(np.around(circles))
for i in circles[0,:]:
# draw the outer circle
cv2.circle(cimg,(i[0],i[1]),i[2],(0,255,0),2)
cv.circle(cimg,(i[0],i[1]),i[2],(0,255,0),2)
# draw the center of the circle
cv2.circle(cimg,(i[0],i[1]),2,(0,0,255),3)
cv.circle(cimg,(i[0],i[1]),2,(0,0,255),3)
cv2.imshow('detected circles',cimg)
cv2.waitKey(0)
cv2.destroyAllWindows()
cv.imshow('detected circles',cimg)
cv.waitKey(0)
cv.destroyAllWindows()
@endcode
Result is shown below:
@@ -7,7 +7,7 @@ Goal
In this chapter,
- We will understand the concept of the Hough Transform.
- We will see how to use it to detect lines in an image.
- We will see the following functions: **cv2.HoughLines()**, **cv2.HoughLinesP()**
- We will see the following functions: **cv.HoughLines()**, **cv.HoughLinesP()**
Theory
------
@@ -62,7 +62,7 @@ denote they are the parameters of possible lines in the image. (Image courtesy:
Hough Transform in OpenCV
=========================
Everything explained above is encapsulated in the OpenCV function, **cv2.HoughLines()**. It simply returns an array of :math:(rho,
Everything explained above is encapsulated in the OpenCV function, **cv.HoughLines()**. It simply returns an array of :math:(rho,
theta)\` values. \f$\rho\f$ is measured in pixels and \f$\theta\f$ is measured in radians. First parameter,
Input image should be a binary image, so apply threshold or use canny edge detection before
applying hough transform. Second and third parameters are \f$\rho\f$ and \f$\theta\f$ accuracies
@@ -88,7 +88,7 @@ Hough Transform and Probabilistic Hough Transform in Hough space. (Image Courtes
OpenCV implementation is based on Robust Detection of Lines Using the Progressive Probabilistic
Hough Transform by Matas, J. and Galambos, C. and Kittler, J.V. @cite Matas00. The function used is
**cv2.HoughLinesP()**. It has two new arguments.
**cv.HoughLinesP()**. It has two new arguments.
- **minLineLength** - Minimum length of line. Line segments shorter than this are rejected.
- **maxLineGap** - Maximum allowed gap between line segments to treat them as a single line.
@@ -7,8 +7,8 @@ Goal
In this chapter,
- We will learn different morphological operations like Erosion, Dilation, Opening, Closing
etc.
- We will see different functions like : **cv2.erode()**, **cv2.dilate()**,
**cv2.morphologyEx()** etc.
- We will see different functions like : **cv.erode()**, **cv.dilate()**,
**cv.morphologyEx()** etc.
Theory
------
@@ -35,12 +35,12 @@ detach two connected objects etc.
Here, as an example, I would use a 5x5 kernel with full of ones. Let's see it how it works:
@code{.py}
import cv2
import cv2 as cv
import numpy as np
img = cv2.imread('j.png',0)
img = cv.imread('j.png',0)
kernel = np.ones((5,5),np.uint8)
erosion = cv2.erode(img,kernel,iterations = 1)
erosion = cv.erode(img,kernel,iterations = 1)
@endcode
Result:
@@ -54,7 +54,7 @@ Normally, in cases like noise removal, erosion is followed by dilation. Because,
white noises, but it also shrinks our object. So we dilate it. Since noise is gone, they won't come
back, but our object area increases. It is also useful in joining broken parts of an object.
@code{.py}
dilation = cv2.dilate(img,kernel,iterations = 1)
dilation = cv.dilate(img,kernel,iterations = 1)
@endcode
Result:
@@ -63,9 +63,9 @@ Result:
### 3. Opening
Opening is just another name of **erosion followed by dilation**. It is useful in removing noise, as
we explained above. Here we use the function, **cv2.morphologyEx()**
we explained above. Here we use the function, **cv.morphologyEx()**
@code{.py}
opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)
opening = cv.morphologyEx(img, cv.MORPH_OPEN, kernel)
@endcode
Result:
@@ -76,7 +76,7 @@ Result:
Closing is reverse of Opening, **Dilation followed by Erosion**. It is useful in closing small holes
inside the foreground objects, or small black points on the object.
@code{.py}
closing = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)
closing = cv.morphologyEx(img, cv.MORPH_CLOSE, kernel)
@endcode
Result:
@@ -88,7 +88,7 @@ It is the difference between dilation and erosion of an image.
The result will look like the outline of the object.
@code{.py}
gradient = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernel)
gradient = cv.morphologyEx(img, cv.MORPH_GRADIENT, kernel)
@endcode
Result:
@@ -99,7 +99,7 @@ Result:
It is the difference between input image and Opening of the image. Below example is done for a 9x9
kernel.
@code{.py}
tophat = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel)
tophat = cv.morphologyEx(img, cv.MORPH_TOPHAT, kernel)
@endcode
Result:
@@ -109,7 +109,7 @@ Result:
It is the difference between the closing of the input image and input image.
@code{.py}
blackhat = cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, kernel)
blackhat = cv.morphologyEx(img, cv.MORPH_BLACKHAT, kernel)
@endcode
Result:
@@ -120,11 +120,11 @@ Structuring Element
We manually created a structuring elements in the previous examples with help of Numpy. It is
rectangular shape. But in some cases, you may need elliptical/circular shaped kernels. So for this
purpose, OpenCV has a function, **cv2.getStructuringElement()**. You just pass the shape and size of
purpose, OpenCV has a function, **cv.getStructuringElement()**. You just pass the shape and size of
the kernel, you get the desired kernel.
@code{.py}
# Rectangular Kernel
>>> cv2.getStructuringElement(cv2.MORPH_RECT,(5,5))
>>> cv.getStructuringElement(cv.MORPH_RECT,(5,5))
array([[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
@@ -132,7 +132,7 @@ array([[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1]], dtype=uint8)
# Elliptical Kernel
>>> cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(5,5))
>>> cv.getStructuringElement(cv.MORPH_ELLIPSE,(5,5))
array([[0, 0, 1, 0, 0],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
@@ -140,7 +140,7 @@ array([[0, 0, 1, 0, 0],
[0, 0, 1, 0, 0]], dtype=uint8)
# Cross-shaped Kernel
>>> cv2.getStructuringElement(cv2.MORPH_CROSS,(5,5))
>>> cv.getStructuringElement(cv.MORPH_CROSS,(5,5))
array([[0, 0, 1, 0, 0],
[0, 0, 1, 0, 0],
[1, 1, 1, 1, 1],
@@ -7,7 +7,7 @@ Goal
In this chapter,
- We will learn about Image Pyramids
- We will use Image pyramids to create a new fruit, "Orapple"
- We will see these functions: **cv2.pyrUp()**, **cv2.pyrDown()**
- We will see these functions: **cv.pyrUp()**, **cv.pyrDown()**
Theory
------
@@ -28,18 +28,18 @@ contribution from 5 pixels in underlying level with gaussian weights. By doing s
image becomes \f$M/2 \times N/2\f$ image. So area reduces to one-fourth of original area. It is called
an Octave. The same pattern continues as we go upper in pyramid (ie, resolution decreases).
Similarly while expanding, area becomes 4 times in each level. We can find Gaussian pyramids using
**cv2.pyrDown()** and **cv2.pyrUp()** functions.
**cv.pyrDown()** and **cv.pyrUp()** functions.
@code{.py}
img = cv2.imread('messi5.jpg')
lower_reso = cv2.pyrDown(higher_reso)
img = cv.imread('messi5.jpg')
lower_reso = cv.pyrDown(higher_reso)
@endcode
Below is the 4 levels in an image pyramid.
![image](images/messipyr.jpg)
Now you can go down the image pyramid with **cv2.pyrUp()** function.
Now you can go down the image pyramid with **cv.pyrUp()** function.
@code{.py}
higher_reso2 = cv2.pyrUp(lower_reso)
higher_reso2 = cv.pyrUp(lower_reso)
@endcode
Remember, higher_reso2 is not equal to higher_reso, because once you decrease the resolution, you
loose the information. Below image is 3 level down the pyramid created from smallest image in
@@ -79,38 +79,38 @@ blending, Laplacian Pyramids etc. Simply it is done as follows:
Below is the full code. (For sake of simplicity, each step is done separately which may take more
memory. You can optimize it if you want so).
@code{.py}
import cv2
import cv2 as cv
import numpy as np,sys
A = cv2.imread('apple.jpg')
B = cv2.imread('orange.jpg')
A = cv.imread('apple.jpg')
B = cv.imread('orange.jpg')
# generate Gaussian pyramid for A
G = A.copy()
gpA = [G]
for i in xrange(6):
G = cv2.pyrDown(G)
G = cv.pyrDown(G)
gpA.append(G)
# generate Gaussian pyramid for B
G = B.copy()
gpB = [G]
for i in xrange(6):
G = cv2.pyrDown(G)
G = cv.pyrDown(G)
gpB.append(G)
# generate Laplacian Pyramid for A
lpA = [gpA[5]]
for i in xrange(5,0,-1):
GE = cv2.pyrUp(gpA[i])
L = cv2.subtract(gpA[i-1],GE)
GE = cv.pyrUp(gpA[i])
L = cv.subtract(gpA[i-1],GE)
lpA.append(L)
# generate Laplacian Pyramid for B
lpB = [gpB[5]]
for i in xrange(5,0,-1):
GE = cv2.pyrUp(gpB[i])
L = cv2.subtract(gpB[i-1],GE)
GE = cv.pyrUp(gpB[i])
L = cv.subtract(gpB[i-1],GE)
lpB.append(L)
# Now add left and right halves of images in each level
@@ -123,14 +123,14 @@ for la,lb in zip(lpA,lpB):
# now reconstruct
ls_ = LS[0]
for i in xrange(1,6):
ls_ = cv2.pyrUp(ls_)
ls_ = cv2.add(ls_, LS[i])
ls_ = cv.pyrUp(ls_)
ls_ = cv.add(ls_, LS[i])
# image with direct connecting each half
real = np.hstack((A[:,:cols/2],B[:,cols/2:]))
cv2.imwrite('Pyramid_blending2.jpg',ls_)
cv2.imwrite('Direct_blending.jpg',real)
cv.imwrite('Pyramid_blending2.jpg',ls_)
cv.imwrite('Direct_blending.jpg',real)
@endcode
Additional Resources
--------------------
@@ -6,24 +6,24 @@ Goals
In this chapter, you will learn
- To find objects in an image using Template Matching
- You will see these functions : **cv2.matchTemplate()**, **cv2.minMaxLoc()**
- You will see these functions : **cv.matchTemplate()**, **cv.minMaxLoc()**
Theory
------
Template Matching is a method for searching and finding the location of a template image in a larger
image. OpenCV comes with a function **cv2.matchTemplate()** for this purpose. It simply slides the
image. OpenCV comes with a function **cv.matchTemplate()** for this purpose. It simply slides the
template image over the input image (as in 2D convolution) and compares the template and patch of
input image under the template image. Several comparison methods are implemented in OpenCV. (You can
check docs for more details). It returns a grayscale image, where each pixel denotes how much does
the neighbourhood of that pixel match with template.
If input image is of size (WxH) and template image is of size (wxh), output image will have a size
of (W-w+1, H-h+1). Once you got the result, you can use **cv2.minMaxLoc()** function to find where
of (W-w+1, H-h+1). Once you got the result, you can use **cv.minMaxLoc()** function to find where
is the maximum/minimum value. Take it as the top-left corner of rectangle and take (w,h) as width
and height of the rectangle. That rectangle is your region of template.
@note If you are using cv2.TM_SQDIFF as comparison method, minimum value gives the best match.
@note If you are using cv.TM_SQDIFF as comparison method, minimum value gives the best match.
Template Matching in OpenCV
---------------------------
@@ -34,35 +34,35 @@ Here, as an example, we will search for Messi's face in his photo. So I created
We will try all the comparison methods so that we can see how their results look like:
@code{.py}
import cv2
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('messi5.jpg',0)
img = cv.imread('messi5.jpg',0)
img2 = img.copy()
template = cv2.imread('template.jpg',0)
template = cv.imread('template.jpg',0)
w, h = template.shape[::-1]
# All the 6 methods for comparison in a list
methods = ['cv2.TM_CCOEFF', 'cv2.TM_CCOEFF_NORMED', 'cv2.TM_CCORR',
'cv2.TM_CCORR_NORMED', 'cv2.TM_SQDIFF', 'cv2.TM_SQDIFF_NORMED']
methods = ['cv.TM_CCOEFF', 'cv.TM_CCOEFF_NORMED', 'cv.TM_CCORR',
'cv.TM_CCORR_NORMED', 'cv.TM_SQDIFF', 'cv.TM_SQDIFF_NORMED']
for meth in methods:
img = img2.copy()
method = eval(meth)
# Apply template Matching
res = cv2.matchTemplate(img,template,method)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
res = cv.matchTemplate(img,template,method)
min_val, max_val, min_loc, max_loc = cv.minMaxLoc(res)
# If the method is TM_SQDIFF or TM_SQDIFF_NORMED, take minimum
if method in [cv2.TM_SQDIFF, cv2.TM_SQDIFF_NORMED]:
if method in [cv.TM_SQDIFF, cv.TM_SQDIFF_NORMED]:
top_left = min_loc
else:
top_left = max_loc
bottom_right = (top_left[0] + w, top_left[1] + h)
cv2.rectangle(img,top_left, bottom_right, 255, 2)
cv.rectangle(img,top_left, bottom_right, 255, 2)
plt.subplot(121),plt.imshow(res,cmap = 'gray')
plt.title('Matching Result'), plt.xticks([]), plt.yticks([])
@@ -74,56 +74,56 @@ for meth in methods:
@endcode
See the results below:
- cv2.TM_CCOEFF
- cv.TM_CCOEFF
![image](images/template_ccoeff_1.jpg)
- cv2.TM_CCOEFF_NORMED
- cv.TM_CCOEFF_NORMED
![image](images/template_ccoeffn_2.jpg)
- cv2.TM_CCORR
- cv.TM_CCORR
![image](images/template_ccorr_3.jpg)
- cv2.TM_CCORR_NORMED
- cv.TM_CCORR_NORMED
![image](images/template_ccorrn_4.jpg)
- cv2.TM_SQDIFF
- cv.TM_SQDIFF
![image](images/template_sqdiff_5.jpg)
- cv2.TM_SQDIFF_NORMED
- cv.TM_SQDIFF_NORMED
![image](images/template_sqdiffn_6.jpg)
You can see that the result using **cv2.TM_CCORR** is not good as we expected.
You can see that the result using **cv.TM_CCORR** is not good as we expected.
Template Matching with Multiple Objects
---------------------------------------
In the previous section, we searched image for Messi's face, which occurs only once in the image.
Suppose you are searching for an object which has multiple occurances, **cv2.minMaxLoc()** won't
Suppose you are searching for an object which has multiple occurances, **cv.minMaxLoc()** won't
give you all the locations. In that case, we will use thresholding. So in this example, we will use
a screenshot of the famous game **Mario** and we will find the coins in it.
@code{.py}
import cv2
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
img_rgb = cv2.imread('mario.png')
img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY)
template = cv2.imread('mario_coin.png',0)
img_rgb = cv.imread('mario.png')
img_gray = cv.cvtColor(img_rgb, cv.COLOR_BGR2GRAY)
template = cv.imread('mario_coin.png',0)
w, h = template.shape[::-1]
res = cv2.matchTemplate(img_gray,template,cv2.TM_CCOEFF_NORMED)
res = cv.matchTemplate(img_gray,template,cv.TM_CCOEFF_NORMED)
threshold = 0.8
loc = np.where( res >= threshold)
for pt in zip(*loc[::-1]):
cv2.rectangle(img_rgb, pt, (pt[0] + w, pt[1] + h), (0,0,255), 2)
cv.rectangle(img_rgb, pt, (pt[0] + w, pt[1] + h), (0,0,255), 2)
cv2.imwrite('res.png',img_rgb)
cv.imwrite('res.png',img_rgb)
@endcode
Result:
@@ -6,24 +6,24 @@ Goal
- In this tutorial, you will learn Simple thresholding, Adaptive thresholding, Otsu's thresholding
etc.
- You will learn these functions : **cv2.threshold**, **cv2.adaptiveThreshold** etc.
- You will learn these functions : **cv.threshold**, **cv.adaptiveThreshold** etc.
Simple Thresholding
-------------------
Here, the matter is straight forward. If pixel value is greater than a threshold value, it is
assigned one value (may be white), else it is assigned another value (may be black). The function
used is **cv2.threshold**. First argument is the source image, which **should be a grayscale
used is **cv.threshold**. First argument is the source image, which **should be a grayscale
image**. Second argument is the threshold value which is used to classify the pixel values. Third
argument is the maxVal which represents the value to be given if pixel value is more than (sometimes
less than) the threshold value. OpenCV provides different styles of thresholding and it is decided
by the fourth parameter of the function. Different types are:
- cv2.THRESH_BINARY
- cv2.THRESH_BINARY_INV
- cv2.THRESH_TRUNC
- cv2.THRESH_TOZERO
- cv2.THRESH_TOZERO_INV
- cv.THRESH_BINARY
- cv.THRESH_BINARY_INV
- cv.THRESH_TRUNC
- cv.THRESH_TOZERO
- cv.THRESH_TOZERO_INV
Documentation clearly explain what each type is meant for. Please check out the documentation.
@@ -32,16 +32,16 @@ our **thresholded image**.
Code :
@code{.py}
import cv2
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('gradient.png',0)
ret,thresh1 = cv2.threshold(img,127,255,cv2.THRESH_BINARY)
ret,thresh2 = cv2.threshold(img,127,255,cv2.THRESH_BINARY_INV)
ret,thresh3 = cv2.threshold(img,127,255,cv2.THRESH_TRUNC)
ret,thresh4 = cv2.threshold(img,127,255,cv2.THRESH_TOZERO)
ret,thresh5 = cv2.threshold(img,127,255,cv2.THRESH_TOZERO_INV)
img = cv.imread('gradient.png',0)
ret,thresh1 = cv.threshold(img,127,255,cv.THRESH_BINARY)
ret,thresh2 = cv.threshold(img,127,255,cv.THRESH_BINARY_INV)
ret,thresh3 = cv.threshold(img,127,255,cv.THRESH_TRUNC)
ret,thresh4 = cv.threshold(img,127,255,cv.THRESH_TOZERO)
ret,thresh5 = cv.threshold(img,127,255,cv.THRESH_TOZERO_INV)
titles = ['Original Image','BINARY','BINARY_INV','TRUNC','TOZERO','TOZERO_INV']
images = [img, thresh1, thresh2, thresh3, thresh4, thresh5]
@@ -72,8 +72,8 @@ results for images with varying illumination.
It has three special input params and only one output argument.
**Adaptive Method** - It decides how thresholding value is calculated.
- cv2.ADAPTIVE_THRESH_MEAN_C : threshold value is the mean of neighbourhood area.
- cv2.ADAPTIVE_THRESH_GAUSSIAN_C : threshold value is the weighted sum of neighbourhood
- cv.ADAPTIVE_THRESH_MEAN_C : threshold value is the mean of neighbourhood area.
- cv.ADAPTIVE_THRESH_GAUSSIAN_C : threshold value is the weighted sum of neighbourhood
values where weights are a gaussian window.
**Block Size** - It decides the size of neighbourhood area.
@@ -83,18 +83,18 @@ It has three special input params and only one output argument.
Below piece of code compares global thresholding and adaptive thresholding for an image with varying
illumination:
@code{.py}
import cv2
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('sudoku.png',0)
img = cv2.medianBlur(img,5)
img = cv.imread('sudoku.png',0)
img = cv.medianBlur(img,5)
ret,th1 = cv2.threshold(img,127,255,cv2.THRESH_BINARY)
th2 = cv2.adaptiveThreshold(img,255,cv2.ADAPTIVE_THRESH_MEAN_C,\
cv2.THRESH_BINARY,11,2)
th3 = cv2.adaptiveThreshold(img,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,\
cv2.THRESH_BINARY,11,2)
ret,th1 = cv.threshold(img,127,255,cv.THRESH_BINARY)
th2 = cv.adaptiveThreshold(img,255,cv.ADAPTIVE_THRESH_MEAN_C,\
cv.THRESH_BINARY,11,2)
th3 = cv.adaptiveThreshold(img,255,cv.ADAPTIVE_THRESH_GAUSSIAN_C,\
cv.THRESH_BINARY,11,2)
titles = ['Original Image', 'Global Thresholding (v = 127)',
'Adaptive Mean Thresholding', 'Adaptive Gaussian Thresholding']
@@ -124,7 +124,7 @@ That is what Otsu binarization does. So in simple words, it automatically calcul
value from image histogram for a bimodal image. (For images which are not bimodal, binarization
wont be accurate.)
For this, our cv2.threshold() function is used, but pass an extra flag, cv2.THRESH_OTSU. **For
For this, our cv.threshold() function is used, but pass an extra flag, cv.THRESH_OTSU. **For
threshold value, simply pass zero**. Then the algorithm finds the optimal threshold value and
returns you as the second output, retVal. If Otsu thresholding is not used, retVal is same as the
threshold value you used.
@@ -134,21 +134,21 @@ for a value of 127. In second case, I applied Otsus thresholding directly. In
filtered image with a 5x5 gaussian kernel to remove the noise, then applied Otsu thresholding. See
how noise filtering improves the result.
@code{.py}
import cv2
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('noisy2.png',0)
img = cv.imread('noisy2.png',0)
# global thresholding
ret1,th1 = cv2.threshold(img,127,255,cv2.THRESH_BINARY)
ret1,th1 = cv.threshold(img,127,255,cv.THRESH_BINARY)
# Otsu's thresholding
ret2,th2 = cv2.threshold(img,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
ret2,th2 = cv.threshold(img,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)
# Otsu's thresholding after Gaussian filtering
blur = cv2.GaussianBlur(img,(5,5),0)
ret3,th3 = cv2.threshold(blur,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
blur = cv.GaussianBlur(img,(5,5),0)
ret3,th3 = cv.threshold(blur,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)
# plot all the images and their histograms
images = [img, 0, th1,
@@ -188,11 +188,11 @@ where
It actually finds a value of t which lies in between two peaks such that variances to both classes
are minimum. It can be simply implemented in Python as follows:
@code{.py}
img = cv2.imread('noisy2.png',0)
blur = cv2.GaussianBlur(img,(5,5),0)
img = cv.imread('noisy2.png',0)
blur = cv.GaussianBlur(img,(5,5),0)
# find normalized_histogram, and its cumulative distribution function
hist = cv2.calcHist([blur],[0],None,[256],[0,256])
hist = cv.calcHist([blur],[0],None,[256],[0,256])
hist_norm = hist.ravel()/hist.max()
Q = hist_norm.cumsum()
@@ -217,7 +217,7 @@ for i in xrange(1,256):
thresh = i
# find otsu's threshold value with OpenCV function
ret, otsu = cv2.threshold(blur,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
ret, otsu = cv.threshold(blur,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)
print( "{} {}".format(thresh,ret) )
@endcode
*(Some of the functions may be new here, but we will cover them in coming chapters)*
@@ -8,7 +8,7 @@ In this section, we will learn
- To find the Fourier Transform of images using OpenCV
- To utilize the FFT functions available in Numpy
- Some applications of Fourier Transform
- We will see following functions : **cv2.dft()**, **cv2.idft()** etc
- We will see following functions : **cv.dft()**, **cv.idft()** etc
Theory
------
@@ -50,11 +50,11 @@ you want to bring it to center, you need to shift the result by \f$\frac{N}{2}\f
directions. This is simply done by the function, **np.fft.fftshift()**. (It is more easier to
analyze). Once you found the frequency transform, you can find the magnitude spectrum.
@code{.py}
import cv2
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('messi5.jpg',0)
img = cv.imread('messi5.jpg',0)
f = np.fft.fft2(img)
fshift = np.fft.fftshift(f)
magnitude_spectrum = 20*np.log(np.abs(fshift))
@@ -112,21 +112,21 @@ Better option is Gaussian Windows.
Fourier Transform in OpenCV
---------------------------
OpenCV provides the functions **cv2.dft()** and **cv2.idft()** for this. It returns the same result
OpenCV provides the functions **cv.dft()** and **cv.idft()** for this. It returns the same result
as previous, but with two channels. First channel will have the real part of the result and second
channel will have the imaginary part of the result. The input image should be converted to
np.float32 first. We will see how to do it.
@code{.py}
import numpy as np
import cv2
import cv2 as cv
from matplotlib import pyplot as plt
img = cv2.imread('messi5.jpg',0)
img = cv.imread('messi5.jpg',0)
dft = cv2.dft(np.float32(img),flags = cv2.DFT_COMPLEX_OUTPUT)
dft = cv.dft(np.float32(img),flags = cv.DFT_COMPLEX_OUTPUT)
dft_shift = np.fft.fftshift(dft)
magnitude_spectrum = 20*np.log(cv2.magnitude(dft_shift[:,:,0],dft_shift[:,:,1]))
magnitude_spectrum = 20*np.log(cv.magnitude(dft_shift[:,:,0],dft_shift[:,:,1]))
plt.subplot(121),plt.imshow(img, cmap = 'gray')
plt.title('Input Image'), plt.xticks([]), plt.yticks([])
@@ -135,7 +135,7 @@ plt.title('Magnitude Spectrum'), plt.xticks([]), plt.yticks([])
plt.show()
@endcode
@note You can also use **cv2.cartToPolar()** which returns both magnitude and phase in a single shot
@note You can also use **cv.cartToPolar()** which returns both magnitude and phase in a single shot
So, now we have to do inverse DFT. In previous session, we created a HPF, this time we will see how
to remove high frequency contents in the image, ie we apply LPF to image. It actually blurs the
@@ -153,8 +153,8 @@ mask[crow-30:crow+30, ccol-30:ccol+30] = 1
# apply mask and inverse DFT
fshift = dft_shift*mask
f_ishift = np.fft.ifftshift(fshift)
img_back = cv2.idft(f_ishift)
img_back = cv2.magnitude(img_back[:,:,0],img_back[:,:,1])
img_back = cv.idft(f_ishift)
img_back = cv.magnitude(img_back[:,:,0],img_back[:,:,1])
plt.subplot(121),plt.imshow(img, cmap = 'gray')
plt.title('Input Image'), plt.xticks([]), plt.yticks([])
@@ -166,7 +166,7 @@ See the result:
![image](images/fft4.jpg)
@note As usual, OpenCV functions **cv2.dft()** and **cv2.idft()** are faster than Numpy
@note As usual, OpenCV functions **cv.dft()** and **cv.idft()** are faster than Numpy
counterparts. But Numpy functions are more user-friendly. For more details about performance issues,
see below section.
@@ -180,23 +180,23 @@ the array to any optimal size (by padding zeros) before finding DFT. For OpenCV,
manually pad zeros. But for Numpy, you specify the new size of FFT calculation, and it will
automatically pad zeros for you.
So how do we find this optimal size ? OpenCV provides a function, **cv2.getOptimalDFTSize()** for
this. It is applicable to both **cv2.dft()** and **np.fft.fft2()**. Let's check their performance
So how do we find this optimal size ? OpenCV provides a function, **cv.getOptimalDFTSize()** for
this. It is applicable to both **cv.dft()** and **np.fft.fft2()**. Let's check their performance
using IPython magic command %timeit.
@code{.py}
In [16]: img = cv2.imread('messi5.jpg',0)
In [16]: img = cv.imread('messi5.jpg',0)
In [17]: rows,cols = img.shape
In [18]: print("{} {}".format(rows,cols))
342 548
In [19]: nrows = cv2.getOptimalDFTSize(rows)
In [20]: ncols = cv2.getOptimalDFTSize(cols)
In [19]: nrows = cv.getOptimalDFTSize(rows)
In [20]: ncols = cv.getOptimalDFTSize(cols)
In [21]: print("{} {}".format(nrows,ncols))
360 576
@endcode
See, the size (342,548) is modified to (360, 576). Now let's pad it with zeros (for OpenCV) and find
their DFT calculation performance. You can do it by creating a new big zero array and copy the data
to it, or use **cv2.copyMakeBorder()**.
to it, or use **cv.copyMakeBorder()**.
@code{.py}
nimg = np.zeros((nrows,ncols))
nimg[:rows,:cols] = img
@@ -205,8 +205,8 @@ OR:
@code{.py}
right = ncols - cols
bottom = nrows - rows
bordertype = cv2.BORDER_CONSTANT #just to avoid line breakup in PDF file
nimg = cv2.copyMakeBorder(img,0,bottom,0,right,bordertype, value = 0)
bordertype = cv.BORDER_CONSTANT #just to avoid line breakup in PDF file
nimg = cv.copyMakeBorder(img,0,bottom,0,right,bordertype, value = 0)
@endcode
Now we calculate the DFT performance comparison of Numpy function:
@code{.py}
@@ -217,9 +217,9 @@ In [23]: %timeit fft2 = np.fft.fft2(img,[nrows,ncols])
@endcode
It shows a 4x speedup. Now we will try the same with OpenCV functions.
@code{.py}
In [24]: %timeit dft1= cv2.dft(np.float32(img),flags=cv2.DFT_COMPLEX_OUTPUT)
In [24]: %timeit dft1= cv.dft(np.float32(img),flags=cv.DFT_COMPLEX_OUTPUT)
100 loops, best of 3: 13.5 ms per loop
In [27]: %timeit dft2= cv2.dft(np.float32(nimg),flags=cv2.DFT_COMPLEX_OUTPUT)
In [27]: %timeit dft2= cv.dft(np.float32(nimg),flags=cv.DFT_COMPLEX_OUTPUT)
100 loops, best of 3: 3.11 ms per loop
@endcode
It also shows a 4x speed-up. You can also see that OpenCV functions are around 3x faster than Numpy
@@ -232,7 +232,7 @@ A similar question was asked in a forum. The question is, why Laplacian is a hig
Sobel is a HPF? etc. And the first answer given to it was in terms of Fourier Transform. Just take
the fourier transform of Laplacian for some higher size of FFT. Analyze it:
@code{.py}
import cv2
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
@@ -240,7 +240,7 @@ from matplotlib import pyplot as plt
mean_filter = np.ones((3,3))
# creating a guassian filter
x = cv2.getGaussianKernel(5,10)
x = cv.getGaussianKernel(5,10)
gaussian = x*x.T
# different edge detecting filters
@@ -6,7 +6,7 @@ Goal
In this chapter,
- We will learn to use marker-based image segmentation using watershed algorithm
- We will see: **cv2.watershed()**
- We will see: **cv.watershed()**
Theory
------
@@ -45,12 +45,12 @@ We start with finding an approximate estimate of the coins. For that, we can use
binarization.
@code{.py}
import numpy as np
import cv2
import cv2 as cv
from matplotlib import pyplot as plt
img = cv2.imread('coins.png')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
img = cv.imread('coins.png')
gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
ret, thresh = cv.threshold(gray,0,255,cv.THRESH_BINARY_INV+cv.THRESH_OTSU)
@endcode
Result:
@@ -78,18 +78,18 @@ obtained from subtracting sure_fg area from sure_bg area.
@code{.py}
# noise removal
kernel = np.ones((3,3),np.uint8)
opening = cv2.morphologyEx(thresh,cv2.MORPH_OPEN,kernel, iterations = 2)
opening = cv.morphologyEx(thresh,cv.MORPH_OPEN,kernel, iterations = 2)
# sure background area
sure_bg = cv2.dilate(opening,kernel,iterations=3)
sure_bg = cv.dilate(opening,kernel,iterations=3)
# Finding sure foreground area
dist_transform = cv2.distanceTransform(opening,cv2.DIST_L2,5)
ret, sure_fg = cv2.threshold(dist_transform,0.7*dist_transform.max(),255,0)
dist_transform = cv.distanceTransform(opening,cv.DIST_L2,5)
ret, sure_fg = cv.threshold(dist_transform,0.7*dist_transform.max(),255,0)
# Finding unknown region
sure_fg = np.uint8(sure_fg)
unknown = cv2.subtract(sure_bg,sure_fg)
unknown = cv.subtract(sure_bg,sure_fg)
@endcode
See the result. In the thresholded image, we get some regions of coins which we are sure of coins
and they are detached now. (In some cases, you may be interested in only foreground segmentation,
@@ -103,7 +103,7 @@ Now we know for sure which are region of coins, which are background and all. So
(it is an array of same size as that of original image, but with int32 datatype) and label the
regions inside it. The regions we know for sure (whether foreground or background) are labelled with
any positive integers, but different integers, and the area we don't know for sure are just left as
zero. For this we use **cv2.connectedComponents()**. It labels background of the image with 0, then
zero. For this we use **cv.connectedComponents()**. It labels background of the image with 0, then
other objects are labelled with integers starting from 1.
But we know that if background is marked with 0, watershed will consider it as unknown area. So we
@@ -111,7 +111,7 @@ want to mark it with different integer. Instead, we will mark unknown region, de
with 0.
@code{.py}
# Marker labelling
ret, markers = cv2.connectedComponents(sure_fg)
ret, markers = cv.connectedComponents(sure_fg)
# Add one to all labels so that sure background is not 0, but 1
markers = markers+1
@@ -128,7 +128,7 @@ compared to unknown region.
Now our marker is ready. It is time for final step, apply watershed. Then marker image will be
modified. The boundary region will be marked with -1.
@code{.py}
markers = cv2.watershed(img,markers)
markers = cv.watershed(img,markers)
img[markers == -1] = [255,0,0]
@endcode
See the result below. For some coins, the region where they touch are segmented properly and for