“全能的主! 看来我刚刚杀了May先生!……但是,只要有可能,我们就继续”(C)J。Clarkson
在本文中,我将告诉您如何使用普通笔记本电脑,一根电线,两个3.5毫米插孔和两个高音压电扬声器通过水通过声音传输视频(以及几乎视频)。 我还将解释其原因和工作原理,并讲述一个有趣的故事,说明我们是如何想到的。 而且,作为蛋糕上的樱桃,在文章后附有一篇有关C#的源代码,以便有兴趣的每个人都可以自己尝试,因为科学知识是可验证的,不是吗?
如果读者突然想对声纳主题进行更深入的研究,建议您熟悉我们以前的出版物,在这些出版物中,我们通过一些方式谈论我们的项目,以揭示通过水传输信息的困难:
每年从零开始的水下GPS水下GPS:继续水下航行:方向寻找-不要方向寻找,您注定要成功蓝细菌对总裁言语功能的影响通常,必须学习一个简单的道理:任何距离(至少数百米)的水视频都不能使用声音进行传输。 关键是可用频带极窄,不同频率的衰减随距离的强烈不均匀性。 优点是噪声,多径传播,混响,介质中声速因密度(即压力,温度和盐度)而变化,多普勒效应,这在无线电通信中效果不佳。
最先进的声纳调制解调器的速度限制离能够传输视频非常远。 据我所知,该记录属于EvoLogics,记录速度为62.5 kbps,最大记录距离为300米。 此外,关于无法通过水(在适当距离)传输视频声音的说法仅属于EvoLogics的创始人兼董事Konstantin Georgievich。
当我是Hydrosvyaz研究所的研究员时,当时完全不省人事,我想要伟大的成就,
北方和南方的胜利,巨大的土壤疏松 (不,我仍然想要它们,但是那时,我一点也不背负着经验和知识,一切似乎都神奇而美妙)。 在那个时代的团队(其中一部分是我的真实团队)中,我们常常幻想着一些不切实际的声纳项目,在
垃圾填埋场翻腾,并试图连续使用来自一个伟大的古代文明的各种文物,该研究所在某种程度上试图理解声纳通信之道。 。
沉浸在那些回忆中使我产生矛盾的感觉。 然后似乎什么也没有,没有人能阻止我们:我们从总监那里淘汰了一台中国铣床,进行产品原型设计,从荷兰水管Van De Lande组装了常压体,制造商甚至就此事写了一封信:“您是否不小心检查了哪个您的管道可以承受外部压力吗?” 他们用自己的钱在早餐容器中收集了面包板模型,然后秘密地去测试以对它们进行秘密测试,为同事和亲戚收集了冰钻和雪橇,甚至在欧尚购买了一艘中国PVC船。 回首过去,我感到心中充满了恐惧,怀旧和恐惧。
公平地说,值得一提的是,我们一直以来都得到了一些领导者的大力支持-言传身教,因此,我们所有的手工艺品都通过了OCD认证(意味着实验设计工作,而不是强迫症),甚至在2013年国际海军沙龙展出 是的,是的,我们开车去水管,用自己的双手涂上鲜橙色的
StDmitirev ! 它们在手提箱里:

