It happens from time to time in football: thousands of people filling a stadium watch the ball cross the goal line, but the most important of them all – the referee – hasn’t seen it. Those thousands are aggrieved, the goal is not given, and the whole course of a tournament or league competition is not what it might have been.
Or perhaps the referee does award a goal – one that the defending side insists shouldn’t stand. The German language even has a specific term for it: a “Wembley-Tor”, after the goal that gave England a 3-2 lead in the 1966 World Cup final. The story that the “Russian” linesman, actually from Azerbaijan, when asked how he could be so sure that the goal was legitimate, replied simply: “Stalingrad” is probably apocryphal.
Now, technology is being introduced in an attempt to overcome human error with mathematical certainty.
The Goal-Line Technology comprised of three modules:
1.The Camera module can be used to take video and pass it to raspberry pi
2.The Raspberry Pi processes the video and detect the passing of the ball from the line and announce the answer to the speaker for warning or no warning.
3.The speaker receives a signal from the Raspberry pi and acts accordingly.
The pieces we need for this design:
1. a wooden football goal that a Carpenter made for us and a ball.
2. A smartphone camera that we used instead of a Raspberry cam for cost reduction.
3. Raspberry pi and speaker
Once we had the hardware cobbled together we needed some software.
The software needs to grab the live stream from the Camera and detect when the ball fully crosses the goal line. To speed things up we made use of the excellent Open-CV library, which on a fresh Raspian image is just a simple apt-get away:
sudo apt-get install opencv-*
Since we were using a fresh Raspian Image we also need to get build essential:
sudo apt-get install build-essential
Now we have the compiler and the Open-CV library.
The following code can ِdetect a ball and say whether it has crossed the line or not.
# python dynamic_color_tracking.py --filter HSV --webcam import cv2 import argparse import numpy as np def callback(value): pass def setup_trackbars(range_filter): cv2.namedWindow("Trackbars", 0) for i in ["MIN", "MAX"]: v = 0 if i == "MIN" else 255 for j in range_filter: cv2.createTrackbar("%s_%s" % (j, i), "Trackbars", v, 255, callback) def get_arguments(): ap = argparse.ArgumentParser() ap.add_argument('-f', '--filter', required=True, help='Range filter. RGB or HSV') ap.add_argument('-w', '--webcam', required=False, help='Use webcam', action='store_true') args = vars(ap.parse_args()) if not args['filter'].upper() in ['RGB', 'HSV']: ap.error("Please speciy a correct filter.") return args def get_trackbar_values(range_filter): values =  for i in ["MIN", "MAX"]: for j in range_filter: v = cv2.getTrackbarPos("%s_%s" % (j, i), "Trackbars") values.append(v) return values def main(): args = get_arguments() range_filter = args['filter'].upper() camera = cv2.VideoCapture(0) setup_trackbars(range_filter) while True: if args['webcam']: ret, image = camera.read() if not ret: break if range_filter == 'RGB': frame_to_thresh = image.copy() else: frame_to_thresh = cv2.cvtColor(image, cv2.COLOR_BGR2HSV) v1_min, v2_min, v3_min, v1_max, v2_max, v3_max = get_trackbar_values(range_filter) thresh = cv2.inRange(frame_to_thresh, (v1_min, v2_min, v3_min), (v1_max, v2_max, v3_max)) kernel = np.ones((5, 5), np.uint8) mask = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel) mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel) cnts = cv2.findContours(mask.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2] center = None # only proceed if at least one contour was found if len(cnts) > 0: # find the largest contour in the mask, then use # it to compute the minimum enclosing circle and # centroid c = max(cnts, key=cv2.contourArea) ((x, y), radius) = cv2.minEnclosingCircle(c) M = cv2.moments(c) center = (int(M["m10"] / M["m00"]), int(M["m01"] / M["m00"])) # only proceed if the radius meets a minimum size if radius > 10: # draw the circle and centroid on the frame, # then update the list of tracked points cv2.circle(image, (int(x), int(y)), int(radius), (0, 255, 255), 2) # show the frame to our screen if x < 200: cv2.putText(image, "GOOOAALLLL!! ", (50, 50), cv2.FONT_HERSHEY_COMPLEX_SMALL, .7, (0, 255, 0)) else: cv2.putText(image, "NO GOAL", (50, 50), cv2.FONT_HERSHEY_COMPLEX_SMALL, .7, (0, 0, 255)) cv2.line(image, (200, 0), (200, 511), (255, 0, 0), 5) cv2.imshow("Original", image) cv2.imshow("Thresh", thresh) cv2.imshow("Mask", mask) if cv2.waitKey(1) & 0xFF is ord('q'): break if __name__ == '__main__': main()
And the photo's of execution:
Using Open-CV and relatively little code we managed to knock up something that detects when a football crosses a goal line. It would obviously need lots more work to take into account players heads and other image artifacts before being usable.
Unfortunately we came to the conclusion that a single Pi is just not up to the job. Perhaps if we have any spare time we can create a distributed solution with a cluster of Pi’s!