opencv
and Z3
to avoid writing
very much ourselves.
You can find sample game images here.
The general implementation plan involves the following steps:
ssh
ing (use mosh.) to attu
. Make a new folder for this week's project (call it something like set
).
This week, we will be using opencv
and numpy
. So, you should make sure to import these as follows:
import numpy as np import cv2Additionally, since we will be working with pictures, it will be extremely helpful to be able to refresh them without
scp
ing every time.
The easiest solution is to spin up a webserver using python
. Make sure you are in your set
directory and run the following command:python -m http.server PORT &
hostname
. Go to your webbrowser and type in the result of the hostname
followed by ":PORT". For example, if you are
on attu4.cs.washington.edu
and you used 9999 as PORT, then you would go to http://attu4.cs.washington.edu:9999.
opencv
to isolate each card from a starting image of the "game board".
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
opencv
opencv
are read and write images. The code looks like this:
The first line reads in an image from the file named img.png, and the second line writes out a copy of the image stored in the variable img
to a file named img_copy.png.
cv2.cvtColor
function with the flag cv2.COLOR_BGR2GRAY
.
The result should look something like this:
mask = np.zeros(imgray.shape, np.uint8)
cv2.drawContours(mask, [cnt], 0, 255, -1)
corners
and rect
(in the order top-left, top-right, bottom-left, bottom-right):
M = cv2.getPerspectiveTransform(np.array(corners, dtype="float32"), np.array(rect, dtype="float32"))
card
into warp
: warp = cv2.warpPerspective(card, M, (w, h))
opencv
to find the four features of a single card (color, shape, number, and fill).
Result | Choices | |
color | purple | purple, red, green |
shape | diamond | squiggle, oval, diamond |
number | 3 | 1, 2, 3 |
fill | striped | open, striped, solid |
opencv
opencv
are read and write images. The code looks like this:
kernel = np.ones((2,2),np.uint8)
img = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)
cv2.cvtColor
function with the flag cv2.COLOR_BGR2GRAY
.
The result should look something like this:
opencv
finding
all of them; then, we filter them down to the ones we care about. On our example card, the contours highlighed in green look like this:
cv2.findContours
returns a tuple of three items:
img
: A copy of the image--this return value can be ignoredcontours
: A list of the actual contourshierarchy
: A dictionary of the contours that contains information about the topology of the imagehierarchy
is the most complicated of the three. For our example image, it might look something like the following:
[[[-1 -1 1 -1] [ 3 -1 2 0] [-1 -1 -1 1] [ 5 1 4 0] [-1 -1 -1 3] [-1 3 6 0] [-1 -1 -1 5]]]The four values in each list represent:
[Next, Previous, First_Child, Parent]
for the corresponding contour (indexed the same way as the elements of contours
).
For our application, we are interested only in the contours at the "second level". In other words, we don't care about the contour around the entire card (which will be the
0th contour), and we don't care about any contours inside any children of the root. This means that we only want contours (indexed by i
)
for which the value of hierarchy[0][i][3]
is 0 (the root). We urge you to think about why that makes sense by tracing these values in the hierarchy above. Once we
filter out these contours, we're left with something like:
hierarchy
will be enough; however, that's not always true. Sometimes, the image might have artifcacts in it.
This is particularly true of the squiggle shape. The solution to this problem is to only consider contours that have area of a reasonable size. You can use the
cv2.contourArea(cnt)
function to calculate the area of a contour cnt
. We recommend ensuring that the "valid" contours are at least 50 pixels and less than
1/3 of the pixels in the whole image.
whole_shape = np.zeros(imgray.shape, np.uint8)
cv2.drawContours(whole_shape, [cnt], 0, 255, -1)
cv2.drawContours(whole_shape, [cnt], 0, 0, 2)
cv2.bitwise_and()
useful.
Once you've anded the two masks together, you should result in a mask like the following:
opencv
has a "mean" function that returns the average color of an image over a mask: cv2.mean(img, mask)
. The hue of the outside mask is exactly the
color of the card! The cutoffs we've found to work are 100 to 160 for purple, 50 to 100 for green, and the rest of the space for red.
opencv
's cv2.matchShapes(img, SHAPE, 1, 0.0)
function. If we have a "pure" version
of each shape (like the following!),