Arkwood loves watching the Dave Channel on his TV set, but hates having to sit through the adverts. ‘Don’t fret,’ I told my seedy Belgian buddy, ‘I’ll write some python code on my Raspberry Pi which will send you an alert when the adverts have ended.’
It wasn’t too difficult to do. First I attached a webcam to my Pi and took a snap of the Dave emblem, the image the channel always uses to signal a TV show is about to start. Here it is captured on Arkwood’s TV set:
Great. Now all we need to do is configure the webcam to take a snap of the TV a few times a second, and as soon as it finds a close match to the Dave emblem we can alert Arkwood. Here’s the basic program:
from webcam import Webcam import pygame webcam = Webcam() # set up cymbal sound pygame.mixer.init() cymbal = pygame.mixer.Sound("75341__neotone__cymbol-scraped.wav") # wait until Dave image appears on TV webcam.detect_image('dave.jpg') # play cymbal, as adverts have ended cymbal.play()
We use Pygame to play a cymbal sound though a set of speakers attached to the Pi, to let my friend know his TV show is about to start.
Now, let’s delve a bit into how the webcam makes the match. Here’s the WebCam detect_image function:
import cv2 from datetime import datetime class Webcam(object): # constructor def __init__(self): self.webcam = cv2.VideoCapture(0) # save image to disk def _save_image(self, path, image): filename = datetime.now().strftime('%Y%m%d_%Hh%Mm%Ss%f') + '.jpg' cv2.imwrite(path + filename, image) # load image from disk def _load_image(self, path, filename): return cv2.imread(path + filename) # wait until image is detected def detect_image(self, filename): # set image threshold threshold = 250000 # load image loaded_img = cv2.cvtColor(self._load_image('WebCam/Image/', filename), cv2.COLOR_RGB2GRAY) # loop until we match our loaded image # with a webcam image while True: # obtain an image from webcam webcam_img = cv2.cvtColor(self.webcam.read(), cv2.COLOR_RGB2GRAY) # compare loaded image against webcam image diff = cv2.absdiff(loaded_img, webcam_img) count = cv2.countNonZero(diff) # debug print(count) # if the threshold has not been breached then # we have matched our loaded and webcam images if(count < threshold): self._save_image('WebCam/Match/', webcam_img) return True # pause to control webcam sample rate cv2.waitKey(250)
Our image detection is done using the OpenCV library.
First we set a threshold value.
Next we load our Dave emblem, the image we snapped earlier.
Now we can loop until we get an image from the TV set, via our webcam, that is below the threshold and therefore a close match. Bingo! We save the webcam image (so we can eyeball it later and confirm the match) and then end the function.
Here’s a screenshot showing a reading below the threshold, with the matched image saved to file:
And here is that matched image:
‘Hurray!’ I said to Arkwood, ‘You can now fix yourself a cup of tea during the ad break, safe in the knowledge that you will be alerted when the TV show is about to start.’
‘Yeah,’ he retorted, ‘But now I hate those fuckin’ cymbals. They set my nerves on edge.’
Well, as Chas and Dave once said, there Ain’t No Pleasing You.
In order to test the program through and obtain a suitable threshold, I saved a bunch of webcam images during a typical advert break and replayed them on my Raspberry Pi. Here’s some amendments I made to the code to replay these sample images:
import os #while True: for sample_file in os.listdir("WebCam/Sample/"): #webcam_img = cv2.cvtColor(self.webcam.read(), cv2.COLOR_RGB2GRAY) webcam_img = cv2.cvtColor(self._load_image('WebCam/Sample/', sample_file), cv2.COLOR_RGB2GRAY)
It also helps if you have something like a Sky+HD box, so you can rewind and play the TV adverts over and over, to confirm that the program works real-time.
Obtaining a suitable threshold value is a bit of trial and error. Also, I was testing during early evening and found that as dusk rolled in it played havoc with my threshold (I’m guessing the reduced daylight affected image detection).
Here’s some other images that were a close match, as I relaxed the threshold:
And some images which had no chance of being a close match:
Note all images are in JPEG format.