有一天,我的朋友和同事
StDmitirev在有关光谱和频谱图的对话中说出了以下一句话:
“但是,建立这样的系统会很有趣:潜艇手坐在潜艇上,看着监视器,光谱图在该监视器上平稳移动,字母和数字像另一只潜艇的手指一样写在另一只潜艇的雾窗上。”
每个人都笑了,开发了这个主题,似乎即使在同一天,他们也对频谱图画了一个笑脸,并听了它的发音。 我真的很想把它付诸实践。
现在很难记得了(那是在2012年)。 我有一台带有网络摄像头的工作计算机,装有各种人工制品的天线和一个特殊的“桶声纳”(VG-1-P)加水。 由于我向所有老板展示了不同设备模型的工作,因此他们称他为升职,这促使我晋升为高级研究员。
我不受任何义务的约束,该方法本身早已在公共领域公开发布,其结果已在大会上反复报道。
所以,我要告诉您的是什么精神-如何通过水传输视频:
如何产生信号?
我们记得该想法是基于“绘制频谱图”的,也就是说,传输的图像就是信号的频谱图。 为了将信号从时域转换到频域,反之亦然,为了简便起见,使用(例如,很好地)傅里叶变换,或更确切地说是快速傅里叶变换,称为FFT或更常见的是FFT(快速傅里叶变换),使用起来很方便。
由于我们需要将图片(视频帧)转换成可以由任何计算机的声卡发出的音频信号,因此显然我们将使用逆变换IFFT来形成它。 我们将分列发射图片,并如下图所示形成一列信号:
=
假设FFT窗口的大小为N,并且有一个大小为N的数组。如果我们将其视为信号的频谱,则其零元素对应于零频率(常数),索引为N-1的计数对应于采样率Sample Rate。 必须选择这样的图像帧大小和FFT窗口大小,以便一方面使它在某种程度上类似于视频(传输一帧将花费一段合理的时间),另一方面,所使用的频带在原则上是足够的,并且对于可用设备是足够的。 现在,如果我们从某个喜欢的计数(从图中的底部到顶部)输入图片的一列(帧共通)的亮度值,然后执行逆FFT,那么我们将在输出端得到一个编码图像一列的信号。 现在剩下的工作是,以相同的方式为图像的其余各列形成信号,并使用声卡交替发出信号。
值得注意的是,输出处的FFT给出了一个复数值数组,因此我们的信号是最重要的部分。 当然,列中的结果信号会减少为16位带符号整数(以这种形式,通常会存储数字音频信号)并进行标准化。
实际上,在图片的开头,我还输入了几列最大亮度,稍后在接收器端,这将确定收发器路径(和传输通道)的频率响应,将其反转并稍微平滑后将有助于我们改善接收的帧。
我认为,演示发射机设备的最简单方法是用一段代码,这里是(Encoder类的Encode方法):
public double[] Encode(Bitmap source) { Bitmap frame; if (source.PixelFormat != System.Drawing.Imaging.PixelFormat.Format8bppIndexed) frame = Grayscale.CommonAlgorithms.RMY.Apply(source); else frame = source; if (!frame.Size.Equals(frameSize)) frame = resizer.Apply(frame); double[] samples = new double[fftSize * frameSize.Width]; alglib.complex[] slice = new alglib.complex[fftSize]; double maxSlice; int sampleIndex = 0; int colsCount = frameSize.Width; int startRow = startLine; int endRow = startRow + frameSize.Height; for (int x = 0; x < colsCount; x++) { for (int y = startRow; y < endRow; y++) slice[y].x = (frame.GetPixel(x, frameSize.Height - (y - startRow) - 1).R / 255.0) * short.MaxValue; for (int y = 0; y < fftSize; y++) slice[y].x *= randomizerMask[y]; alglib.fftc1dinv(ref slice); maxSlice = double.MinValue; for (int y = 0; y < slice.Length; y++) if (Math.Abs(slice[y].x) > maxSlice) maxSlice = Math.Abs(slice[y].x); for (int i = 0; i < slice.Length; i++) { samples[sampleIndex] = (short)Math.Round(slice[i].x * short.MaxValue / maxSlice); sampleIndex++; } } return samples; }
该代码自然不会伪装成任何东西,而只是为了演示而匆忙编写。
那么传输速度呢?
以及如何评估呢? 我们设法(
从邪恶而不是邪恶)维持了大约两个月的阴谋,而我们的一些高级同志和领导人在空闲时间设法写了一堆纸,想知道如此疯狂的传输速度会如何。
例如,如果采样频率为96 kHz,并且将FFT窗口大小设为512,我们将向发送器输入发送120 x 120像素(每像素8位),则发送一个图像帧所需的时间为:
120 * 512/96000 = 0.64秒比特率似乎应该是:
120x120 * 8 / 0.64 =每秒180,000位!导演的儿子当时很高兴-是的,您已经可以使用Internet协议! 这是一个突破!正如我将在下面显示的那样,很容易陷入这种误解。 怎么了 毕竟,一切都如此简单而优雅!
实际上,这种速度计算不适用于此方法,就像它不适用于模拟电视信号一样,每个像素有多少位? =)那最简单的探测器接收器呢? =))
所描述的传输方法本质上是
模拟,并且“位”和“像素”的概念不适用于它-在同一张图片中,理论上,每个像素亮度不能采用8位,而是16位,“速度”将自动加倍。
现在该展示我们“突破”的最初结果:

