玩转示波器——显示任意图片和特定视频

目录

第二篇博客感言

结果展示

任意图片显示

特定视频显示

原理说明

任意图片显示

特定视频显示

方式一

方式二

方式三

可能出现的问题

待探究的问题


第二篇博客感言

少说话,多做事,苦心孤诣,厚积薄发。

本篇内容主要为实验探究型,适用于想玩点花样的同学们,包括示波器成像和图像处理的内容。 


结果展示

任意图片显示

4ded90d4f1be46a38147301592779343.jpege15da22e59a249c1b9cb8dfdffd47685.jpeg

特定视频显示

似乎无法在这里直接上传视频,请移步我的另一篇博客。

玩转示波器——播放视频-CSDN直播将视频的音频左右声道作为两路输入,示波器作X-Y图,即可实现示波器播放视频https://live.csdn.net/v/395006


原理说明

示波器成像原理比较简单,就是李萨如图的原理,使用示波器的X-Y显示。李萨如图原理参考:

https://blog.csdn.net/weixin_62380586/article/details/135718807?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522171717102016800226520859%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=171717102016800226520859&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_click~default-1-135718807-null-null.142^v100^pc_search_result_base5&utm_term=%E6%9D%8E%E8%90%A8%E5%A6%82%E5%9B%BE&spm=1018.2226.3001.4187icon-default.png?t=N7T8https://blog.csdn.net/weixin_62380586/article/details/135718807?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522171717102016800226520859%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=171717102016800226520859&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_click~default-1-135718807-null-null.142%5Ev100%5Epc_search_result_base5&utm_term=%E6%9D%8E%E8%90%A8%E5%A6%82%E5%9B%BE&spm=1018.2226.3001.4187

任意图片显示

在某一时刻,XY两路输入的电压值共同决定示波器X-Y显示中亮点的位置。例如,XY输入的电压向量为[1, -1],则打开示波器的X-Y显示,将能在图像上X电压为1,Y电压为-1的点看到亮点。

所以,对于任意灰度图,我们如果将其中“比较黑”的点的坐标[X, Y]记录下来,再将X、Y转为示波器能够读取的电压值,将其输入示波器的两个通道,则原灰度图中“比较黑”的点将会反映在示波器X-Y显示的亮点上,两者将具有同样的相对位置。并且,数字示波器的X-Y显示将会读取所有在某一可调时间段内的XY输入,因此可以出现多个亮点,从而实现图片显示。

因此代码编写思路如下:

1、将图片转化为灰度图;

2、确定某一阈值,如108;

3、遍历整个灰度图,将灰度值大于阈值的点的坐标存在两个向量中;

4、将这两个向量里的值转为合适的电压值;

5、将向量导出为可输入示波器的数据类型。

最后将数据分别输入示波器的CH1和CH2,打开X-Y显示即可。

具体实现的代码如下(这段代码由共同实验成员袁哥提供)。

import cv2
import matplotlib.pyplot as plt
import csv

MAX_V = 5

""" 1.导入图片 """
img = cv2.imread('hezuo.jpeg')  # 图片名
img_gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
cv2.imshow('img_grey', img_gray)
# cv2.waitKey(2)

""" 2.测试 """
print(img_gray.shape)
# print(img_gray)
width, height = img_gray.shape
lll = width/height
print(width,height)
flag = 0
for i in range(width):
    for j in range(height):
        if img_gray[i, j] == 0:
            flag = 1
assert flag == 1,"没有找到黑色点,请检查图片"

""" 3.找到黑色像素的横纵坐标 """
# 将黑色点的横纵坐标选择出来
width_list = []
height_list = []

for i in range(width):
    for j in range(height):
        if img_gray[i, j] >= 108:
            width_list.append((-i/width*MAX_V + MAX_V)*lll)
            height_list.append(j/height*MAX_V)

# print(width_list)
# print(height_list)

assert len(width_list) == len(height_list), "The width list and height list are not equal!"

# 处理列表
T = len(width_list) // 4
zero_list = []
for i in range(T):
    zero_list.append(0)
