Data from home – district heating, computer vision (Part 4)

This post is the fourth part of this series. If you want to start reading from the first part, you can find it here: Data from home – concept and results (Part 1)

The next challenge within gathering more data was the district heating meter. The meter happens to be manufactured by Kamstrup just like my water meter. While water meter is my own, this heating meter is sealed by local district heating company. They offer pulse output installation for a price of over 400€, which is too much for me. Luckily the meter has a default display with the meter reading. So what could be more interesting than learning to read that display with a camera?

First I started with some optical character recognition (OCR) programs like tesseract.

Photo of printed text: An easy task for the tesseract

It came quickly obvious that it is hard to read my meter display with OCR. This is since in the display numbers are presented with a seven segment display. This time I found OCR program specialized for reading seven segment displays. So I tested out the ssocr. Trying it out on my water kettle with a backlit LCD gave promising results.

Backlit LCD display: An easy task for the ssocr

In my meter I have an unlit LCD. To make things even worse I am taking photos of it with Raspberry Pi 2 Model B and Camera Module V2, which does not provide very good quality. After experimenting for some time with the ssocr I decided it is time to try out something different. I found this excellent blog by Adrian from pyimagesearch with an example that fits my situation perfectly: “Recognizing digits with OpenCV and Python“. So I set up my camera and gave it go!

Photo of the meter taken with raspberry Pi Camera V2

Being still new with Python and first timer with the OpenCV there was going to be some learning ahead. I approached Adrian’s blog post by running one step at the time and trying to get to similar results as he had. I think the image quality made the process at least hard enough so I took some liberties, first of which was to crop image so that the meter case will not end up as complete to the picture to help finding the display. Otherwise I would end up with the display not being the largest rectangular object. I also draw black lines around the display with a marker to make contrast a bit bigger.

Trying to find rectangular shapes like the display

This computer vision based approach is forgiving in a way that you don’t have to make pixel per pixel crop or alignment. OpenCV will find the display and make it rectangular for you even if the photo differs from take to take.

Display found by OpenCV

Then its time to make some image manipulation trying to make the numbers as clear as possible. In this display there are borders surrounding the last three digits which makes it even harder. The decimal point is not making things any easier either.

Photo taken with an iPhone looks quite nice and shows the borders around last three digits more clearly

Lets continue with photo taken by Raspberry Pi Camera V2. It looks already really easy to read by human eye. For the method used here, the biggest problem is the gaps between segments inside the digits.

Image made from Raspberry Pi Camera V2 photo

After making some more manipulation the image is ready for the next step. Again I decided to make a rough crop from top and bottom parts of the image to get rid of error prone noise in the image.

Digits ready for recognition
Digits found by OpenCV

The rest of the process goes pretty much according to Adrian’s blog. Along the process I had to make quite some adjustments to the script. For example the seven segment representation of the digits in my display is a bit different from the example so I had to adjust the dictionary accordingly. I also decided to go with full resolution available. The most of the image manipulation is based on MORPH_OPEN and MORPH_CLOSE functions.

# Removing spots
kernel2c = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (7, 9))
threshc = cv2.morphologyEx(threshc, cv2.MORPH_OPEN, kernel2c)

# Handling disconnected segments within digits
kernel2d = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (8, 12))
threshd = cv2.morphologyEx(threshc, cv2.MORPH_CLOSE, kernel2d)

To be honest, the process is not 100% accurate yet. So I took some more liberties and made the following check within the script.

# Check if last known reading is available. Compare and only allow same or up to .002 bigger values to be accepted

if(cachedold > 0):
	if((float(digits0) >= float(cachedold)) and (float(digits0) <= (float(cachedold) + 0.002))):
		client = requests.session()
		client.post(KL_URL, data=ready)

The result is that reliability is good enough for now. I am getting the reading consistent to the Grafana.

Grafana graph showing the district heating meter reading

Combined with the data from the previous parts I can now try to make correlation between water flow and heating power. This would look a lot nicer if I had higher resolution available. Unfortunately the display of the meter is showing only to the kWh resolution.

Grafana graph showing the correlation between water flow and heating power during a bath

During these couple of weeks I have had a couple of breaks where reading has failed so bad that eventually my cached value check has stopped updating the values totally. I am planning on changing the lights for my camera as soon as I get the new ones. Hopefully it will make image quality a bit better and the process more robust.

You can find the next part of this series here:

Data from home – alerting and water shutoff (Part 5)