GithubHelp home page GithubHelp logo

badappleosc's Introduction

在示波器上播放 Bad Apple!!

输入一个视频,输出其二值化后边缘点的坐标组成的波形文件

左声道:水平坐标

右声道:垂直坐标

无法加载图片的话,在hosts文件中添加199.232.68.133 raw.githubusercontent.com,或点这里

badapple_hot

1920×1080

2560×1440

3840×2160

1. MATLAB 脚本的详细过程

脚本中预设的每帧扫描次数scanNumPF为 2 次(示波器的光点在屏幕上画 2 次,且原视频帧率为 20 帧,这样输出的波形基频为 2×20 = 40 Hz,避免音频设备在听域范围外的衰减),输出音频采样率Fs为 48 kHz(采样位数为默认的 16 位,这是完全足够的,而图像越复杂时采样率越高越好)。

scanNumPF = 2; % 每帧扫描次数
Fs = 48e3; % 采样率

选择输入的视频文件和输出的波形文件,得到文件名和路径名。

[vidFile, vidPath] = uigetfile('*.avi;*.mp4', '选择视频文件', '22118703_5_0.mp4');
[wavFile, wavPath] = uiputfile({'*.wav';'*.flac'}, '保存音频文件', 'PlayMe');

1.1 读取视频文件

首先创建VideoReader对象Vid,用于读取原视频数据。

Vid = VideoReader([vidPath vidFile]);

读取原视频的部分信息

vidFrameRate = Vid.FrameRate; % 帧率
nFrames = Vid.NumFrames; % 总帧数
vidHeight = Vid.Height; % 高度
vidWidth = Vid.Width; % 宽度

算出每帧图像的采样点数dotNumPF和示波器的光点在屏幕上每画 1 次的采样点数dotNum

dotNumPF = Fs/vidFrameRate; % 每帧点数
dotNum = dotNumPF/scanNumPF; % 每次扫描点数

1.2 读取帧并处理

接着一帧一帧读取图像

while hasFrame(Vid)
    vidFrame = readFrame(Vid); % 读取每帧图像

以原视频 56 秒处为例

badapple_1

读取的图像数据类型为uint8的 RGB 图像,即由范围在 [0, 255] 的整数值组成的 360×480×3 的三维矩阵。将其值转为范围在 [0, 1] 的双精度值以用于计算。

vidFrame = im2double(vidFrame);

badapple_2_2

转换为灰度图,即 360 行,480 列的二维矩阵

vidFrame = rgb2gray(vidFrame);

badapple_2_3

高斯滤波,其中标准差与视频宽度成正比(适应图像尺寸),与每次扫描点数成反比(根据采样点数简化图形,采样点越多,需要简化的越少),二值化

vidFrame = imgaussfilt(vidFrame, vidWidth/dotNum) >= 0.5; % 滤波

badapple_3

Canny 算子边缘检测,得到边缘的线条

vidFrame = edge(double(vidFrame), 'Canny'); % 边缘检测

badapple_5

再跟踪边缘的线条的边界,得到边界坐标,存放在元胞数组Bou中。元胞的个数等于线条的数量,一个元胞中的坐标连起来近似于沿着一条边缘的线条上走一个来回。

Bou = bwboundaries(vidFrame); % 获取边界坐标

badapple_6

直接将获取的坐标首尾相接并不合适,这些线条的顺序不合理,画完一条线画下一条时,跨越的距离可能很长,显示在示波器上的杂乱线条更加明显

badapple_7

也会使波形中的跳变幅度增大,产生更多的高频成分。

badapple_9

所以可以优化一下线条的顺序

st=>start: 开始
op1=>operation: 获取线条数量
op2=>operation: 初始化输出
cond1=>condition: j<=线条数
op3=>operation: 获取剩余未排序线条数
op4=>operation: 初始化距离列表dist
cond2=>condition: i<=剩余线条数
op5=>operation: 获取第i条线的第一个点的坐标p1
op6=>operation: 计算p0到p1的距离,放入dist(i)
op7=>operation: 找到距离列表dist中最小值的位置indx
op8=>operation: 将对应位置的线条Bou{indx}放入BouTemp{j}
op9=>operation: 获取这条线的最后一个点的坐标p0
op10=>operation: 将这条线从原线条中删除
op11=>operation: 将排好序的线条连成一串
e=>end: 结束

st->op1->op2->cond1
cond1(yes)->op3
cond1(no)->op11
op3->op4->cond2
cond2(yes)->op5
cond2(no)->op7
op5->op6->cond2
op7->op8->op9->op10->cond1
op11->e
bouNum = length(Bou);
BouTemp = cell(bouNum, 1);
for j = 1:bouNum
    bouNumLeft = length(Bou);
    dist = zeros(bouNumLeft, 1);
    for i = 1:bouNumLeft
        p1 = Bou{i}(1,:);
        dist(i) = norm(p0-p1);
    end
    [~, indx] = min(dist);
    BouTemp{j} = Bou{indx};
    p0 = Bou{indx}(end,:);
    Bou(indx) = [];
end
bouDot = cell2mat(BouTemp); % 边界上的每一点

一般来说,排序后多余的连线会更短

badapple_8

并减少多余的跳变

badapple_10

将排好序的线条坐标连成一串后,统计坐标点的数量bouDotNum。如果大于 0 ,将坐标点数重采样到dotNum个,然后重复scanNumPF次。如果等于 0 ,说明无画面内容,全部填充 NaN 。

bouDotNum = length(bouDot); % 每一帧点的数量
if bouDotNum > 0
    bouDot = resample(bouDot, dotNum, bouDotNum, 0); % 调整点数
    bouDotTemp = repmat(bouDot, scanNumPF, 1); % 每帧重复扫描scanNumPF次
else
    bouDotTemp = NaN(dotNumPF, 2); % 无画面
end

为了在跳变处不产生中间值,重采样的方法为最邻近法

badapple_11

将这一帧所有坐标点放入bouDotxy{k}

bouDotxy{k} = bouDotTemp; % 所有要描的点的坐标

1.3 调整幅度

处理完所有的视频帧后,将所有帧的坐标连成一串

bouDotxy = cell2mat(bouDotxy);

移除直流

bouDotxy = bouDotxy - mean(bouDotxy, 'omitnan'); % 移除直流

归一化,将数值调整到 [-1, 1] 的范围

bouDotxy = bouDotxy / max(abs(bouDotxy),[],'all'); % 归一化

调整画面方向

% 顺时针旋转90°
bouDotxy(:,1) = -bouDotxy(:,1); % 水平翻转
bouDotxy(:,[1 2]) = bouDotxy(:,[2 1]); % 交换xy

将无数值的点替换为 0

% 无画面的点
bouDotxy(isnan(bouDotxy)) = 0;

1.4 输出音频文件

audiowrite([wavPath wavFile], bouDotxy, Fs)

badapple_13

不要使用有损压缩

badapple_17

badapple_18

2. 硬件连接

将示波器视图设置为 X-Y 模式,连接如下

badapple_14

如果正确,以下音频将显示校准圆

d = 60;
fs = 48e3;
ts = 1/fs;
t = 0:ts:d-ts;
x = cospi(500*2*t);
y = sinpi(500*2*t);
test = [x' y'];
audiowrite('校准圆.wav',test,fs)

badapple_15

badapple_16

badappleosc's People

Contributors

chdilo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.