# width_list = width_list + zero_list
# height_list = height_list + zero_list
width_list = width_list
height_list = height_list
# print(width_list)
assert len(width_list) == len(height_list), "The width list and height list are not equal!"

# # 绘制散点图
plt.scatter(height_list, width_list, s=5, color='black')
plt.show()

""" 4.输出csv文件 """
def CsvOutput(filename, input_data):
    with open(filename, 'w', newline='') as csvfile:
        writer = csv.writer(csvfile)
        for row in input_data:
            writer.writerow([row])

# list1 = [1,2,3]
# CsvOutput('test.csv', list1)

CsvOutput('hezuo-ch1.csv', width_list)
CsvOutput('hezuo-ch2.csv', height_list)

特定视频显示

显示视频就有些复杂了,考虑三种方式。

方式一

首先,如果按照图片显示的方式,我们读取视频的每一帧,将每一帧的图片通过上述方式显示在示波器上,这是否可行?

答案几乎是否定的,尤其在数字示波器中,模拟示波器或许可行。这是因为视频是动态的,也就是说你需要不断地将每一帧图片的X、Y向量输入示波器中,并且保证在这一帧图片的显示时间内,不允许下一帧的X、Y向量进来,然而在数字示波器中这是不可能的(也可能是博主没有发现)。尽管你可以实现电压信号X、Y的连续输入,但是你不能在某一帧的显示中保证下一帧信号不进入显示周期,因为你没办法使一帧信号的X、Y向量“整体移动”。

方式二

在查询大量资料后发现,播放特定音频,然后将耳机的左右声道作为示波器输入,可以实现在示波器上显示视频。原理是类似的,将某一视频的每一帧按上述方式转化为X、Y两列电压向量,然后将这两列向量制作为音频的左右声道,通过播放这段音频,将其分别输入示波器。并且这种方法已被充分证明可行,或者部分视频可行,如下所示:

用示波器播放Bad Apple

但是博主并不清楚这种方式与第一种方式的具体区别在哪里,甚至觉得是一致的。这也是实验的遗留问题,因为理论上来说这种方式是对任意视频都可以显示的,但是我在数字示波器上没有显示成功,并且失败的原因和方式一相同。由于不清楚具体原理,所以无法检错,这种方法只能到此为止了。

video2wav的代码如下,采用的是边缘检测。我并非原创者,但是不记得从哪里粘的了,如果需要注明,作者可以联系我。

%% 读取视频
path = 'C:\Users\86138\Desktop\通信系统实验\01示波器\video\oscilloscopeBadApple-main'; % 设置工作文件夹
rawVideo = VideoReader([path,'\badApple.mp4']); % 读取原视频
% 设置压缩后的文件信息
videoWriter = VideoWriter([rawVideo.Path,'\badApple_Compressed'],'Uncompressed AVI'); % 创建一个VideoWriter用于写入视频
videoWriter.FrameRate = rawVideo.FrameRate; % 设置新视频的帧率与原视频一样
open(videoWriter); % 打开写入视频通道
%设置压缩后视频的长宽
if rawVideo.Height >= rawVideo.Width
    height = 256;
    width = round(rawVideo.Width / rawVideo.Height * heigth);
else
    width = 256;
    height = round(rawVideo.Height / rawVideo.Width * width);
end
%%
for i = 1: rawVideo.NumFrames % 遍历原视频每一帧
    frame = read(rawVideo, i); % 读取原视频第i帧
    newFrame = imresize(frame, [height width]); % 将新的帧压缩
    writeVideo(videoWriter, newFrame); % 写入压缩后的帧到新视频
    i / rawVideo.NumFrames;
