Learning OpenCV 5 Computer Vision with Python Fourth Edition Joseph Howse & Joe Minichino all chapter instant download
Learning OpenCV 5 Computer Vision with Python Fourth Edition Joseph Howse & Joe Minichino all chapter instant download
https://ebookfinal.com/download/learning-opencv-computer-vision-with-
the-opencv-library-1st-edition-gary-bradski/
https://ebookfinal.com/download/learning-image-processing-with-
opencv-1st-edition-gloria-bueno-garcia-oscar-deniz-suarez/
https://ebookfinal.com/download/deep-learning-with-python-2nd-edition-
francois-chollet/
Architectures for computer vision from algorithm to chip
with Verilog Jeong
https://ebookfinal.com/download/architectures-for-computer-vision-
from-algorithm-to-chip-with-verilog-jeong/
https://ebookfinal.com/download/thoughtful-machine-learning-with-
python-early-release-matthew-kirk/
https://ebookfinal.com/download/fish-quality-control-by-computer-
vision-first-edition-pau/
Learning OpenCV 5 Computer Vision with Python
Fourth Edition Joseph Howse & Joe Minichino Digital
Instant Download
Author(s): Joseph Howse & Joe Minichino
ISBN(s): 9781803230221, 1803230223
File Details: PDF, 18.83 MB
Year: 2023
Language: english
Learning OpenCV 5 Computer
Vision with Python
Copyright © 2022 Packt Publishing
Every effort has been made in the preparation of this book to ensure the
accuracy of the information presented. However, the information contained
in this book is sold without warranty, either express or implied. Neither the
author, nor Packt Publishing, and its dealers and distributors will be held
liable for any damages caused or alleged to be caused directly or indirectly
by this book.
Livery Place
35 Livery Street
Birmingham
B3 2PB, UK
ISBN: 978-1-80323-022-1
www.packt.com
Table of Contents
1. Learning OpenCV 5 Computer Vision with Python, Fourth Edition:
Tackle tools, techniques, and algorithms for computer vision and
machine learning
2. 5 Detecting and Recognizing Faces
I. Technical requirements
II. Conceptualizing Haar cascades
III. Getting Haar cascade data
IV. Using OpenCV to perform face detection
i. Performing face detection on a still image
ii. Performing face detection on a video
V. Performing face recognition
i. Generating the data for face recognition
ii. Choosing a face recognition algorithm
iii. Loading the training data for face recognition
iv. Performing face recognition with Eigenfaces
v. Performing face recognition with Fisherfaces
vi. Performing face recognition with LBPH
vii. Discarding results based on the confidence score
VI. Swapping faces in infrared
i. Modifying the application's loop
ii. Masking a copy operation
VII. Summary
VIII. Join our book community on Discord
3. 6 Retrieving Images and Searching Using Image Descriptors
I. Technical requirements
II. Understanding types of feature detection and matching
i. Defining features
III. Detecting Harris corners
IV. Detecting DoG features and extracting SIFT descriptors
i. Anatomy of a keypoint
V. Detecting Fast Hessian features and extracting SURF descriptors
VI. Using ORB with FAST features and BRIEF descriptors
i. FAST
ii. BRIEF
iii. Brute-force matching
iv. Matching a logo in two images
VII. Filtering matches using K-Nearest Neighbors and the ratio test
VIII. Matching with FLANN
IX. Finding homography with FLANN-based matches
X. A sample application – tattoo forensics
i. Saving image descriptors to file
ii. Scanning for matches
XI. Summary
XII. Join our book community on Discord
4. 7 Building Custom Object Detectors
I. Technical requirements
II. Understanding HOG descriptors
III. Visualizing HOG
IV. Using HOG to describe regions of an image
V. Understanding NMS
VI. Understanding SVMs
VII. Detecting people with HOG descriptors
VIII. Creating and training an object detector
i. Understanding BoW
ii. Applying BoW to computer vision
IX. Detecting cars
i. Combining an SVM with a sliding window
X. Summary
XI. Join our book community on Discord
Learning OpenCV 5 Computer
Vision with Python, Fourth Edition:
Tackle tools, techniques, and
algorithms for computer vision
and machine learning
Welcome to Packt Early Access. We’re giving you an exclusive preview
of this book before it goes on sale. It can take many months to write a book,
but our authors have cutting-edge information to share with you today.
Early Access gives you an insight into the latest developments by making
chapter drafts available. The chapters may be a little rough around the edges
right now, but our authors will update them over time. You can dip in and
out of this book or follow along from start to finish; Early Access is designed
to be flexible. We hope you enjoy getting to know more about the process
of writing a Packt book.
While this chapter focuses on classic approaches to face detection and recognition, we will go on to explore
advanced new models by using OpenCV with a variety of other libraries in Chapter 11, Neutral Networks
with OpenCV – an Introduction.
By the end of this chapter, we will have integrated face tracking and rectangle manipulations into Cameo, the
interactive application that we have developed in previous chapters. Finally, we will have some face-to-face
interaction!
Technical requirements
This chapter uses Python, OpenCV, and NumPy. As part of OpenCV, it uses the optional opencv_contrib
modules, which include functionality for face recognition. Some parts of this chapter use OpenCV's optional
support for OpenNI 2 to capture images from depth cameras. Please refer back to Chapter 1, Setting Up OpenCV,
for installation instructions.The complete code for this chapter can be found in this book's GitHub repository,
https://github.com/PacktPublishing/Learning-OpenCV-5-Computer-Vision-with-Python-Fourth-Edition, in the
chapter05 folder. Sample images are in the repository images folder.A subset of the chapter’s sample code can
be edited and run interactively in Google Colab at
https://colab.research.google.com/github/PacktPublishing/Learning-OpenCV-5-Computer-Vision-with-Python-
Fourth-Edition/blob/main/chapter05/chapter05.ipynb.
Later, in Chapter 6, Retrieving Images and Searching Using Image Descriptors, we will explore several kinds
of features, as well as advanced ways of describing and matching sets of features.
Haar-like features are one type of feature that is often applied to real-time face detection. They were first used for
this purpose in the paper Robust Real-Time Face Detection, by Paul Viola and Michael Jones (International
Journal of Computer Vision 57(2), 137–154, Kluwer Academic Publishers, 2001). An electronic version of this
paper is available at http://comp3204.ecs.soton.ac.uk/cw/viola04ijcv.pdf.Each Haar-like feature describes the
pattern of contrast among adjacent image regions. For example, edges, vertices, and thin lines each generate a kind
of feature. Some features are distinctive in the sense that they typically occur in a certain class of object (such as a
face) but not in other objects. These distinctive features can be organized into a hierarchy, called a cascade, in
which the highest layers contain features of greatest distinctiveness, enabling a classifier to quickly reject subjects
that lack these features. If a subject is a good match for the higher-layer features, then the classifier considers the
lower-layer features too in order to weed out more false positives.For any given subject, the features may vary
depending on the scale of the image and the size of the neighborhood (the region of nearby pixels) in which
contrast is being evaluated. The neighborhood’s size is called the window size. To make a Haar cascade classifier
scale-invariant or, in other words, robust to changes in scale, the window size is kept constant but images are
rescaled a number of times; hence, at some level of rescaling, the size of an object (such as a face) may match the
window size. Together, the original image and the rescaled images are called an image pyramid, and each
successive level in this pyramid is a smaller rescaled image. OpenCV provides a scale-invariant classifier that can
load a Haar cascade from an XML file in a particular format. Internally, this classifier converts any given image
into an image pyramid.Haar cascades, as implemented in OpenCV, are not robust to changes in rotation or
perspective. For example, an upside-down face is not considered similar to an upright face and a face viewed in
profile is not considered similar to a face viewed from the front. A more complex and resource-intensive
implementation could improve a Haar cascade’s robustness to rotation by considering multiple transformations of
images as well as multiple window sizes. However, we will confine ourselves to the implementation in OpenCV.
As their names suggest, these cascades are for detecting faces and eyes. They require a frontal, upright view of the
subject. We will use them later when building a face detector.
If you are curious about how these cascade files are generated, you can find more information in Joseph
Howse's book, OpenCV 4 for Secret Agents (Packt Publishing, 2019), specifically in Chapter 3, Training a
Smart Alarm to Recognize the Villain and His Cat. With a lot of patience and a reasonably powerful computer,
you can make your own cascades and train them for various types of objects.
The first and most basic way to perform face detection is to load an image and detect faces in it. To make the result
visually meaningful, we will draw rectangles around faces in the original image. Remembering that the face
detector is designed for upright, frontal faces, we will use an image of a row of people, specifically woodcutters,
standing shoulder to shoulder and facing the photographer or viewer.Let's go ahead and create the following basic
script to perform face detection:
import cv2face_cascade = cv2.CascadeClassifier(
f'{cv2.data.haarcascades}haarcascade_frontalface_default.xml')
img = cv2.imread('../images/woodcutters.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
faces = face_cascade.detectMultiScale(gray, 1.08, 5)
for (x, y, w, h) in faces:
img = cv2.rectangle(img, (x, y), (x+w, y+h), (255, 255255, 0), 2)
cv2.namedWindow('Woodcutters Detected!')
cv2.imshow('Woodcutters Detected!', img)
cv2.imwrite('./woodcutters_detected.pngpng', img)
cv2.waitKey(0)
Let's walk through the preceding code in small steps. First, we use the obligatory cv2 import that you will find in
every script in this book. Then, we declare a face_cascade variable, which is a CascadeClassifier object that
loads a cascade for face detection:
face_cascade = cv2.CascadeClassifier(
f'{cv2.data.haarcascades}haarcascade_frontalface_default.xml')
We then load our image file with cv2.imread and convert it into grayscale because CascadeClassifier , like
many of OpenCV’s classifiers, expects grayscale images. (If we try to use a color image, CascadeClassifier will
internally convert it to grayscale anyway.) The next step, face_cascade.detectMultiScale , is where we perform
the actual face detection:
img = cv2.imread('../images/woodcutters.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
faces = face_cascade.detectMultiScale(gray, 1.08, 5)
The parameters of detectMultiScale include scaleFactor and minNeighbors . The scaleFactor argument,
which should be greater than 1.0, determines the downscaling ratio of the image at each iteration of the face
detection process. As we discussed earlier in the Conceptualizing Haar cascades section, this downscaling is
intended to achieve scale invariance by matching various faces to the window size. The minNeighbors argument
is the minimum number of overlapping detections that are required in order to retain a detection result. Normally,
we expect that a face may be detected in multiple overlapping windows, and a greater number of overlapping
detections makes us more confident that the detected face is truly a face.The value returned from the detection
operation is a list of tuples that represent the face rectangles. OpenCV's cv2.rectangle function allows us to
draw rectangles at the specified coordinates. x and y represent the left and top coordinates, while w and h
represent the width and height of the face rectangle. We draw cyan rectangles around all of the faces we find by
looping through the faces variable, making sure we use the original image for drawing, not the gray version:
for (x, y, w, h) in faces:
cv2.rectangle(img, (x, y), (x+w, y+h), (255, 255255, 0), 2)
Lastly, we call cv2.imshow to display the resulting processed image and we call cv2.imwrite to save it. As
usual, to prevent the image window from closing automatically, we insert a call to waitKey , which returns when
the user presses any key:
cv2.imshow('Woodcutters Detected!', img)
cv2.imwrite('./woodcutters_detected.pngpng', img)
cv2.waitKey(0)
And there we go, three members of the band of woodcutters have been detected in our image, as shown in the
following screenshot:
Figure 5.1: Face detection results of a photograph of woodcutters (Image credit: Prokudin-Gorsky)
The photograph in this example is the work of Sergey Prokudin-Gorsky (1863-1944), a pioneer of color
photography. Tsar Nicholas II sponsored Prokudin-Gorsky to photograph people and places throughout the
Russian Empire as a vast documentary project. Prokudin-Gorsky photographed these woodcutters near the
Svir river, in northwestern Russia, in 1909.
Here, we have no false positive detections; all three rectangles really are woodcutters’ faces. However, we do have
two false negatives (woodcutters whose faces were not detected). Try adjusting the parameters of
face_cascade.detectMultiScale to see how the results change. Then, let’s proceed to a more interactive
example.
Performing face detection on a video
We now understand how to perform face detection on a still image. As mentioned previously, we can repeat the
process of face detection on each frame of a video (be it a camera feed or a pre-recorded video file).The next script
will open a camera feed, read a frame, examine that frame for faces, and scan for eyes within the detected faces.
Finally, it will draw blue rectangles around the faces and green rectangles around the eyes. Here is the script in its
entirety:
import cv2
face_cascade = cv2.CascadeClassifier(
f'{cv2.data.haarcascades}haarcascade_frontalface_default.xml')
eye_cascade = cv2.CascadeClassifier(
f'{cv2.data.haarcascades}haarcascade_eye.xml')
camera = cv2.VideoCapture(0)
while (cv2.waitKey(1) == -1):
success, frame = camera.read()
if success:
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
faces = face_cascade.detectMultiScale(
gray, 1.3, 5, minSize=(120, 120))
for (x, y, w, h) in faces:
cv2.rectangle(frame, (x, y), (x+w, y+h), (255, 0, 0), 2)
roi_gray = gray[y:y+h, x:x+w]
eyes = eye_cascade.detectMultiScale(
roi_gray, 1.11, 5, minSize=(40, 40))
for (ex, ey, ew, eh) in eyes:
cv2.rectangle(frame, (x+ex, y+ey),
(x+ex+ew, y+ey+eh), (0, 255, 0), 2)
cv2.imshow('Face Detection', frame)
Let's break up the preceding sample into smaller, more digestible chunks:
1. As usual, we import the cv2 module. After that, we initialize two CascadeClassifier objects, one for faces
and another for eyes:
face_cascade = cv2.CascadeClassifier( f'{cv2.data.haarcascades}haarcascade_frontalface_default
eye_cascade = cv2.CascadeClassifier(
f'{cv2.data.haarcascades}haarcascade_eye.xml')
1. As in most of our interactive scripts, we open a camera feed and start iterating over frames. We continue until
the user presses any key. Whenever we successfully capture a frame, we convert it into grayscale as our first
step in processing it:
camera = cv2.VideoCapture(0)
while (cv2.waitKey(1) == -1):
success, frame = camera.read()
if success:
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
1. We detect faces with the detectMultiScale method of our face detector. As we have previously done, we
use the scaleFactor and minNeighbors arguments. We also use the minSize argument to specify a
minimum size of a face, specifically 120x120. No attempt will be made to detect faces smaller than this.
(Assuming that our user is sitting close to the camera, it is safe to say that the user's face will be larger than
120x120 pixels.) Here is the call to detectMultiScale :
faces = face_cascade.detectMultiScale(
gray, 1.3, 5, minSize=(120, 120))
1. We iterate over the rectangles of the detected faces. We draw a blue border around each rectangle in the
original color image. Then, within the same rectangular region of the grayscale image, we perform eye
detection:
for (x, y, w, h) in faces:
cv2.rectangle(frame, (x, y), (x+w, y+h), (255, 0, 0), 2)
roi_gray = gray[y:y+h, x:x+w]
eyes = eye_cascade.detectMultiScale(
roi_gray, 1.1, 5, minSize=(40, 40))
The eye detector is a bit less accurate than the face detector. You might see shadows, parts of the frames of
glasses, or other regions of the face falsely detected as eyes. To improve the results, you could try defining
roi_gray as a smaller region of the face, since we can make a good guess about the eyes' location in an
upright face. You could also try using a maxSize argument to avoid false positives that are too large to be
eyes. Also, you could adjust minSize and maxSize so that the dimensions are proportional to w and h , the
size of the detected face. As an exercise, feel free to experiment with changes to these and other parameters.
1. We loop through the resulting eye rectangles and draw green outlines around them:
for (ex, ey, ew, eh) in eyes:
cv2.rectangle(frame, (x+ex, y+ey),
(x+ex+ew, y+ey+eh), (0, 255, 0), 2)
Run the script. If our detectors produce accurate results, and if any face is within the field of view of the camera,
you should see a blue rectangle around the face and a green rectangle around each eye, as shown in this screenshot:
If we trained a face recognizer on these samples, we would then have to run face recognition on an image that
contains the face of one of the sampled people. This process might be educational, but perhaps not as satisfying as
providing images of our own. You probably had the same thought that many computer vision learners have had: I
wonder if I can write a program that recognizes my face with a certain degree of confidence. Indeed, you can and
soon you will!
Let's go ahead and write a script that will generate those images for us. A few images containing different
expressions are all that we need, but it is preferable that the training images are square and are all the same size.
Our sample script uses a size of 200x200, but most freely available datasets have smaller images than this.Here is
the script itself:
import cv2
import os
output_folder = '../data/at/jm'
if not os.path.exists(output_folder):
os.makedirs(output_folder)
face_cascade = cv2.CascadeClassifier(
f'{cv2.data.haarcascades}haarcascade_frontalface_default.xml')
camera = cv2.VideoCapture(0)
count = 0
while (cv2.waitKey(1) == -1):
success, frame = camera.read()
if success:
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
faces = face_cascade.detectMultiScale(
gray, 1.3, 5, minSize=(120, 120))
for (x, y, w, h) in faces:
cv2.rectangle(frame, (x, y), (x+w, y+h), (255, 0, 0), 2)
face_img = cv2.resize(gray[y:y+h, x:x+w], (200, 200))
face_filename = '%s/%d.pgm' % (output_folder, count)
cv2.imwrite(face_filename, face_img)
count += 1
cv2.imshow('Capturing Faces...', frame)
Here, we are generating sample images by building on our newfound knowledge of how to detect a face in a video
feed. We are detecting a face, cropping that region of the grayscale-converted frame, resizing it to be 200x200
pixels, and saving it as a PGM file with a name in a particular folder (in this case, jm , one of the authors’ initials;
you can use your own initials). Like many of our windowed applications, this one runs until the user presses any
key.The count variable is present because we needed progressive names for the images. Run the script for a few
seconds, change your facial expression a few times, and check the destination folder you specified in the script.
You will find a number of images of your face, grayed, resized, and named with the format <count>.pgm .Modify
the output_folder variable to make it match your name. For example, you might choose
'../data/at/my_name' . Run the script, wait for it to detect your face in a number of frames (say, 20 or more),
and then press any key to quit. Now, modify the output_folder variable again to make it match the name of a
friend whom you also want to recognize. For example, you might choose '../data/at/name_of_my_friend' . Do
not change the base part of the folder (in this case, '../data/at' ) because later, in the Loading the training data
for face recognition section, we will write code that loads the training images from all of the subfolders of this
base folder. Ask your friend to sit in front of the camera, run the script again, let it detect your friend's face in a
number of frames, and then quit. Repeat this process for any additional people you might want to recognize.Let's
now move on to try and recognize the user's face in a video feed. This should be fun!
OpenCV 5 implements three different algorithms for recognizing faces: Eigenfaces, Fisherfaces, and Local
Binary Pattern Histograms (LBPHs). Eigenfaces and Fisherfaces are derived from a more general-purpose
algorithm called Principal Component Analysis (PCA). For a detailed description of the algorithms, refer to the
following links:
For this book's purposes, let's just take a high-level overview of the algorithms. First and foremost, they all follow
a similar process; they take a set of classified observations (our face database, containing numerous samples per
individual), train a model based on it, perform an analysis of face images (which may be face regions that we
detected in an image or video), and determine two things: the subject's identity and a measure of confidence that
this identification is correct. The latter is commonly known as the confidence score.Eigenfaces performs PCA,
which identifies principal components of a certain set of observations (again, your face database), calculates the
divergence of the current observation (the face being detected in an image or frame) compared to the dataset, and
produces a value. The smaller the value, the smaller the difference between the face database and the detected face;
hence, a value of 0 is an exact match.Fisherfaces also derives from PCA and evolves the concept, applying more
complex logic. While computationally more intensive, it tends to yield more accurate results than
Eigenfaces.LBPH instead divides a detected face into small cells and, for each cell, builds a histogram that
describes whether the brightness of the image is increasing when comparing neighboring pixels in a given
direction. This cell's histogram can be compared to the histogram of the corresponding cell in the model, producing
a measure of similarity. Of the face recognizers in OpenCV, the implementation of LBPH is the only one that
allows the model sample faces and the detected faces to be of a different shape and size. Hence, it is a convenient
option, and the authors of this book find that its accuracy compares favorably to the other two options.Despite the
algorithms’ differences, OpenCV provides a similar interface for all three, as we shall soon see.
Regardless of our choice of face recognition algorithm, we can load the training images in the same way. Earlier,
in the Generating the data for face recognition section, we generated training images and saved them in folders
that were organized according to people's names or initials. For example, the following folder structure could
contain sample face images of this book's authors, Joseph Howse ( jh ) and Joe Minichino ( jm ):
../
data/
at/
jh/
jm/
Let's write a script that loads these images and labels them in a way that OpenCV's face recognizers will
understand. To work with the filesystem and the data, we will use the Python standard library's os module, as well
as the cv2 and numpy modules. Let's create a script that starts with the following import statements:
import os
import cv2
import numpy
Let's add the following read_images function, which walks through a directory's subdirectories, loads the images
and shows each of them for user information purposes, resizes them to a specified size, and puts the resized images
in a list. The order of this list has no significance in itself but, at the same time, our function builds two
corresponding lists that describe the images: first, a corresponding list of people's names or initials (based on the
subfolder names), and second, a corresponding list of labels or numeric IDs associated with the loaded images. For
example, jh could be a name and 0 could be the label for all images that were loaded from the jh subfolder.
Finally, the function converts the lists of images and labels into NumPy arrays, and it returns three variables: the
list of names, the NumPy array of images, and the NumPy array of labels. Here is the function's implementation:
def read_images(path, image_size):
names = []
training_images, training_labels = [], []
label = 0
for dirname, subdirnames, filenames in os.walk(path):
for subdirname in subdirnames:
names.append(subdirname)
subject_path = os.path.join(dirname, subdirname)
for filename in os.listdir(subject_path):
img = cv2.imread(os.path.join(subject_path, filename),
cv2.IMREAD_GRAYSCALE)
if img is None:
# The file cannot be loaded as an image.
# Skip it.
continue
img = cv2.resize(img, image_size)
cv2.imshow('training', img)
cv2.waitKey(5)
training_images.append(img)
training_labels.append(label)
label += 1
training_images = numpy.asarray(training_images, numpy.uint8)
training_labels = numpy.asarray(training_labels, numpy.int32)
return names, training_images, training_labels
Let's call our read_images function by adding code such as the following:
path_to_training_images = '../data/at'
training_image_size = (200, 200)
names, training_images, training_labels = read_images(
path_to_training_images, training_image_size)
Edit the path_to_training_images variable in the preceding code block to ensure that it matches the base
folder of the output_folder variables you defined earlier in the code for the section Generating the data for
face recognition.
So far, we have training data in a useful format but we have not yet created a face recognizer or performed any
training. We will do so in the next section, where we continue the implementation of the same script.
Performing face recognition with Eigenfaces
Now that we have an array of training images and an array of their labels, we can create and train a face recognizer
with just two more lines of code:
model = cv2.face.EigenFaceRecognizer_create()
model.train(training_images, training_labels)
What have we done here? We created the Eigenfaces face recognizer with OpenCV's
cv2.EigenFaceRecognizer_create function, and we trained the recognizer by passing the arrays of images and
labels (numeric IDs). Optionally, we could have passed two arguments to cv2.EigenFaceRecognizer_create :
Having trained this model, we could save it to a file using code such as model.write('my_model.xml') .
Later (for example, in another script), we could skip the training step and just load the pre-training model
from the file using code such as model.read('my_model.xml') . However, for the sake of our simple demo,
we will just train and test the model in one script without saving the model to a file.
To test this recognizer, let's use a face detector and a video feed from a camera. As we have done in previous
scripts, we can use the following line of code to initialize the face detector:
face_cascade = cv2.CascadeClassifier(
f'{cv2.data.haarcascades}haarcascade_frontalface_default.xml')
The following code initializes the camera feed, iterates over frames (until the user presses any key), and performs
face detection and recognition on each frame:
camera = cv2.VideoCapture(0)
while (cv2.waitKey(1) == -1):
success, frame = camera.read()
if success:
faces = face_cascade.detectMultiScale(frame, 1.3, 5)
for (x, y, w, h) in faces:
cv2.rectangle(frame, (x, y), (x+w, y+h), (255, 0, 0), 2)
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
roi_gray = gray[x:x+w, y:y+h]
if roi_gray.size == 0:
# The ROI is empty. Maybe the face is at the image edge.
# Skip it.
continue
roi_gray = cv2.resize(roi_gray, training_image_size)
label, confidence = model.predict(roi_gray)
text = '%s, confidence=%.2f' % (names[label], confidence)
cv2.putText(frame, text, (x, y - 20),
cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 0, 0), 2)
cv2.imshow('Face Recognition', frame)
Let's walk through the most important functionality of the preceding block of code. For each detected face, we
convert and resize it so that we have a grayscale version that matches the expected size (in this case, 200x200
pixels as defined by the training_image_size variable in the previous section, Loading the training data for face
recognition). Then, we pass the resized, grayscale face to the face recognizer's predict function. This returns a
label and confidence score. We look up the person's name corresponding to the numeric label of that face.
(Remember that we created the names array in the previous section, Loading the training data for face
recognition.) We draw the name and confidence score in blue text above the recognized face. After iterating over
all detected faces, we display the annotated image.
We have taken a simple approach to face detection and recognition, and it serves the purpose of enabling you
to have a basic application running and understand the process of face recognition in OpenCV 5. To improve
upon this approach and make it more robust, you could take further steps such as correctly aligning and
rotating detected faces so that the accuracy of the recognition is maximized.
When you run the script, you should see something similar to the following screenshot:
Next, let's consider how we would adapt these script to replace Eigenfaces with another face recognition
algorithm.
What about Fisherfaces? The process does not change much; we simply need to instantiate a different algorithm.
With default arguments, the declaration of our model variable would look like this:
model = cv2.face.FisherFaceRecognizer_create()
For the LBPH algorithm, again, the process is similar. However, the algorithm factory takes the following optional
parameters (in order):
radius : The pixel distance between the neighbors that are used to calculate a cell's histogram (by default, 1)
neighbors : The number of neighbors used to calculate a cell's histogram (by default, 8)
grid_x : The number of cells into which the face is divided horizontally (by default, 8)
grid_y : The number of cells into which the face is divided vertically (by default, 8)
confidence : The confidence threshold (by default, the highest possible floating-point value so that no results
are discarded)
With default arguments, the model declaration would look like this:
model = cv2.face.LBPHFaceRecognizer_create()
Note that, with LBPH, we do not need to resize images as the division into grids allows a comparison of
patterns identified in each cell.
Having looked at the available face recognition algorithms, let’s now consider how to evaluate a recognition result.
The predict method returns a tuple, in which the first element is the label of the recognized individual and the
second is the confidence score. All algorithms come with the option of setting a confidence score threshold, which
measures the distance of the recognized face from the original model; therefore, a score of 0 signifies an exact
match.There may be cases in which you would rather retain all recognitions and then apply further processing, so
you can come up with your own algorithms to estimate the confidence score of a recognition. For example, if you
are trying to identify people in a video, you may want to analyze the confidence score in subsequent frames to
establish whether the recognition was successful or not. In this case, you can inspect the confidence score obtained
by the algorithm and draw your own conclusions.
The typical range of the confidence score depends on the algorithm. Eigenfaces and Fisherfaces produce
values (roughly) in the range of 0 to 20,000, with any score below 4,000-5,000 being a quite confident
recognition. For LBPH, a good recognition scores (roughly) below 50, and any value above 80 is considered a
poor confidence score.
A normal custom approach would be to hold off drawing a rectangle around a recognized face until we have a
number of frames with a good confidence score (where “good” is an arbitrary threshold we must choose, based on
our algorithm and use case), but you have total freedom to use OpenCV's face recognition module to tailor your
application to your needs. Next, let’s see how face detection and recognition work in a specialized use case.
Here, we see the face of Joseph Howse swapped with the face of Janet Howse, his mother. Although Cameo is
copying pixels from rectangular regions (and this is clearly visible at the bottom of the swapped regions, in the
foreground), some of the background pixels within the rectangles are masked based on depth and thus are not
swapped, so we do not see rectangular edges everywhere.You can find all of the relevant changes to the Cameo
source code in this book's repository at https://github.com/PacktPublishing/Learning-OpenCV-5-Computer-Vision-
with-Python-Fourth-Edition, specifically in the chapter05/cameo folder. For brevity, we will not discuss all of the
changes here in this book, but we will cover some of the highlights in the next two subsections, Modifying the
application's loop and Masking a copy operation.
To support face swapping, the Cameo project has two new modules called rects and trackers . The rects
module contains functions for copying and swapping rectangles, with an optional mask to limit the copy or swap
operation to particular pixels. The trackers module contains a class called FaceTracker , which adapts
OpenCV's face detection functionality to an object-oriented style of programming.As we have covered OpenCV's
face detection functionality earlier in this chapter, and we have demonstrated an object-oriented programming style
in previous chapters, we will not go into the FaceTracker implementation here. Instead, you may look at it in this
book's repository.Let's open cameo.py so that we can walk through the overall changes to the application:
1. Near the top of the file, we need to import our new modules, as highlightedhighlighted in the following
code block:
import cv2
import depth
import filters
from managers import WindowManager, CaptureManager
import rects
from trackers import FaceTracker
1. Now, let's turn our attention to changes in the __init__ method of our CameoDepth class. Our updated
application uses an instance of FaceTracker . As part of its functionality, FaceTracker can draw rectangles
around detected faces. Let's give Cameo's user the option to enable or disable the drawing of face rectangles.
We will keep track of the currently selected option via a Boolean variable. The following code block
highlights the necessary changes to initialize the FaceTracker object and the Boolean variable:
class CameoDepth(Cameo):
def __init__(self):
self._windowManager = WindowManager('Cameo',
self.onKeypress)
#device = cv2.CAP_OPENNI2 # uncomment for Kinect
device = cv2.CAP_OPENNI2_ASUS # uncomment for Xtion
self._captureManager = CaptureManager(
cv2.VideoCapture(device), self._windowManager, True)
self._faceTracker = FaceTracker()
self._shouldDrawDebugRects = False
self._curveFilter = filters.BGRPortraCurveFilter()
We make use of the FaceTracker object in the run method of CameoDepth , which contains the application's
main loop that captures and processes frames. Every time we successfully capture a frame, we call methods of
FaceTracker to update the face detection result and get the latest detected faces. Then, for each face, we create a
mask based on the depth camera's disparity map. (Previously, in Chapter 4, Depth Estimation and Segmentation,
we created such a mask for the entire image instead of a mask for each face rectangle.)
1. Then, we call a function, rects.swapRects , to perform a masked swap of the face rectangles. (We will look
at the implementation of swapRects a little later, in the Masking a copy operation section.) Depending on the
currently selected option, we might tell FaceTracker to draw rectangles around the faces. All of the relevant
changes are highlighted in the following code block:
def run(self):
"""Run the main loop."""
self._windowManager.createWindow()
while self._windowManager.isWindowCreated:
# ... The logic for capturing a frame is unchanged ...
if frame is not None:
self._faceTracker.update(frame)
faces = self._faceTracker.faces
masks = [
depth.createMedianMask(
disparityMap, validDepthMask,
face.faceRect) \
for face in faces
]
rects.swapRects(frame, frame,
[face.faceRect for face in faces],
masks)
if self._captureManager.channel == cv2.CAP_OPENNI_BGR_IMAGE:
# A BGR frame was captured.
# Apply filters to it.
filters.strokeEdges(frame, frame)
self._curveFilter.apply(frame, frame)
if self._shouldDrawDebugRects:
self._faceTracker.drawDebugRects(frame)
self._captureManager.exitFrame()
self._windowManager.processEvents()
1. Finally, let's modify the onKeypress method so that the user can hit the X key to start or stop displaying
rectangles around detected faces. Again, the relevant changes are highlighted in the following code block:
def onKeypress(self, keycode):
"""Handle a keypress.
space -> Take a screenshot.
tab -> Start/stop recording a screencast.
x -> Start/stop drawing debug rectangles around faces.
escape -> Quit.
"""
if keycode == 32: # space
self._captureManager.writeImage('screenshot.png')
elif keycode == 9: # tab
if not self._captureManager.isWritingVideo:
self._captureManager.startWritingVideo(
'screencast.avi')
else:
self._captureManager.stopWritingVideo()
elif keycode == 120: # x
self._shouldDrawDebugRects = \
not self._shouldDrawDebugRects
elif keycode == 27: # escape
self._windowManager.destroyWindow()
Next, let's look at the implementation of the rects module that we imported earlier in this section.
The rects module is implemented in rects.py . We already saw a call to the rects.swapRects function in the
previous section. However, before we consider the implementation of swapRects , we first need a more basic
copyRect function.As far back as Chapter 2, Handling Files, Cameras, and GUIs, we learned how to copy data
from one rectangular region of interest (ROI) to another using NumPy's slicing syntax. Outside the ROIs, the
source and destination images were unaffected. Now, we want to apply further limits to this copy operation. We
want to use a given mask that has the same dimensions as the source rectangle.We shall copy only those pixels in
the source rectangle where the mask's value is not zero. Other pixels shall retain their old values from the
destination image. This logic, with an array of conditions and two arrays of possible output values, can be
expressed concisely with the numpy.where function.With this approach in mind, let's consider our copyRect
function. As arguments, it takes a source and destination image, a source and destination rectangle, and a mask.
The latter may be None , in which case, we simply resize the content of the source rectangle to match the
destination rectangle and then assign the resulting resized content to the destination rectangle. Otherwise, we next
ensure that the mask and the images have the same number of channels. We assume that the mask has one channel
but the images may have three channels (BGR). We can add duplicate channels to mask using the repeat and
reshape methods of numpy.array . Finally, we perform the copy operation using numpy.where . The complete
implementation is as follows:
def copyRect(src, dst, srcRect, dstRect, mask = None,
interpolation = cv2.INTER_LINEAR):
"""Copy part of the source to part of the destination."""
x0, y0, w0, h0 = srcRect
x1, y1, w1, h1 = dstRect
# Resize the contents of the source sub-rectangle.
# Put the result in the destination sub-rectangle.
if mask is None:
dst[y1:y1+h1, x1:x1+w1] = \
cv2.resize(src[y0:y0+h0, x0:x0+w0], (w1, h1),
interpolation = interpolation)
else:
if not utils.isGray(src):
# Convert the mask to 3 channels, like the image.
mask = mask.repeat(3).reshape(h0, w0, 3)
# Perform the copy, with the mask applied.
dst[y1:y1+h1, x1:x1+w1] = \
numpy.where(cv2.resize(mask, (w1, h1),
interpolation = \
cv2.INTER_NEAREST),
cv2.resize(src[y0:y0+h0, x0:x0+w0], (w1, h1),
interpolation = interpolation),
dst[y1:y1+h1, x1:x1+w1])
We also need to define a swapRects function, which uses copyRect to perform a circular swap of a list of
rectangular regions. swapRects has a masks argument, which is a list of masks whose elements are passed to the
respective copyRect calls. If the value of the masks argument is None , we pass None to every copyRect call.
The following code shows the full implementation of swapRects :
def swapRects(src, dst, rects, masks = None,
interpolation = cv2.INTER_LINEAR):
"""Copy the source with two or more sub-rectangles swapped."""
if dst is not src:
dst[:] = src
numRects = len(rects)
if numRects < 2:
return
if masks is None:
masks = [None] * numRects
# Copy the contents of the last rectangle into temporary storage.
x, y, w, h = rects[numRects - 1]
temp = src[y:y+h, x:x+w].copy()
# Copy the contents of each rectangle into the next.
i = numRects - 2
while i >= 0:
copyRect(src, dst, rects[i], rects[i+1], masks[i],
interpolation)
i -= 1
# Copy the temporarily stored content into the first rectangle.
copyRect(temp, dst, (0, 0, w, h), rects[0], masks[numRects - 1],
interpolation)
Note that the mask argument in copyRect and the masks argument in swapRects both have a default value of
None . If no mask is specified, these functions copy or swap the entire contents of the rectangle or rectangles.
Summary
By now, you should have a good understanding of how face detection and face recognition work and how to
implement them in Python and OpenCV 5.The accuracy of detection and recognition algorithms heavily depends
on the quality of the training data, so make sure you provide your applications with a large number of training
images covering a variety of expressions, poses, and lighting conditions. Later in this book, in Chapter 11, Neutral
Networks with OpenCV – an Introduction, we will look at how to use several robust, pre-trained face detection
models that build atop advanced algorithms and large sets of training data.As human beings, we might be
predisposed to think that human faces are particularly recognizable. We might even be overconfident in our own
face recognition abilities. However, in computer vision, there is nothing very special about human faces, and we
can just as readily use algorithms to find and identify other things. We will begin to do so next in Chapter 6,
Retrieving Images and Searching Using Image Descriptors.
Join our book community on Discord
https://discord.gg/djGjeMECxw
6 Retrieving Images and Searching
Using Image Descriptors
Similar to the human eyes and brain, OpenCV can detect the main features of an
image and extract them into so-called image descriptors. These features can then be
used as a database, enabling image-based searches. Moreover, we can use key points
to stitch images together and compose a bigger image. (Think of putting together
many pictures to form a 360° panorama.)This chapter will show you how to detect
the features of an image with OpenCV and make use of them to match and search
images. Over the course of this chapter, we will take sample images and detect their
main features, and then try to find a region of another image that matches the sample
image. We will also find the homography or spatial relationship between a sample
image and a matching region of another image.More specifically, we will cover the
following tasks:
Detecting keypoints and extracting local descriptors around the keypoints using
any of the following algorithms: Harris corners, SIFT, SURF, or ORB
Matching keypoints using brute-force algorithms or the FLANN algorithm
Filtering out bad matches using KNN and the ratio test
Finding the homography between two sets of matching keypoints
Searching a set of images to determine which one contains the best match for a
reference image
Technical requirements
This chapter uses Python, OpenCV, and NumPy. In regards to OpenCV, we use the
optional opencv_contrib modules, which include additional algorithms for
keypoint detection and matching. To enable theSURF algorithm (whichis patented
and not free for commercial use), we must configure the opencv_contrib modules
with the OPENCV_ENABLE_NONFREE flag in CMake. Please refer to Chapter 1, Setting
Up OpenCV, for installation instructions. Additionally, if you have not already
installed Matplotlib, install it by running $ pip install matplotlib (or
$ pip3 install matplotlib , depending on your environment).The complete code
for this chapter can be found in this book's GitHub repository,
https://github.com/PacktPublishing/Learning-OpenCV-5-Computer-Vision-with-
Python-Fourth-Edition, in the chapter06 folder. The sample images can be found
in the images folder.A subset of the chapter’s sample code can be edited and run
interactively in Google Colab at
https://colab.research.google.com/github/PacktPublishing/Learning-OpenCV-5-
Computer-Vision-with-Python-Fourth-
Edition/blob/main/chapter06/chapter06.ipynb.
Brute-force matching
FLANN-based matching
Defining features
The most important parameter here is the third one, which defines the aperture or
kernel size of the Sobel operator. The Sobel operator detects edges by measuring
horizontal and vertical differences between pixel values in a neighborhood, and it
does this using a kernel. The cv2.cornerHarris function uses a Sobel operator
whose aperture is defined by this parameter. In plain English, the parameters define
how sensitive corner detection is. It must be between 3 and 31 and be an odd value.
With a low (highly sensitive) value of 3 , all those diagonal lines in the black
squares of the chessboard will register as corners when they touch the border of the
square. For a higher (less sensitive) value of 23 , only the corners of each square
will be detected as corners. cv2.cornerHarris returns an image in floating-point
format. Each value in this image represents a score for the corresponding pixel in
the source image. A moderate or high score indicates that the pixel is likely to be a
corner. Conversely, we can treat pixels with the lowest scores as non-corners.
Consider the following line:
img[dst > 0.01 * dst.max()] = [0, 0, 255]
Here, we select pixels with scores that are at least 1% of the highest score, and we
color these pixels red in the original image. Here is the result:
Figure 6.2: Corners detections in a chessboard
Great! Nearly all the detected corners are marked in red. The marked points include
nearly all the corners of the chessboard's squares.
Figure 6.3: Corner detections in an image of the F1 Italian Grand Prix track
Here is the corner detection result with a smaller version of the same image:
Figure 6.4: Corner detections in a smaller image of the F1 Italian Grand Prix track
You will notice how the corners are a lot more condensed; however, even though we
gained some corners, we lost others! In particular, let's examine the Variante Ascari
chicane, which looks like a squiggle at the end of the part of the track that runs
straight from northwest to southeast. In the larger version of the image, both the
entrance and the apex of the double bend were detected as corners. In the smaller
image, the apex is not detected as such. If we further reduce the image, at some
scale, we will lose the entrance to that chicane too.This loss of features raises an
issue; we need an algorithm that works regardless of the scale of the image. Enter
Scale-Invariant Feature Transform (SIFT). While the name may sound a bit
mysterious, now that we know what problem we are trying to solve, it actually
makes sense. We need a function (a transform) that will detect features (a feature
transform) and will not output different results depending on the scale of the image
(a scale-invariant feature transform). Note that SIFT does not detect keypoints.
Keypoint detection is done with the Difference of Gaussians (DoG), while SIFT
describes the region surrounding the keypoints by means of a feature vector.A quick
introduction to the DoG is in order. Previously, in Chapter 3, Processing Images
with OpenCV, we talked about low pass filters and blurring operations, and
specifically the cv2.GaussianBlur function. DoG is the result of applying different
Gaussian filters to the same image. Previously, we applied this type of technique for
edge detection, and the idea is the same here. The final result of a DoG operation
contains areas of interest (keypoints), which are then going to be described through
SIFT.Let's see how DoG and SIFT behave in the following image, which is full of
corners and features:
Here, the beautiful panorama of Varese (in Lombardy, Italy) gains a new type of
fame as a subject of computer vision. Here is the code that produces this processed
image:
import cv2
img = cv2.imread('../images/varese.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
sift = cv2.SIFT_create()
keypoints, descriptors = sift.detectAndCompute(gray, None)
cv2.drawKeypoints(img, keypoints, img, (51, 163, 236),
cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
cv2.imshow('sift_keypoints', img)
cv2.waitKey()
After the usual imports, we load the image we want to process. Then, we convert the
image into grayscale. By now, you may have gathered that many methods in
OpenCV expect a grayscale image as input. The next step is to create a SIFT
detection object and compute the features and descriptors of the grayscale image:
sift = cv2.SIFT_create()
keypoints, descriptors = sift.detectAndCompute(gray, None)
Behind the scenes, these simple lines of code carry out an elaborate process; we
create a cv2.SIFT object, which uses DoG to detect keypoints and then computes a
feature vector for the surrounding region of each keypoint. As the name of the
detectAndCompute method clearly suggests, two main operations are performed:
feature detection and the computation of descriptors. The return value of the
operation is a tuple containing a list of keypoints and another list of the keypoints'
descriptors.Finally, we process this image by drawing the keypoints on it with the
cv2.drawKeypoints function and then displaying it with the usual cv2.imshow
function. As one of its arguments, the cv2.drawKeypoints function accepts a flag
that specifies the type of visualization we want. Here, we specify
cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINT in order to draw a visualization of
the scale and orientation of each keypoint.
Anatomy of a keypoint
Each keypoint is an instance of the cv2.KeyPoint class, which has the following
properties:
The pt (point) property contains the x and y coordinates of the keypoint in the
image.
The size property indicates the diameter of the feature.
The angle property indicates the orientation of the feature, as shown by the
radial lines in the preceding processed image.
The response property indicates the strength of the keypoint. Some features
are classified by SIFT as stronger than others, and response is the property
you would check to evaluate the strength of a feature.
The octave property indicates the layer in the image pyramid where the
feature was found. Let's briefly review the concept of an image pyramid, which
we discussed previously in Chapter 5, Detecting and Recognizing Faces, in the
Conceptualizing Haar cascades section. The SIFT algorithm operates in a
similar fashion to face detection algorithms in that it processes the same image
iteratively but alters the input at each iteration. In particular, the scale of the
image is a parameter that changes at each iteration ( octave ) of the algorithm.
Thus, the octave property is related to the image scale at which the keypoint
was detected.
Finally, the class_id property can be used to assign a custom identifier to a
keypoint or a group of keypoints.
Note that SURF is a patented algorithm and, for this reason, is made available
only in builds of opencv_contrib where the OPENCV_ENABLE_NONFREE
CMake flag is used. SIFT was formerly a patented algorithm but its patent
expired and now SIFT is available in standard builds of OpenCV.
It is not particularly relevant to this book to understand how SURF works under the
hood, inasmuch as we can use it in our applications and make the best of it. What is
important to understand is that cv2.SURF is an OpenCV class that performs
keypoint detection with the Fast Hessian algorithm and descriptor extraction with
SURF, much like the cv2.SIFT class performs keypoint detection with DoG and
descriptor extraction with SIFT.Also, the good news is that OpenCV provides a
standardized API for all its supported feature detection and descriptor extraction
algorithms. Thus, with only trivial changes, we can adapt our previous code sample
to use SURF instead of SIFT. Here is the modified code, with the changes
highlighted :
import cv2
img = cv2.imread('../images/varese.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
surf = cv2.xfeatures2d.SURF_create(8000)
keypoints, descriptor = surf.detectAndCompute(gray, None)
cv2.drawKeypoints(img, keypoints, img, (51, 163, 236),
cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
cv2.imshow('surf_keypoints', img)
cv2.waitKey()
Try adjusting the threshold to see how it affects the result. As an exercise, you may
want to build a GUI application with a slider that controls the value of the threshold.
This way, a user can adjust the threshold and see the number of features increase and
decrease in an inversely proportional fashion. We built a GUI application with
sliders in Chapter 4, Depth Estimation and Segmentation, in the Depth estimation
with a normal camera section, so you may want to refer back to that section as a
guide.Next, we'll examine the FAST corner detector, the BRIEF keypoint descriptor,
and ORB (which uses FAST and BRIEF together).
FAST
The Features from Accelerated Segment Test (FAST) algorithm works by
analyzing circular neighborhoods of 16 pixels. It marks each pixel in a
neighborhood as brighter or darker than a particular threshold, which is defined
relative to the center of the circle. A neighborhood is deemed to be a corner if it
contains a number of contiguous pixels marked as brighter or darker.FAST also uses
a high-speed test, which can sometimes determine that a neighborhood is not a
corner by checking just 2 or 4 pixels instead of 16. To understand how this test
works, let's take a look at the following diagram, taken from the OpenCV
documentation:
Figure 6.7: How the FAST algorithm analyzes a neighbourhood around a corner
BRIEF
Brute-force matching
The main points are quite clear: ORB aims to optimize and speed up operations,
including the very important step of utilizing BRIEF in a rotation-aware fashion so
that matching is improved, even in situations where a training image has a very
different rotation to the query image.At this stage, though, perhaps you have had
enough of the theory and want to sink your teeth into some feature matching, so let's
look at some code. The following script attempts to match features in a logo to the
features in a photograph that contains the logo:
import cv2
from matplotlib import pyplot as plt
# Load the images.
img0 = cv2.imread('../images/nasa_logo.png',
cv2.IMREAD_GRAYSCALE)
img1 = cv2.imread('../images/kennedy_space_center.jpg',
cv2.IMREAD_GRAYSCALE)
# Perform ORB feature detection and description.
orb = cv2.ORB_create()
kp0, des0 = orb.detectAndCompute(img0, None)
kp1, des1 = orb.detectAndCompute(img1, None)
# Perform brute-force matching.
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
matches = bf.match(des0, des1)
# Sort the matches by distance.
matches = sorted(matches, key=lambda x:x.distance)
# Draw the best 25 matches.
img_matches = cv2.drawMatches(
img0, kp0, img1, kp1, matches[:25], img1,
flags=cv2.DRAW_MATCHES_FLAGS_NOT_DRAW_SINGLE_POINTS)
# Show the matches.
plt.imshow(img_matches)
plt.show()
Let's examine this code step by step. After the usual imports, we load two images
(the query image and the scene) in grayscale format. Here is the query image, which
is the NASA logo:
Figure 6.8: The NASA logo, to be used for keypoint descriptor matching
Here is the photo of the scene, which is the Kennedy Space Center:
Figure 6.9: The Kennedy Space Center, with a NASA logo on its wall
In a similar fashion to what we did with SIFT and SURF, we detect and compute the
keypoints and descriptors for both images.From here, the concept is pretty simple:
iterate through the descriptors and determine whether they are a match or not, and
then calculate the quality of this match (distance) and sort the matches so that we
can display the top n matches with a degree of confidence that they are, in fact,
matching features on both images. cv2.BFMatcher does this for us:
# Perform brute-force matching.
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
matches = bf.match(des0, des1)
# Sort the matches by distance.
matches = sorted(matches, key=lambda x:x.distance)
Exploring the Variety of Random
Documents with Different Content
however, in Scotland where he met any such troubles. With immense
difficulty, he next day crossed Mount Skene by an uneven stony way,
full of bogs, quagmires, and long heath, ‘where a dog with three
legs would outrun a horse with four,’ and came in the evening to
Braemar. This he describes as a large county, full of lofty mountains,
compared with which English hills are but ‘as a liver or a gizzard
below a capon’s wing.’ ‘There I saw Benawne [Ben Aven], with a
furred mist upon his snowy head, instead of a night-cap.’
He here found his friend, Sir William Murray, engaged in Highland
sports, along with the Earl of Mar, the Earl of Enzie (afterwards
second Marquis of Huntly), the Earl of Buchan, and Lord Erskine,
accompanied by their countesses, and a hundred other knights and
squires, with their followers, ‘all in general in one habit, as if
Lycurgus had been there.’ ‘For once in the year, which is the whole
month of August, and sometimes part of September, many of the
nobility and gentry of the kingdom, for their pleasure, come into
these Highland countries to hunt, where they conform to the habit of
the Highlandmen, who for the most part speak nothing but Irish....
Their habit is shoes with but one sole apiece; stockings which they
call short hose, made of a warm stuff of divers colours, which they
call tartan: as for breeches, many of them, nor their forefathers,
never wore any, but a jerkin of the same stuff that their hose is of,
their garters being bands or wreaths of hay or straw, with a plaid
about their shoulders, which is a mantle of divers colours, [of] much
finer and lighter stuff than their hose; with flat blue caps on their
heads, a handkerchief knit with two knots about their neck; and thus
they are attired.... Their weapons are long bows and forked arrows,
swords and targets, harquebusses, muskets, durks, and Lochaber
axes. With these arms, I found many of them armed for the hunting.
As for their attire, any man of what degree soever that comes
amongst them, must not disdain to wear it; for if they do, they will
disdain to hunt, or willingly to bring in their dogs; but if men be kind
to them, and be in their habit, then they are conquered with
kindness, and the sport will be plentiful. This was the reason that I
found so many noblemen and gentlemen in those shapes.’
Taylor allowed himself to be invested by the Earl of
Mar in Highland attire, and then accompanied the 1618.
party for twelve days into a wilderness devoid of
corn and human habitations—probably the district around the skirts
of Ben Muicdhui. He found temporary lodges called lonchards,
designed for the use of the sportsmen, and he himself received a
kind of accommodation in that of Lord Erskine. The kitchen, he tells
us, was ‘always on the side of a bank, many kettles and pots boiling,
and many spits turning and winding, with great variety of cheer, as
venison—baked, sodden, roast, and stewed beef—mutton, goats,
kid, hares, fresh salmon, pigeons, hens, capons, chickens, partridge,
moorcoots, heath-cocks, cappercailzies, and termagants; good ale,
sack, white and claret, tent (or Alicant), with most potent aquavitæ.’
Thus a company of about fourteen hundred persons was most amply
fed.
‘The manner of the hunting is this: five or six hundred men rise early
in the morning, and disperse themselves divers ways, and seven,
eight, or ten miles compass, they bring or chase in the deer in many
herds (two, three, or four hundred in a herd), to such or such a
place, as the noblemen shall appoint them. Then, when day is come,
the lords and gentlemen of their companies ride or go to the said
places, sometimes wading up to the middle, through burns and
rivers; and then they, being come to the place, lie down on the
ground, till those foresaid scouts, who are called the Tinchel-men,
bring down the deer.... After we had stayed there three hours or
thereabouts, we might perceive the deer appear on the hills round
about us (their heads making a show like a wood), which, being
followed close by the Tinchel, are chased down into the valley where
we lay. Then, all the valley on each side being waylaid with a
hundred couple of strong Irish greyhounds, they are let loose, as
occasion serves, upon the herd of deer, [so] that with dogs, guns,
arrows, durks, and daggers, in the space of two hours, fourscore fat
deer were slain, which after are disposed, some one way and some
another, twenty or thirty miles, and more than enough left for us to
make merry withal at our rendezvous’.
After spending some days in this manner in the
Brae of Mar, the party, attended by Taylor, went 1618.
into Badenoch, and renewed the sport there for
three or four days, concluding with a brief visit to Ruthven Castle.
This grand old fortress—anciently the stronghold of the Cumins,
lords of Badenoch—seated on an alluvial promontory jutting into the
haugh beside the Spey, occupying an area of a hundred and twenty
yards long, and consisting of two great towers surrounded by a
fortified wall with an iron gate and portcullis,383 was now the
property of the Gordon family. Here, says Taylor, ‘my Lord of Enzie
and his noble countess (being daughter to the Earl of Argyle) did
give us most noble welcome for three days.’ ‘From thence we went
to a place called Ballo[ch] Castle, a fair and stately house, a worthy
gentleman being the owner of it, called the Laird of Grant.384... Our
cheer was more than sufficient, and yet much less than they could
afford us. There stayed there four days four earls, one lord, divers
knights and gentlemen, and their servants, footmen, and horses;
and every meal four long tables furnished with all varieties; our first
and second course being threescore dishes at one board; and after
that always a banquet; and there, if I had not forsworn wine till I
came to Edinburgh, I think I had there drank my last.’
The Water-poet was afterwards four days at Tarnaway, entertained
in the same hospitable manner by the Earl and Countess of Moray.
He speaks of Morayland as the pleasantest and most plentiful
country in Scotland, ‘being plain land, that a coach may be driven
more than four-and-thirty miles one way in it, alongst the sea-coast,’
He spent a few days with the Marquis of Huntly at the Bog, ‘where
our entertainment was, like himself, free, bountiful, and honourable,’
and then returned by the Cairn-a-mount to Edinburgh.
Here he was again in the midst of plentiful good cheer and good
company for eight days, while recovering from certain bruises he
had got at the Highland hunting. In Leith, at the house of Mr John
Stuart, he found his ‘long approved and assured good friend, Mr
Benjamin Jonson,’ who gave him a piece of gold of the value of
twenty-two shillings, to drink his health in England. ‘So with a
friendly farewell, I left him as well as I hope never to see him in a
worse estate; for he is among noblemen and gentlemen that know
his true worth and their own honours, where with much respective
love he is worthily entertained.’
In short, Taylor, in his progress through Scotland, seems to have
been everywhere feasted sumptuously, and supplied liberally with
money. So much of a virtue comparatively rare in England, and so
much plenty in a country which his own people were accustomed to
think of as the birthplace of famine, seems to have greatly
astonished him. The wonder comes to a climax at Cockburnspath,
near his exit from Scotland, where he was handsomely entertained
at an inn by Master William Arnot and his wife, the owners thereof. ‘I
must explain,’ he says, ‘their bountiful entertainment of guests,
which is this:
‘Suppose ten, fifteen, or twenty men and horses
come to lodge at their house. The men shall have 1618.
flesh, tame and wild fowl, fish, with all variety of
good cheer, good lodging, and welcome, and the horses shall want
neither hay nor provender; and at the morning at their departure the
reckoning is just nothing. This is this worthy gentleman’s use, his
chief delight being to give strangers entertainment gratis! And I am
sure that in Scotland, beyond Edinburgh, I have been at houses like
castles for building; the master of the house’s beaver being his blue
bonnet, one that will wear no other shirts but of the flax that grows
on his own ground, and of his wife’s, daughters’, or servants’
spinning; that hath his stockings, hose, and jerkin of the wool of his
own sheep’s backs; that never by his pride of apparel caused mercer,
draper, silk-man, embroiderer, or haberdasher, to break and turn
bankrupt; and yet this plain home-spun fellow keeps and maintains
thirty, forty, fifty servants, or perhaps more, every day relieving three
or four score poor people at his gate; and besides all this, can give
noble entertainment for four days together to five or six earls and
lords, besides knights, gentlemen, and their followers, if they be
three or four hundred men and horse of them, where they shall not
only feed but feast, and not feast but banquet; this is a man that
desires to know nothing so much as his duty to God and his king,
whose greatest cares are to practise the works of piety, charity, and
hospitality. He never studies the consuming art of fashionless
fashions; he never tries his strength to bear four or five hundred
acres on his back at once; his legs are always at liberty, not being
fettered with golden garters and manacled with artificial roses....
Many of these worthy housekeepers there are in Scotland....
has not been preserved to us. We can readily see that the work
contemplated must have been of a general character, for Jonson
wrote to Drummond (London, May 10, 1619), not merely for ‘some
things concerning the Loch of Lomond,’ but for copies of ‘the
inscriptions at Pinkie,’ referring probably to the Roman antiquities
which had been found in Queen Mary’s time at Inveresk, and also
bids him ‘urge Mr James Scott,’ and send ‘what else you can procure
for me with all speed.’ The king, he adds, was ‘pleased to hear of the
purpose of my book.’ How much to be regretted that we have not
the Scotland of that day delineated by so vigorous a pen as that of
the author of Sejanus!
The last visit Jonson paid in Scotland was to Drummond at
Hawthornden in the month of April, just before his return to London,
which, as we see, he had reached before the 10th of May. He lived
with Drummond on that occasion three weeks, enjoying, doubtless,
the vernal beauties of that romantic spot, as well as the converse of
his friend, and the more substantial hospitalities for which, if
Drummond be right, he had only too keen a relish. Their parting—
which, by Scottish use and wont, would be under the Covine Tree,
when royal Ben set out on foot as before to return to London—who
but wishes he could picture as it really was! Jonson’s letter of the
10th May, written soon after his arrival in London, and breathing of
the feelings which his excursion had excited, may aptly conclude this
notice:
‘TO MY WORTHY, HONOURED, AND BELOVED
FRIEND, MR W. DRUMMOND. 1618.
We find it noted that in this year a pearl was found in the burn of
Kellie, a tributary of the Ythan, Aberdeenshire, so large and beautiful
that it was esteemed the best that had at any time been found in
Scotland. Sir Thomas Menzies, provost of Aberdeen, obtaining this
precious jewel, went to London to present it to the king, who, in
requital, ‘gave him twelve or fourteen chalder of victual about
Dunfermline, and the custom of merchant goods in Aberdeen during
his life.’396 It has been reported that this pearl was inserted in the
apex of the crown of Scotland.
Apparently this circumstance called the king’s
attention to the old repute of certain Scottish rivers 1620.
for the production of pearls. In January 1621, we
find the Privy Council adverting to the fact, that the seeking for
pearls had for many years been left to interlopers, who pursued their
vocation at unseasonable times, and thus damaged the fishery, to
the hurt of his majesty’s interest, he having an undoubted right to all
pearls, as he had to all precious metals found in his dominions.
Being now inclined to take up pearl-seeking on his own account, he
issued a proclamation for the preservation of ‘the waters wherein the
pearls do breed;’ and, took measures to have the fishery conducted
on a regular plan ‘no pearls to be socht or taken but at such times
and seasons of the year when they are at their chief perfection both
of colour and quality, whilk will be in the months of July and August
yearly.’ The Privy Council commissioned three gentlemen to protect
the rivers, and ‘nominat expert and skilful men to fish for pearls at
convenient seasons;’ one gentleman for the rivers of Sutherland,
another for those of Ross, and another (Mr Patrick Maitland of
Auchincroch) for the waters Ythan and Don. The gentleman just
named was further made commissioner ‘for receiving to his
majesty’s use, of the haill pearls that sall be gotten in the waters
within the bounds above written, and who will give reasonable prices
for the same; the best of the whilk pearls for bigness and colour he
sall reserve to his majesty’s awn use.’
Patrick Maitland gave up his commission in July 1622, and it was
then conferred on Robert Buchan, merchant in Aberdeen, who was
reputed to be skilful in fishing for pearls, and ‘hath not only taken
divers of good value, but hath found some to be in divers waters
where none were expected.’—P. C. R.
Among the acts of the first parliament of Charles I. was one for the
‘discharge of Robert Buchan’s patent of the pearl and other
monopolies.’ Since then, there has occasionally been successful
fishing for pearls in this river; it is said that ‘about the middle of the
last century, a gentleman in Aberdeen got £100 for a lot of pearls
found in the Ythan.’ The mouth of the river has a great muscle and
cockle fishery, and is accordingly the haunt of an extraordinary
variety and quantity of sea-fowl. In summer, when the water is low,
school-boys often amuse themselves by going in search of pearls,
feeling with their toes for the shell, which is distinguished by its
curved shape, and griping it when found with a kind of forceps at
the end of a long stick.397
Our website is not just a platform for buying books, but a bridge
connecting readers to the timeless values of culture and wisdom. With
an elegant, user-friendly interface and an intelligent search system,
we are committed to providing a quick and convenient shopping
experience. Additionally, our special promotions and home delivery
services ensure that you save time and fully enjoy the joy of reading.
ebookfinal.com