img = cv2.imread("SampleImages/bristleconePine.jpg")
# Filter types: MORPH_DILATE, MORPH_ERODE, MORPH_OPEN, MORPH_CLOSE, MORPH_TOPHAT, MORPH_BLACKHAT, MORPH_GRADIENT
morphType = cv2.MORPH_DILATE
# Filter shapes: MORPH_RECT, MORPH_ELLIPSE, MORPH_CROSS, plus you can define your own
morphShape = cv2.MORPH_RECT
kernelObj = cv2.getStructuringElement(morphShape, (5, 5))
newImg = cv2.morphologyEx(img, morphType, kernelObj)
cv2.imshow("Original", img)
cv2.imshow("Morphed", newImg)
cv2.waitKey()Overview
In this activity you will experiment with morphological filters, blurring, and edge detection. You’ll look at how we might use these filters to simplify images so that we can find the things in them better. We’ll look at finding coins and balls, as we did with thresholds, and we’ll also just try using these activities on frames of a video.
The Github repository for this assignment will contain a starter code file, activ13.py. Put your code in this file, and create others, as directed below and according to the TODO comments in the file.
Morphological filters
First we’ll briefly summarize the different morphological filters. They are discussed in much more detail in the readings, so bringing that up while you work on this activity is a good idea.
Dilation and Erosion: Much like blurring, dilation and erosion determine the value for a pixel based on a neighborhood of pixels from the original image. Unlike the default in blurring, we can select the shape of the neighborhood, as well as its size, to be either rectangular, elliptical, or cross shaped. With dilation, the value of each channel of a pixel is the maximum value of that channel in any pixel in its neighborhood. Erosion is the opposite: the value of each channel of a pixel is the minimum value of that channel in any pixel in its neighborhood.
These can be used to emphasize and thicken edges or regions of color in a particular part of an image.
Opening and Closing: Sometimes we want to preserve both dark and light features of an image. Opening and closing combine dilation and erosion. Opening an image means first performing an erosion of the image, then a dilation, using the same size and shape of neighborhood. Closing first dilates the image, then erodes it.
Both these operations are good at removing noise and small details from images.
Top-hat and Black-hat: The Top-Hat filters do the opposite of opening and closing. Instead of removing the fine details, these filters keep just the fine details. The white top-hat takes the difference between the original image and the opening of the image. The black top-hat takes the difference between the closing and the original image.
These filters may be used for feature extraction tasks, or image enhancement.
Morphological Gradient: The morphological gradient takes the difference between the dilation and erosion of an image. This emphasizes the places where there is a change in color, and can be useful to enhance edges.
In the SampleCode folder provided to you is a demo program for how to use the morphological filters: simpleMorph.py. I have copied the code below.
Change the image loaded to any one of your choice. Then experiment with choosing different filter types (the morphType variable), different neighborhood shapes (the morphShape variable), and different neighborhood sizes (defined in the call to getStructuringElemeng).
CHOOSE ONE OF THE FOLLOWING TO COMPLETE:
Try this to hand in: Here we can practice with smoothing images to make thresholding work better, or cleaning up the result of thresholding.
In the earlier activity on masks and thresholds, you worked on isolating coins and balls using color and grayscale thresholding. If you didn’t do that, skip this activity and go on to the next one. Otherwise, go find your code, and choose the best examples you have of thresholding separating the ball or the coins from the background.
Copy your code to this project, and experiment with modifying it in various ways:
- Apply a morph filter like opening or close (or erosion/dilation) to the original image, before you use thresholding on it.
- Analyze the results: does make the overall program work better, worse, or about the same?
- Try varying the neighborhood shape and size
- Try different morphological filters: do any of them improve your results?
- Apply a morph filter like opening or closing to the threshold image (to clear up some of the noise, fill in partially detected shapes, etc.)
- Analyze the results: does make the overall program work better, worse, or about the same?
- Try varying the neighborhood shape and size
- Try different morphological filters: do any of them improve your results?
Try this to hand in: Here we will just practice with using the morphologyEx program in a fun way (could be helpful for the open-ended homework question). The goal is to have the morphing fluctuate over time between small effects (small neighborhood sizes) to large effects, and back again. Start by copying one of the video-reading programs, ideally one with the processImage helper.
- Choose one of the morphological filters described above
- Before the
whileloop, set up a variablekSizeto be used as the neighborhood size - Also set up a variable
deltakto hold the value 2, this is how muchkSizewill change from one frame to the next - In the
processImagefunction (or in the middle of the loop, if you don’t have it), set up a neighborhood “structuring element” object with(kSize, kSize)as its size - Apply
morphologyExto the current frame, using the filter you chose and the structuring element you just created - Display the morphed image instead of the original
- At the end of the
whileloop, add anifstatement that checks if the neighborhood size is too large or too small (too large might be 21 or 25, too small would be 3). If true, do:deltak = -deltak. This will change howkSizeis changing from frame to frame - After the
ifstatements (and still inside the loop), updatekSizeby addingdeltakto it
Blurring an image
Blurring is used to remove noise and variation from an image, making other operations work better. Look at the code samples in the reading to remind yourself how to use blur and GaussianBlur.
CHOOSE ONE OF THE FOLLOWING TO COMPLETE:
Try this to hand in: This task is similar to the first task for morphological filters. Here we can practice with smoothing images to make thresholding work better, or cleaning up the result of thresholding.
In the earlier activity on masks and thresholds, you worked on isolating coins and balls using color and grayscale thresholding. If you didn’t do that, skip this activity and go on to the next one. Otherwise, go find your code, and choose the best examples you have of thresholding separating the ball or the coins from the background.
Copy your code to this project, and experiment with modifying it in various ways:
- Apply either the basic blur or Gaussian blur to the original image, before you use thresholding on it.
- Analyze the results: does make the overall program work better, worse, or about the same?
- Try varying the neighborhood size
- Try switching between basic and Gaussian blur: does either work better at improving the results?
- Apply blurring to the threshold image (to clear up some of the noise)
- Analyze the results: does make the overall program work better, worse, or about the same?
- Try varying the neighborhood shape and size
- Try both simple and Gaussian blur: do either of them improve your results?
Try this to hand in: Here we will just practice with using blurring on frames from a video. The goal is to have the blurring fluctuate over time between small effects (small neighborhood sizes) to large effects, and back again. Start by copying one of the video-reading programs, ideally one with the processImage helper.
- Choose basic or Gaussian blurring
- Before the
whileloop, set up a variablekSizeto be used as the neighborhood size - Also set up a variable
deltakto hold the value 2, this is how muchkSizewill change from one frame to the next - In the
processImagefunction (or in the middle of the loop, if you don’t have it), set up a neighborhood “structuring element” object with(kSize, kSize)as its size - Apply the blur you’ve chosen to the current frame, using the filter you chose and the structuring element you just created
- Display the blurred image instead of the original
- At the end of the
whileloop, add anifstatement that checks if the neighborhood size is too large or too small (too large might be 21 or 25, too small would be 3). If true, do:deltak = -deltak. This will change howkSizeis changing from frame to frame - After the
ifstatements (and still inside the loop), updatekSizeby addingdeltakto it
Edge Detection
Edges, in computer vision, are just patches where the brightness in an image changes dramatically from one side of the patch to another. Your readings looked at two edge detection methods, the very basic Sobel filter and the more complex Canny algorithm (review that section if you need to).
We will focus on implementing these two filters on a video feed. Looking at the edge detection on a video feed, and being able to change the view, put objects in front of the camera, hold up items with words on them, all these things help to strengthen your intuitions about what edges are, and what these functions can do for you.
Try this to hand in: Just like the previous sections, start with a basic video program. Then, either copy the code example for Soble from the readings (also found in EdgesAndLines.py in SampleCode), or use the stripped-down version here:
Apply this code to the frames of the video. No need to vary anything here, just run the filter steps on the frame and display the result.
Try this to hand in: Do the same thing as the previous example, except run the Canny algorithm on the frame instead. Below are some examples of Canny run with different input parameters (notice that Canny can take in a color image).
Recall that the two numbers input to Canny are threshold values: a lower threshold and an upper threshold.
- All pixels with brightness below the lower threshold are set to zero: their edges are discarded
- All pixels with brightness above the upper threshold are set to 255
- Pixels with brightness between the two thresholds are set to 255 if they have an adjacent pixel that is 255, otherwise they are set to zero
One of the issues with Canny is figuring out the best set of threshold values for any given situation.
Add to your Canny video program: Let’s add a method to the Canny video program for the user to change the threshold values.
- Before the
whileloop, set up two variables:lowThreshanduppThresh, and initialize them to some reasonable initial values (maybe 100 and 200, for instance) - Pass
lowThreshanduppThreshtoCannyinstead of hard-coding numbers into the call - Add more cases to the
ifstatement that checks what key the user pressed fromwaitKey- If the user presses the w key, then add a small amount (between 1 and 5) to
lowThresh - If the user presses the s key, then subtract a small amount from
lowThresh(be sure not to go below 0 or aboe 255) - If the user presses the e key, then add a small amount to
uppThresh - If the user presses the d key, then subtract a small amount from
lowThresh
- If the user presses the w key, then add a small amount (between 1 and 5) to
Look at how different threshold values change what Canny shows you. Try holding up books or other items with writing on them. Hold up a ball to see whether you can get a clean edge around the ball.
What to hand in
Put all of your function definitions for this activity into the activ13.py file to be submitted. Make sure you format your code appropriately:
- At the top of the file is a triple-quoted string describing the file
- Next you include all import statements
- Next you have your function definitions, visually separated by blank lines, and maybe comments with dashed or other visual horizontal lines
- Each function should have a triple-quoted descriptive comment right after the
defline - All calls to all functions should be in a script at the bottom of the file, ideally inside an
if __name__ ...block
Use commit and push to copy your code to Github to submit this work.