Reading an analog dial with image processing

Justin Pearson
2014-10-19

index_1.gif

Function to display video metadata

index_2.gif

index_3.gif

index_4.gif

Video of gas meter

I recorded a video of the gas line next to my apartment:

index_5.gif

index_6.gif

index_7.gif

index_8.gif

index_9.gif

index_10.gif

index_11.gif

index_12.gif

index_13.gif

index_14.gif

index_15.gif

Too big to import every frame of. Instead I used After Effects 6.5 to stabilize and crop it, making “dial.mov”.

Import

index_16.gif

index_17.gif

index_18.gif

index_19.gif

index_20.gif

index_21.gif

index_22.gif

index_23.gif

index_24.gif

index_25.gif

index_26.gif

index_27.gif

index_28.gif

Manually record dial positions

index_29.gif

index_30.gif

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.

index_31.gif

index_32.gif

index_33.gif

index_34.gif

index_35.gif

Graphics:Manually-recorded training data of dial position

That discontinuity where it wraps will cause problems, as we’ll see.

It’ll be nice to draw a cute picture of a gauge.

index_37.gif

index_38.gif

training pic dial position
(read manually)
pretty position
index_39.gif 8. Graphics:None
index_41.gif 7. Graphics:None
index_43.gif 6. Graphics:None
index_45.gif 5. Graphics:None
index_47.gif 4. Graphics:None
index_49.gif 3. Graphics:None
index_51.gif 2. Graphics:None
index_53.gif 1. Graphics:None
index_55.gif 0. Graphics:None
index_57.gif 9. Graphics:None
index_59.gif 8. Graphics:None
index_61.gif 7. Graphics:None
index_63.gif 6. Graphics:None
index_65.gif 5. Graphics:None
index_67.gif 4. Graphics:None
index_69.gif 3. Graphics:None
index_71.gif 2. Graphics:None
index_73.gif 1. Graphics:None
index_75.gif 0. Graphics:None

Simplest place to start: use built-in Predict[]

There are a lot of built-in methods that Predict[] uses:

index_77.gif

Will any of them work out-of-the-box?

index_78.gif

index_79.gif

index_80.gif

Weirdly, the LinearRegression predictor varies across subsequent invocations:

index_81.gif

index_82.gif

index_83.gif

index_84.gif

LinearRegression index_85.gif
NearestNeighbors index_86.gif
NeuralNetwork index_87.gif
RandomForest index_88.gif
GaussianProcess index_89.gif

Does any of these predictors work out-of-the-box?

Unsurprisingly, the predictors are decent predicting the data they were trained on:

index_90.gif

index_91.gif

index_92.gif

index_93.gif

index_94.gif

index_95.gif

index_96.gif

index_97.gif

index_98.gif

index_99.gif

index_100.gif

But the predictors work horribly on non-training images:

index_101.gif

index_102.gif

index_103.gif

index_104.gif

index_105.gif

index_106.gif

index_107.gif

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.

index_108.gif

index_109.gif

index_110.gif

index_111.gif

Let’s dilate the image a bit, then take the centroid of the largest blob.

index_112.gif

index_113.gif

index_114.gif

index_115.gif

index_116.gif

index_117.gif

index_118.gif

index_119.gif

index_120.gif

Biggest blob:

index_121.gif

index_122.gif

index_123.gif

index_124.gif

index_125.gif

index_126.gif

Roll it up:

index_127.gif

index_128.gif

index_129.gif

index_130.gif

index_131.gif

index_132.gif

index_133.gif

index_134.gif

index_135.gif

That component-measurement thing might be good enough that we can just arctan it to get the dial position and be done with it.

index_136.gif

index_137.gif

index_138.gif

index_139.gif

index_140.gif

training pic dial position
(read manually)
predicted position pretty
index_141.gif 8. 7.92911 Graphics:None
index_143.gif 7. 6.92176 Graphics:None
index_145.gif 6. 5.93068 Graphics:None
index_147.gif 5. 5.13835 Graphics:None
index_149.gif 4. 4.16442 Graphics:None
index_151.gif 3. 3.06872 Graphics:None
index_153.gif 2. 2.12698 Graphics:None
index_155.gif 1. 1.15822 Graphics:None
index_157.gif 0. 0.0209366 Graphics:None
index_159.gif 9. 8.99893 Graphics:None
index_161.gif 8. 7.94127 Graphics:None
index_163.gif 7. 6.91711 Graphics:None
index_165.gif 6. 5.93815 Graphics:None
index_167.gif 5. 5.09774 Graphics:None
index_169.gif 4. 4.05716 Graphics:None
index_171.gif 3. 3.19994 Graphics:None
index_173.gif 2. 2.16478 Graphics:None
index_175.gif 1. 1.18382 Graphics:None
index_177.gif 0. 9.93654 Graphics:None

Looks very nice.

index_179.gif

index_180.gif

index_181.gif

index_182.gif

index_183.gif

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.

index_184.gif

index_185.gif

Let’s see how well these do:

index_186.gif

index_187.gif

index_188.gif

index_189.gif

index_190.gif

index_191.gif

Armed with these, this function calculates the angle of the dial in degrees, then the dial position in [0,10):

index_192.gif

index_193.gif

index_194.gif

Do the predictor functions work?

index_195.gif

index_196.gif

That looks like 4 o’clock degrees, nice.

index_197.gif

index_198.gif 145.817 8.44953
index_199.gif -148.789 6.63304
index_200.gif -83.7146 4.82541
index_201.gif -14.2011 2.89448
index_202.gif 71.5818 0.511615
index_203.gif 160.953 8.02908
index_204.gif -126.611 6.01697
index_205.gif -53.9613 3.99892
index_206.gif 29.6138 1.6774

Predict the dial’s angle for all zillion frames.

index_207.gif

index_208.gif

index_209.gif

index_210.gif

index_211.gif

index_212.gif

index_213.gif

index_214.gif

index_215.gif

We’ve removed the discontinuity in the predictor! This is because the predictor is secretly predicting the x and y coords.

index_216.gif

Created with the Wolfram Language