上图是我们于2012年冬天在Pichuga河上拍摄的。 传输距离为700米。 是的,a,我亲爱的读者,这根本不是高清的,甚至还没有采用最可耻的CamRip。 我不记得是谁了,但是有人非常准确地注意到,我们所有的“视频”都像从垂死的星球上发出求救信号。
值得注意的是,随着时间的推移,这可以描述为一种OFDM-数据在正交子载波上传输,这意味着对音调和其他窄带干扰具有良好的抵抗力-在这种情况下,图片的各个“线”会失真。 相反,脉冲噪声会使一个或一组色谱柱失真。 图片的特征“条带”是由所谓的 由于多径传播而导致频率选择性衰落,但我将在其他时间讨论。
接收器如何布置?
我将立即预订,以便在水桶甚至小水池中尝试此方法,两个小时长的零件(例如圆形零件)带有将声卡焊接到上面的连接器就足够了。 对于发射器,您可以使用相当长的电缆(2-3-4-5米)并且使用非屏蔽电缆,并用zapon清漆或一小层密封剂将压电元件密封-足够多次。 产生的声纳天线(不是吗?)插入了耳机插孔。
下图显示了撰写本文时手头上的各种作品。 所示的所有压电元件都非常适合“尝试”,并且通常在任何
垃圾场中都有一个无线电商店。 Pyatak没有压电效应,并且在图像中以比例尺显示。

对于接收器,最好采用带相同连接器的屏蔽麦克风电缆,并在末端涂上密封剂或清漆的压电二极管。 我们将此天线插入麦克风插孔。
对于在池塘上进行的实验,最好采用某种压电环作为发射器,然后将其放大后馈入其中(带有正确缠绕变压器的TDA2030上的放大器在一个好的池塘中可以持续数百米,
或者可以缠绕
另外5圈 )。 对于接收器,在这种情况下,还需要前置放大器,最好是带通滤波器。 如果读者有兴趣了解更多详细信息,请在评论中告诉我们,我们将尝试撰写有关声纳通信的功率放大器,前置放大器和天线的创建的文章。
因此,回到接收器,更确切地说,回到其软件部分
通信中最重要的事情是同步和确定有用信号的存在。 在我们的示例中,检测是通过频带中的能量执行的:确定了急剧增加的位置(帧的开始)和急剧下降的位置(帧的末端),条件是从前到后至少应存在帧的持续时间。
尽管非常简单,但效果却出奇的好。
声卡上的数据由FFTSize样本收集,对它们立即进行FFT,并将它们存储为单独的“片段”,等待它们被搜索过程处理的时刻,这是其代码(Receiver类中的Search方法):
private void Search() { int sliceIndex = 0; int frameWidth = encoder.FrameSize.Width; int minSlicesToSearch = Convert.ToInt32((frameWidth + 5) * 2); int sliceSize = encoder.FFTSize; double weight; int lastRisePosition = 0; int prevRisePosition = 0; while ((slices.Count > minSlicesToSearch) && (sliceIndex < slices.Count)) { weight = 0.0; for (int i = 0; i < sliceSize; i++) weight += Math.Abs(slices[sliceIndex][i]); double ratio = weight / previousWeight; if ((ratio >= risePeekRatio) && (sliceIndex - prevRisePosition > frameWidth)) { prevRisePosition = lastRisePosition; lastRisePosition = sliceIndex; if (lastRisePosition + (frameWidth + 5) < slices.Count) { double[][] samples = new double[frameWidth + 5][]; for (int i = 0; i < frameWidth + 5; i++) { samples[i] = new double[sliceSize]; Array.Copy(slices[lastRisePosition + i], samples[i], sliceSize); } slices.RemoveRange(0, sliceIndex); lastRisePosition = 0; if (FrameReceived != null) FrameReceived(this, new FrameReceivedEventArgs(encoder.DecodeEx(samples, 5))); lastRisePosition = sliceIndex; } } sliceIndex++; previousWeight = weight; } Interlocked.Decrement(ref isSearching); }
这是一段负责解码图片的代码(Encoder.DecodeEx):
public Bitmap Decode(double[] samples, int measureCols) { int colCount = samples.Length / fftSize; if (colCount == frameSize.Width + measureCols) { int rowCount = frameSize.Height; Bitmap temp = new Bitmap(colCount, rowCount); double[] slice = new double[fftSize]; alglib.complex[] sliceC = new alglib.complex[fftSize]; int samplesCount = 0; byte component; int decodeStart = startLine; int decodeEnd = startLine + rowCount; double maxSlice; for (int x = 0; x < colCount; x++) { for (int y = 0; y < fftSize; y++) { slice[y] = samples[samplesCount]; samplesCount++; } alglib.fftr1d(slice, out sliceC); maxSlice = double.MinValue; for (int y = decodeStart; y < decodeEnd; y++) if (alglib.math.abscomplex(sliceC[y].x) > maxSlice) maxSlice = alglib.math.abscomplex(sliceC[y].x); int offset = temp.Height + decodeStart - 1; for (int y = decodeStart; y < decodeEnd; y++) { component = (byte)(255.0 * alglib.math.abscomplex(sliceC[y].x) / maxSlice); temp.SetPixel(x, offset - y, Color.FromArgb(component, component, component)); } } return temp; } else { throw new ApplicationException("Specified array length error"); } }
现在,我建议看一下在不同水库不同时间进行的“视频”传输的实验结果。
这两张照片(下图)是2013年在圣彼得堡国际海军沙龙上(当时)通过两台笔记本电脑和一个水族馆拍摄的。
无法弄清楚徽章上写的是什么


