I have made a morse code translator and I want it to be able to record a flashing light and make it into morse code. I think I will need OpenCV or a light sensor, but I don't know how to use either of them. I haven't got any code for it yet, as I couldn't find any solutions anywhere else.
-
Please read [How to ask](https://stackoverflow.com/help/how-to-ask). – Stefan Falk Jul 30 '21 at 10:22
-
Using OpenCV's camera input will probably be the easiest. All you really have to do is to read each frame, figure out if they're lit or not, and record the times when a lit frame turns into unlit and the other way around, then turn that into a morse sequence. (You'd probably want to look at the average length of each lit period and consider ones shorter than that to be dots and longer ones to be dashes.) – AKX Jul 30 '21 at 10:24
-
But the answer is: Yes, this is definitely possible but it depends on your requirements. The simplest thing would be, assuming the image has the flashlight e.g. in the center of the image, to simply calculate the average brightness or a certain area in each image and track the duration of each positive edge (light goes on) until the next negative edge (light goes down). Now you only have to cluster the signals into two classes by length and you're (basically) done. – Stefan Falk Jul 30 '21 at 10:24
-
what format is your flashing light? like how does your computer see it? or have you got no plan whatsoever?........ if you got no plan whatsoever, find a raspberry pi and light sensor first. – Joshua Jul 30 '21 at 10:26
-
@StefanFalk This sounds like it would work, and I have looked for how to do it but found no tutorials. How can you do it? – Pazzel Aug 20 '21 at 07:43
-
If you have a video, you can e.g. simply calculate the average brightness of each frame. After normalizing the values, everything below 0.5 is "turned off" and anything above 0.5 is "turned on". You'll then have a simply binary signal `[0, 1, 0, 1, 1, 1, 0, ..]` which you could already (kind of) interpret as morse code. The only problem here is time and variations in timing. You'll need an estimate for each kind of flash (short/long). You can try to cluster flashes into two groups by computing their duration and divide them into two groups given a threshold. – Stefan Falk Aug 20 '21 at 08:08
-
E.g. everything less then 0.5 seconds is a short flash, everything longer than 1 second is a long flash. Anything in between is a grey area any may be the source of errors.. – Stefan Falk Aug 20 '21 at 08:09
2 Answers
The following is just a concept of what you could try. Yes, you could also train a neural network for this but if your setup is simple enough, some engineering will do.
We first create a "toy-video" to work with:
import numpy as np
import matplotlib.pyplot as plt
# Create a toy "video"
image = np.asarray([
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0],
[0, 0, 1, 2, 2, 1],
[0, 0, 2, 4, 4, 2],
[0, 0, 2, 4, 4, 2],
[0, 0, 1, 2, 2, 1],
])
signal = np.asarray([0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0])
x = list(range(len(signal)))
signal = np.interp(np.linspace(0, len(signal), 100), x, signal)[..., None]
frames = np.einsum('tk,xy->txyk', signal, image)[..., 0]
Plot a few frames:
fig, axes = plt.subplots(1, 12, sharex='all', sharey='all')
for i, ax in enumerate(axes):
ax.matshow(frames[i], vmin=0, vmax=1)
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
ax.set_title(i)
plt.show()
Now that you have this kind of toy video, it's pretty straight forward to convert it back to some sort of binary signal. You'd simply compute the average brightness of each frame:
reconstructed = frames.mean(1).mean(1)
reconstructed_bin = reconstructed > 0.5
plt.plot(reconstructed, label='original')
plt.plot(reconstructed_bin, label='binary')
plt.title('Reconstructed Signal')
plt.legend()
plt.show()
From here we only have to determine the length of each flash.
# This is ugly, I know. Just for understanding though:
# 1. Splits the binary signal on zero-values
# 2. Filters out the garbage (accept only lists where len(e) > 1)
# 3. Gets the length of the remaining list == the duration of each flash
tmp = np.split(reconstructed_bin, np.where(reconstructed_bin == 0)[0][1:])
flashes = list(map(len, filter(lambda e: len(e) > 1, tmp)))
We can now take a look at how long flashes take:
print(flashes)
gives us
[5, 5, 5, 10, 9, 9, 5, 5, 5]
So.. "short" flashes seem to take 5 frames, "long" around 10. With this we can classify each flash as either being "long" or "short" by defining a sensible threshold of 7 like so:
# Classify each flash-duration
flashes_classified = list(map(lambda f: 'long' if f > 7 else 'short', flashes))
And let's repeat for pauses
# Repeat for pauses
tmp = np.split(reconstructed_bin, np.where(reconstructed_bin != False)[0][1:])
pauses = list(map(len, filter(lambda e: len(e) > 1, tmp)))
pauses_classified = np.asarray(list(map(lambda f: 'w' if f > 6 else 'c', pauses)))
pauses_indices, = np.where(np.asarray(pauses_classified) == 'w')
Now we can visualize the results.
fig = plt.figure()
ax = fig.gca()
ax.bar(range(len(flashes)), flashes, label='Flash duration')
ax.set_xticks(list(range(len(flashes_classified))))
ax.set_xticklabels(flashes_classified)
[ax.axvline(idx-0.5, ls='--', c='r', label='Pause' if i == 0 else None) for i, idx in enumerate(pauses_indices)]
plt.legend()
plt.show()

- 23,898
- 50
- 191
- 378
-
interesting answer to a random looking question on stackoverflow - upvote deserved! – B.Kocis Aug 20 '21 at 09:14
-
1:D Thanks. Give it some more thought and this can probably done _a lot_ cleaner but .. it's just supposed to explain the main idea. The rest is OPs job I guess :) – Stefan Falk Aug 20 '21 at 09:18
-
1The most fun, and nicely engineered answer I've read for months! – Mark Setchell Aug 20 '21 at 10:17
-
I tried this on Trinket, but it didn't work. It returned `AttributeError: '
' object has no attribute 'interp' on line 17 in main.py`. How can I fix this, or can this not run on Trinket – Pazzel Aug 30 '21 at 18:12 -
-
I just ran it on raspberry pi and it gave: `RuntimeError: module compiled against API version 0xe but this version of numpy is 0xd ImportError: numpy.core.multiarray failed to import` – Pazzel Oct 19 '21 at 16:56
-
@Pazzel Looks like something is wrong with your environment. I don't know about your setup. You can try re-installing `numpy` (without cache) or [look here](https://stackoverflow.com/questions/33859531/runtimeerror-module-compiled-against-api-version-a-but-this-version-of-numpy-is). But it's a separate issue. – Stefan Falk Oct 20 '21 at 08:17
It somewhat depends on your environment. You might try inexpensively with a Raspberry Pi Zero (£9) or even a Pico (£4) or Arduino and an attached LDR - Light Dependent Resistor for £1 rather than a £100 USB camera.
Your program would then come down to repeatedly measuring the resistance (which depends on the light intensity) and making it into long and short pulses.
This has the benefit of being cheap and not requiring you to learn OpenCV, but Stefan's idea is far more fun and has my vote!

- 191,897
- 31
- 273
- 432
-
This also sounds good. How would you do it? I've got all the peices, but I don't know how to put them together. – Pazzel Oct 19 '21 at 16:57