Reading an analog dial with image processing
Justin Pearson
2014-10-19
Function to display video metadata
Video of gas meter
I recorded a video of the gas line next to my apartment:
Too big to import every frame of. Instead I used After Effects 6.5 to stabilize and crop it, making “dial.mov”.
Import
Manually record dial positions
Manually record roughly what frames show which dial positions. Despite this dial rotating CCW, we label the positions like clock hands, clockwise with 0 at the top, up to 9.
That discontinuity where it wraps will cause problems, as we’ll see.
It’ll be nice to draw a cute picture of a gauge.
training pic | dial position (read manually) |
pretty position |
8. | ||
7. | ||
6. | ||
5. | ||
4. | ||
3. | ||
2. | ||
1. | ||
0. | ||
9. | ||
8. | ||
7. | ||
6. | ||
5. | ||
4. | ||
3. | ||
2. | ||
1. | ||
0. |
Simplest place to start: use built-in Predict[]
There are a lot of built-in methods that Predict[] uses:
Will any of them work out-of-the-box?
Weirdly, the LinearRegression predictor varies across subsequent invocations:
LinearRegression | |
NearestNeighbors | |
NeuralNetwork | |
RandomForest | |
GaussianProcess |
Does any of these predictors work out-of-the-box?
Unsurprisingly, the predictors are decent predicting the data they were trained on:
But the predictors work horribly on non-training images:
They’re all horrible. The best one is NearestNeighbors, which still sucks becuase it only returns values that were exactly in the training set (no interpolation).
So: let’s explore another technique for reading a picture of an analog dial.
Read dial with image processing
Let’s see if we can do better by just using good old image processing.
The first step in cleaning up the image feature-space is to use simpler images. Remove the background.
Let’s dilate the image a bit, then take the centroid of the largest blob.
Biggest blob:
Roll it up:
That component-measurement thing might be good enough that we can just arctan it to get the dial position and be done with it.
training pic | dial position (read manually) |
predicted position | pretty |
8. | 7.92911 | ||
7. | 6.92176 | ||
6. | 5.93068 | ||
5. | 5.13835 | ||
4. | 4.16442 | ||
3. | 3.06872 | ||
2. | 2.12698 | ||
1. | 1.15822 | ||
0. | 0.0209366 | ||
9. | 8.99893 | ||
8. | 7.94127 | ||
7. | 6.91711 | ||
6. | 5.93815 | ||
5. | 5.09774 | ||
4. | 4.05716 | ||
3. | 3.19994 | ||
2. | 2.16478 | ||
1. | 1.18382 | ||
0. | 9.93654 |
Looks very nice.
OLD: Predict dial x,y coordinates
I locked these cells because newer versions of MMA don’t produce the same nice outputs we have here. I left this here as a cautionary tale -- if you use MMA’s Predict[] function, and they change it later, it’ll break your stuff.
To get around the 0->9 discontinuity, let’s make two predictors: one for the x coordinate of the tip of the dial, and one for the y coordinate. These are continuous quantities of the angle, so perhaps this will be more amenable to prediction.
Let’s see how well these do:
Armed with these, this function calculates the angle of the dial in degrees, then the dial position in [0,10):
Do the predictor functions work?
That looks like 4 o’clock degrees, nice.
145.817 | 8.44953 | |
-148.789 | 6.63304 | |
-83.7146 | 4.82541 | |
-14.2011 | 2.89448 | |
71.5818 | 0.511615 | |
160.953 | 8.02908 | |
-126.611 | 6.01697 | |
-53.9613 | 3.99892 | |
29.6138 | 1.6774 |
Predict the dial’s angle for all zillion frames.
We’ve removed the discontinuity in the predictor! This is because the predictor is secretly predicting the x and y coords.