Step 2
Finding balls by colour
Turn an image into masks with numpy, then locate each ball as the centroid of its pixels.
Builds on Noise, mean, variance & bias, from the free Foundations.
Pictures are arrays
To your program, img.rgb is just a (240, 320, 3) array: channels 0, 1 and 2 are red, green and blue. Comparing whole channels gives a mask, a boolean array marking the pixels that pass:
r, g, b = [rgb[..., i].astype(float) for i in range(3)]
red = (r > 50) & (g < 0.4 * r) & (b < 0.4 * r)
Convert to float first, because uint8 arithmetic wraps around at 255.
Use ratios, not levels
A ball's shadowed side is about three times darker. Shading scales all three channels by the same factor, so a rule like r > 150 loses the shadowed half while ratios such as don't change. Fully lit colours:
| Surface | r | g | b |
|---|---|---|---|
| red ball | 230 | 61 | 61 |
| yellow ball | 247 | 194 | 26 |
| green ball | 51 | 179 | 107 |
| blue ball | 64 | 107 | 230 |
| table | 146 | 111 | 80 |
| orange arm joints | 255 | 115 | 46 |
The table and the yellow ball have almost the same . What sets yellow apart is its tiny . (Real vision code often converts to HSV for the same reason: hue ignores brightness.)
Centroids
vs, us = np.nonzero(mask) returns the row and column of every selected pixel; the ball's centre is their mean, . A ball covers about 100–180 pixels here, so ask for a minimum count (say 20) before you believe a mask. That's also how you report a missing ball.
Your task
Implement find_ball(rgb, color) for "red", "yellow", "green" and "blue". It returns (u, v), or None if the ball isn't visible. The starter shows every mask in the Images tab so you can see what your rules pick up.
The grader renders three new scenes. Each centroid must be within 3 px of the ball's true centre, and in one scene the blue ball is hidden under the table. For a bonus, it adds sensor noise: a few stray pixels far from the ball can drag a mean away, but a median barely moves.