Giter Club home page Giter Club 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

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.