end
close(videoWriter); % 写完后关闭写入通道
%% 读取压缩后的视频文件并把视频分解为bmp并写入文件夹
compressedVideo = VideoReader([path, '\', videoWriter.Filename]); % 读取压缩后的视频
for i = 1: compressedVideo.NumFrames 
    frame = read(compressedVideo, i); 
    imwrite(frame,[path, '\bmp\', num2str(i), '.bmp']); % 作为为bmp文件写入\bmp\文件夹
    i / rawVideo.NumFrames;
end
%% 找出轮廓并记录
for i = 1: compressedVideo.NumFrames %遍历所有帧
    str = [path,'\bmp\',num2str(i),'.bmp']; %读取所有bmp文件
    gray = rgb2gray(imread(str)); % 将彩色转为黑白
    pic = edge(gray,'canny'); % 获取边缘
    [y,x] = find(pic == 1); % 得到边缘点的坐标
    edgeArray{i} = [x, compressedVideo.height - y]; % 记录在edgeLoc数组
    i / rawVideo.NumFrames;
end
%% 按轮廓排序
for i = 1: compressedVideo.NumFrames
   [len,wid] = size(edgeArray{i}); % 得到数组的大小
   if len > 2 % 如果小于等于两个数据点,就没必要排序了
       for j = 1: len - 1 
           temp = 100000; % 设置临时变量
           % 对于未排序的所有点,
           for k = j + 1: len
               if ((edgeArray{i}(k,2) - edgeArray{i}(j,2))^2 + (edgeArray{i}(k,1) - edgeArray{i}(j,1))^2) < temp % 如果距离小于临时变量
                   temp = (edgeArray{i}(k,2) - edgeArray{i}(j,2))^2 + (edgeArray{i}(k,1) - edgeArray{i}(j,1))^2; % 把临时变量更新为新的
                   tempLoc = k; % 记录下临时变量的所在行
               end
           end
           % 交换当前行的下一行与最小值所在行
           temp1 = edgeArray{i}(tempLoc, 1);
           temp2 = edgeArray{i}(tempLoc, 2);
           edgeArray{i}(tempLoc, 1) = edgeArray{i}(j + 1, 1);
           edgeArray{i}(tempLoc, 2) = edgeArray{i}(j + 1, 2);
           edgeArray{i}(j + 1, 1) = temp1;
           edgeArray{i}(j + 1, 2) = temp2;
           clear temp1; clear temp2;
       end
   end
   i / rawVideo.NumFrames;
end
%% 播放 可以先用Maltab 的plot函数播放看看效果 (这步可以省略)
for i = 1: compressedVideo.NumFrames 
    min = fix(i / compressedVideo.FrameRate / 60);
    sec = mod(fix(i / compressedVideo.FrameRate), 60);
    fra = fix(mod(i, compressedVideo.FrameRate));
    time = ['', num2str(min), ':', num2str(sec,'%02d'), '.', num2str(fra,'%02d'),' / 3:39']
    plot(edgeArray{i}(:, 1), edgeArray{i}(:, 2), '.');
    axis equal;
    axis([0 compressedVideo.Width 0 compressedVideo.Height]);
    set(gca, 'xtick', [0 : compressedVideo.Width / 4 : compressedVideo.Width]);
    set(gca, 'ytick', [0 : compressedVideo.Height / 4 : compressedVideo.Height]);
    pause(0.01);
end
%% 设置wav头文件
samplingRate = 88200; % 音频采样率
soundChannel = 2; % 左右声道对应示波器的 Channel 1 和 Channel 2
bitsPerSample = 8; % 每个采样的位数,8位能表示 256 个梯度就足够了
sampPerFrame = round(samplingRate / compressedVideo.FrameRate); % 每帧的采样数
frameRateWav = samplingRate / sampPerFrame; % 实际在示波器上播放的帧数
dataSize = compressedVideo.NumFrames * sampPerFrame * 2;
fileSize = dataSize + 36; % 生成的wav文件的大小-8,为数据大小加上wav的数据头 44 byte - 'R''I''F''F'(4 byte) - fileSize(4 byte) 
byteRate = samplingRate * 2; % 采样率 * 2声道
blockAlign = soundChannel * bitsPerSample / 8; % 每个采样字节数 2 * 8 / 8 = 2

%format = [ R  I  F  F/  fileSize / W  A  V  E   f   m   t   /Subchunk1Size/AudioFormat/soundChannels/samplingRate/bytePerSec/blockAlign/bitsPerSample/ d  a   t  a/  dataSize
%HEX    = [52 49 46 46  -  -  -  - 57 41 56 45  66  6D  74 20/ 10 00 00 00     01 00       -  -        -  -  -  -  -  -  -  -/   -  -        -  -      64 61  74 61  -  -  -  -];
data    = [82;73;70;70; 0; 0; 0; 0;87;65;86;69;102;109;116;32; 16; 0; 0; 0;    1; 0;       0; 0;       0; 0; 0; 0; 0; 0; 0; 0;   2; 0;       8; 0;    100;97;116;97; 0; 0; 0; 0];
data(5) = mod(fileSize, 256);
data(6) = fix(mod(fileSize, 65536) / 256);
data(7) = fix(mod(fileSize, 16777216) / 65536);
data(8) = fix(samplingRate / 16777216);
data(23) = mod(soundChannel, 256);
data(24) = fix(soundChannel / 256);
data(25) = mod(samplingRate, 256);
data(26) = fix(mod(samplingRate, 65536) / 256);
data(27) = fix(mod(samplingRate, 16777216) / 65536);
data(28) = fix(samplingRate / 16777216);
data(29) = mod(byteRate, 256);
data(30) = fix(mod(byteRate, 65536) / 256);
data(31) = fix(mod(byteRate, 16777216) / 65536);
data(32) = fix(byteRate / 16777216);
data(33) = mod(blockAlign, 256);
data(34) = fix(blockAlign / 256);
data(35) = mod(bitsPerSample, 256);
data(36) = fix(mod(bitsPerSample, 65536) / 256);
data(41) = mod(dataSize,256);
data(42) = fix(mod(dataSize,65536) / 256);
data(43) = fix(mod(dataSize,16777216) / 65536);
data(44) = fix(dataSize / 16777216);

%% 设置wav数据文件
data = [data; zeros(dataSize,1)]; % 生成dataSize大小的数组加到文件头后面
for i = 1: compressedVideo.NumFrames
    [len,wid] = size(edgeArray{i}); % 获取每帧轮廓点的个数
    if (len > 0)
        for j = 1: sampPerFrame % 均匀映射到该帧的文件位置上
            data(44+(i-1)*2*sampPerFrame+j*2-1) = edgeArray{i}(fix((j - 1) * len / sampPerFrame) + 1, 1); % 左声道
            data(44+(i-1)*2*sampPerFrame+j*2) = edgeArray{i}(fix((j - 1) * len / sampPerFrame) + 1, 2);   % 右声道
        end
    end
end
%% 写入文件
fid = fopen([path,'\badApple.wav'],'w');
fwrite(fid,data,'uint8','n');
fclose(fid);

方式三

不对视频做处理,而是使用现成的音视频资源,直接播放并将左右声道输入示波器中。这类资源可以在下面这个网站下载,但是需要付费,我已经替大家付过钱了:),后文可以直接下载,如有侵权,请联系删除。

https://oscilloscopemusic.com/watch/oscilloscope_musicicon-default.png?t=N7T8https://oscilloscopemusic.com/watch/oscilloscope_music

我将下载的资源输入示波器,调整时间窗长度,的确实现了视频的播放,如开头所示。

蓝奏云上传的资源大小有限,所以我上传到CSDN上,总共两个part,不消耗积分可以免费下载。资源链接:

https://download.csdn.net/download/weixin_68190597/89383153icon-default.png?t=N7T8https://download.csdn.net/download/weixin_68190597/89383153

https://download.csdn.net/download/weixin_68190597/89383155icon-default.png?t=N7T8https://download.csdn.net/download/weixin_68190597/89383155


可能出现的问题

1、耳机需要把外皮剥掉,一个声道耳机线内一般有三种类型的线,一根地线,数根尼龙线,数根数据线,并且数据线表面有漆覆盖,可以用使用刀片刮除或火烧,然后将数据线与示波器探头连接即可输入示波器中;

2、注意区分数字示波器和模拟示波器;

3、第一段代码把图片转成电压向量后,有两种办法输入示波器:一是输入到本实验用的Easywave信号发生器中,它可以同时产生两路特定电压值信号;二是导入U盘中,插入示波器读取。


待探究的问题

1、方式二能在模拟示波器上实现视频播放的原因;

2、为什么数字示波器没能实现方式二。


欢迎交流讨论。

  • 26
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 12
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值