这是我们在卡累利阿的拉多加湖的一个海湾中录制的两个“视频”,它们是这种方法的一种记录(我们再也没有尝试过,而且不太可能)–第一个是在500公里的距离上拍摄的,第二个是在1000米处拍摄的:
通过水视频传输,距离为500 m(文件8.7 mb)
由于使用网络摄像头实时录制了“视频”,因此各种奇怪的东西都掉进了框架。 如果有人猜到并在评论中写下最后一个“视频”中的背景,这将非常有趣。
支持该方法已发布很久的事实-
我们的文章已于2013年发布
我使用了很棒的
AForge库来
捕获网络摄像头图像 。
出色的
AlgLib库使用复数和FFT函数。
而且,正如我所承诺的那样,C#(VS2012)中的整个项目都作为“家庭”工作的材料附加到文章中。 为了方便起见,
项目和
二进制文件是分开的。
该演示提供了更改(移动)占用频带以及对输出帧进行伽马校正的功能(所有内容都可以实时更改)。
聚苯乙烯
我已经很长时间没有使用C#了,很难在工作时间表中找到时间,因此对于代码的混乱和匆忙,我事先表示歉意。
PPS
我没有在商品上附上一根电线,两个插孔和两根电线-不够给所有人。
勘误表和附录
-在某些输入的声卡中,有一个低通滤波器,可悲地削减了〜15 kHz以上的所有信号(为什么?)。
-默认情况下,该演示项目的采样频率为96 kHz,但并非所有现代声卡均支持该采样频率(为什么?)。 如果设备不能达到96 kHz,则需要在设置中设置48 kHz,否则,肯定在任何地方都支持44100,但是,一帧的传输持续时间将相应地更长。
以下是可以视为年轻声纳设备的笔记本电脑和声卡的列表:
- 联想Ideapad Y510P带有JBL声音
- 华硕n55s
- 华硕K501U
- 外部声卡Sound Blaster X-Fi Surround 5.1(型号SB 1095)