目录
第二篇博客感言
少说话,多做事,苦心孤诣,厚积薄发。
本篇内容主要为实验探究型,适用于想玩点花样的同学们,包括示波器成像和图像处理的内容。
结果展示
任意图片显示
特定视频显示
似乎无法在这里直接上传视频,请移步我的另一篇博客。
玩转示波器——播放视频-CSDN直播将视频的音频左右声道作为两路输入,示波器作X-Y图,即可实现示波器播放视频https://live.csdn.net/v/395006
原理说明
示波器成像原理比较简单,就是李萨如图的原理,使用示波器的X-Y显示。李萨如图原理参考:
任意图片显示
在某一时刻,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);
方式三
不对视频做处理,而是使用现成的音视频资源,直接播放并将左右声道输入示波器中。这类资源可以在下面这个网站下载,但是需要付费,我已经替大家付过钱了:),后文可以直接下载,如有侵权,请联系删除。
我将下载的资源输入示波器,调整时间窗长度,的确实现了视频的播放,如开头所示。
蓝奏云上传的资源大小有限,所以我上传到CSDN上,总共两个part,不消耗积分可以免费下载。资源链接:
可能出现的问题
1、耳机需要把外皮剥掉,一个声道耳机线内一般有三种类型的线,一根地线,数根尼龙线,数根数据线,并且数据线表面有漆覆盖,可以用使用刀片刮除或火烧,然后将数据线与示波器探头连接即可输入示波器中;
2、注意区分数字示波器和模拟示波器;
3、第一段代码把图片转成电压向量后,有两种办法输入示波器:一是输入到本实验用的Easywave信号发生器中,它可以同时产生两路特定电压值信号;二是导入U盘中,插入示波器读取。
待探究的问题
1、方式二能在模拟示波器上实现视频播放的原因;
2、为什么数字示波器没能实现方式二。
欢迎交流讨论。