计算机视觉 - 解码摩尔斯电码闪烁 LED






4.94/5 (63投票s)
使用网络摄像头和图像处理来解码摩尔斯电码闪烁的 LED。
引言
希望大家都知道什么是摩尔斯电码;对于不熟悉它的人来说,摩尔斯电码是一系列标准化的点和划,用来表示任何字符和数字。好吧,在这篇文章中,我将演示一个程序,它可以控制连接到我电脑并行端口的 LED,并使其闪烁摩尔斯电码。对于点,LED 保持“亮”的时间比划短,从而给人摩尔斯电码的印象。不要忘记最有趣的部分:该程序可以使用网络摄像头和一点图像处理来让计算机理解闪烁摩尔斯电码的 LED,并将其转换回英语。
也许你正在问,“有什么用?” 呃,没有什么实际用途……但有时,编程就是一种乐趣。自从我做了网络摄像头版井字棋以来,我就一直在思考用真实而物理的东西与我的电脑交流的一些独特方法……最终写了这个程序!
在我们开始之前,我建议您阅读 Levent Saltuklaroglu 的《I/O 端口未审查 - 1 - 使用并行端口控制 LED(发光二极管)》,并确保阅读有关并行端口和十六进制/十进制/二进制的部分(如果您尚未这样做),因为我们将使用相同的方法在我们的应用程序中访问并行端口。
这个程序的一个很酷的功能是使用两台计算机,一台拥有并行端口(连接了 LED),另一台拥有 USB 网络摄像头。观看这个YouTube 视频,看看这个程序运行的效果……
生成摩尔斯电码(通过闪烁 LED)
正如您在上图中所见,该程序可以控制连接到我计算机并行端口的 LED,并使其闪烁摩尔斯电码。我所要做的就是输入一个英文短语并按下按钮。它还有一个选项可以发出模拟摩尔斯电码的声音。
首先,一段文字通过字符串扩展转换为摩尔斯电码,最后,生成的摩尔斯电码用于控制 LED 和音频部分。查看下面的代码片段
static class StringToMorse
{
//extension to string
public static string GetMorseCode(this string str)
{
string morse="";
foreach (char ch in str)
{
if (ch == 'a' || ch == 'A')
{
morse += ".- ";
}
else if (ch == 'b' || ch == 'B')
{
morse += "-... ";
}
else if (ch == 'c' || ch == 'C')
{
morse += "-.-. ";
}
// All alphabets not included
// It'd have made article unnecessarily big..
}
}
现在,一旦生成了摩尔斯电码,程序就会在一个单独的线程中异步调用一个函数,使 LED 闪烁摩尔斯电码而不挂起应用程序。我正在使用*inpout32.dll*来控制并行端口。您可以在我上面推荐的文章中找到有关导入和使用此 DLL 的完整详细信息。下面是一个使用生成的摩尔斯电码闪烁 LED 的代码片段
private void stringToLed(string str)//generated morse code is argument
{
foreach (char ch in str)
{
int mul_fac = Convert.ToInt16(comboBox1.Text);
richTextBox1.Text += ch;
int sleep = Convert.ToInt16(some vaue);//pause between dot and dash
if (ch == '.')
{
PortInterop.Output(888, 255); // set all data pins to 1
System.Threading.Thread.Sleep(on time of dot);
PortInterop.Output(888, 0);
System.Threading.Thread.Sleep(sleep);
}
else if (ch == '-')
{
PortInterop.Output(888, 255);
System.Threading.Thread.Sleep(on time for dash);
PortInterop.Output(888, 0);
System.Threading.Thread.Sleep(sleep);
}
else if (ch == '/')
{
PortInterop.Output(888, 0);// set all data pins to 0
System.Threading.Thread.Sleep(character pause);
}
else if (ch == ' ')
{
PortInterop.Output(888, 0);
System.Threading.Thread.Sleep(word pause);
}
}
}
网络摄像头和图像处理...
为了增加趣味性,我添加了另一个解码摩尔斯电码的功能。程序会监视 LED 的开/关序列,并将其转换为英语!
之前,我曾考虑过处理整个网络摄像头帧并找出 LED 的开/关状态,但这种技术使应用程序运行得太慢,以至于它甚至无法区分点和划。因此,我假设摄像机源是固定的,并且用户需要在网络摄像头窗口内通过鼠标单击来定义光源(请参阅下图:两条黄色线的交叉点是定义光源的标记)。
一旦定义了光源,程序就可以检查定义光源附近的像素,并计算每个像素的平均亮度。
using System.Drawing;
Color c = someBitmap.GetPixel(x,y);
float b = c.GetBrightness();
哇,太简单了!这段代码编写简单,易于理解。然而,不幸的是,它**非常**慢。如果您使用此代码,可能需要花费几毫秒才能处理完,因为*GetPixel()*/*SetPixel()*方法对于迭代位图来说太慢了。因此,在这个项目中,我们将利用 GDI+ 中的*BitmapData*类来访问我们想要的信息。*BitmapData*只允许我们通过指针访问它存储的数据。这意味着我们将不得不使用*unsafe*关键字来限定访问数据的代码块的范围。基于 Eric Gunnerson 的一篇文章,这是一个可以执行非常快速的不安全图像处理的类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Imaging;
public unsafe class UnsafeBitmap
{
Bitmap bitmap;
int width;
BitmapData bitmapData = null;
Byte* pBase = null;
public UnsafeBitmap(Bitmap bitmap)
{
this.bitmap = new Bitmap(bitmap);
}
public UnsafeBitmap(int width, int height)
{
this.bitmap = new Bitmap(width, height, PixelFormat.Format24bppRgb);
}
public void Dispose()
{
bitmap.Dispose();
}
public Bitmap Bitmap
{
get
{
return (bitmap);
}
}
public struct PixelData
{
public byte blue;
public byte green;
public byte red;
}
private Point PixelSize
{
get
{
GraphicsUnit unit = GraphicsUnit.Pixel;
RectangleF bounds = bitmap.GetBounds(ref unit);
return new Point((int)bounds.Width, (int)bounds.Height);
}
}
public void LockBitmap()
{
GraphicsUnit unit = GraphicsUnit.Pixel;
RectangleF boundsF = bitmap.GetBounds(ref unit);
Rectangle bounds = new Rectangle((int)boundsF.X, (int)boundsF.Y,
(int)boundsF.Width, (int)boundsF.Height);
width = (int)boundsF.Width * sizeof(PixelData);
if (width % 4 != 0)
{
width = 4 * (width / 4 + 1);
}
bitmapData = bitmap.LockBits(bounds, ImageLockMode.ReadWrite,
PixelFormat.Format24bppRgb);
pBase = (Byte*)bitmapData.Scan0.ToPointer();
}
public PixelData GetPixel(int x, int y)
{
PixelData returnValue = *PixelAt(x, y);
return returnValue;
}
public void SetPixel(int x, int y, PixelData colour)
{
PixelData* pixel = PixelAt(x, y);
*pixel = colour;
}
public void UnlockBitmap()
{
bitmap.UnlockBits(bitmapData);
bitmapData = null;
pBase = null;
}
public PixelData* PixelAt(int x, int y)
{
return (PixelData*)(pBase + y * width + x * sizeof(PixelData));
}
}
务必查看 Eric 关于不安全图像处理的文章。此类可用于检索任何像素的红色、绿色和蓝色值,如下所示
private void GetBritnessOnce(ref Bitmap image)
{
// This code is for getting brighness only once !!
// pt is point defining light source
Rectangle rect = new Rectangle(pt.X - 3, pt.Y - 3, 6, 6);
//cropping image withing boundaries of this rectangle
Bitmap img = image.Clone(rect, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
UnsafeBitmap uBitmap = new UnsafeBitmap(img);//unsafe bitmap class
uBitmap.LockBitmap();
float avgBritness = 0;
for (int x = 0; x < 6; x++)
{
for (int y = 0; y < 6; y++)
{
byte red, green, blue;
red = uBitmap.GetPixel(x, y).red;
green = uBitmap.GetPixel(x, y).green;
blue = uBitmap.GetPixel(x, y).blue;
avgBritness += (299 * red + 587 * green + 114 * blue) / 1000;
// brightness function
}
}
avgBritness /= 36 ;
uBitmap.UnlockBitmap();
label19.Text = Convert.ToString(avgBritness);
}
有了亮度值,程序就可以确定光源是“开”还是“关”,并通过秒表可以计算开/关序列的时间……
程序会在网络摄像头视图下方提供所有统计数据,并利用这些统计数据预测摩尔斯电码!请务必观看上面的视频。
关于使用软件的一些说明
嗯,最棘手的部分是调整程序内的设置,使其能够完美运行……让我们一项一项地开始设置……
在此,“dot”定义了摩尔斯电码中每个点 LED 亮起的时间跨度,而“DMF”默认为 3,这意味着摩尔斯电码中每个划的时间跨度将是“dot”* 3。
假设我们需要通过闪烁 LED 来定义“ ._ ”。我们将如何做到呢……?
LED on for "LESS time" --> LED off for "SOME time" --> LED on for "MORE time"
LED 熄灭“一段时间”就是上面设置中的“Imm”。
现在,让我们来看看解码部分的设置。我很快就会添加一些人工智能,以便程序在收集了一些开/关数据后能够进行自适应。现在,让我告诉您每个设置的意义
对于亮度低于“亮度阈值”的光源,将被视为“关闭”。为了获得最佳效果,此设置应仅略低于“开启”状态下光源的亮度。同样,您可以调整其他设置以获得最佳结果。程序将在网络摄像头窗口下方提供所有统计数据。
结论
我们已经到达了本文的结尾,希望您阅读愉快。现在,这里有一些家庭作业:尝试为程序实现类似 AI 的功能,并使其根据环境进行自适应。运用您的想法,如果您最终做了一些很酷的事情,我很想听听。:) 玩得开心!