[设计报告]有屏的地方就有Bad Apple!! —— 12864版

先放出本人在B站投稿的视频:http://www.bilibili.com/video/av3842032/

《Bad Apple!!》原是东方旧五作中东方幻想乡里的一个BGM,但现在更多的是指一个在二次元界妇孺皆知的黑白影绘视频同人作品。只有黑白两色的特性,使其能够在各种屏幕上播放而不失味道,由此引出各路大神各种二次创作,“有屏的地方就有Bad Apple”这句话也是因此得来。
我倒不是什么大神哈,寒假回家带回了一堆材料,Bad Apple 12864版 也是想完成的作品之一,下面就按照整个作品完成的过程,仔细的介绍一下吧~

目录:


获取视频

下载B站视频很简单,只要在视频链接的bilibili之前加i,即可进入下载界面:

【東方】Bad Apple!! PV【影絵】
原视频:http://www.bilibili.com/video/av706/
下载链接:http://www.ibilibili.com/video/av706/

截取图像

要完成这个作品,其实可以有很多种方式,比如可以编写上位机程序,播放视频同时实时截取视频图像,再传输给单片机。例如这里有个少年,水平不知道比我高到哪里去… 我跟他谈笑风生。 或者可以像我这样,使用截取每一帧图像再传送显示的方法实现。这样上位机的程序会简单很多啦(主要是水平不够=。=嘘…)
考虑到串口传输的速度以及液晶屏的刷新速度,选择了10FPS,也就是每秒10帧。那么是不是要手动每隔0.1秒截一张图呢?必然不需要,貌似也做不到。这里可以用到kmplayer或者potplayer,身边的朋友应该都被我安利过这两款播放器了,个人感觉功能齐全、界面简洁、十分好用。实际上后者的作者也是前者作者之一,所以这两个软件基本相同。

用上述软件打开视频后点击ctrl+G 就会出现连续截取画面的对话框:

连续截取画面

选定保存位置、命名方式、文件格式、尺寸、截图方式之后,点击开始,然后再开始播放视频,播放器就会自动开始截图了。
按照每秒10帧,视频总长3:39,最终得到2188张512×384的原始图片。

处理图像

大小调整

12864之所以被称为12864,就是说它的分辨率是128×64,因此还需要把原始图片处理成这个分辨率的图片。另外可以看到原视频是4:3的比例,而12864是2:1的比例,因此又有了比例不同的问题。三个解决方法,一个是拉伸;一个是就着宽度,截去上下两节;最后一种就是就着高度,两边留白。
作为重度强迫症患者,前两种自然不会采取,故使用PS将原图片处理成如下效果:

处理效果

主要过程:转换成灰度模式→转换成位图模式→用128*64尺寸裁剪
当然两千多张图片肯定要批处理的。在PS里录制上述过程成为动作,然后就可以批量处理啦~具体过程这里就不缩了。

转换成数据文件

最终给单片机的数据是一连串的1和0,所以要得到代表图片内容的二进制的文件。这一步通常由图片取模软件实现,但是没找到能够批量处理的软件,干脆拿matlab写一个了。
之前已经得到了原视频位图模式的每一帧,而所谓位图,就是只有黑白两种颜色并且没有灰度区分的图片,也就是已经是1和0构成的图片了。在用matlab读取这样的图片时,直接就是一个1和0的矩阵了。然后将这个矩阵的数据按每八位一个字节看做一个数据(串口一次传一字节),存入二进制文件*.bin中。
废话不多说上代码:

clc;
clear;
P = 'D:\pic\';
D = dir([P '*.bmp']);
for i = 1 : length(D)
    raw_data = imread([P D(i).name]);
    raw_data = double(raw_data');       
    for j = 1:1024                      
        byte = 0;
            for k = 1:8                 
                byte = byte + bitshift(raw_data(8*(j-1)+k),8-k);
            end
        B(j) = byte;
    end
    newName=sprintf('%d.bin', i);   
    fid = fopen(newName,'wb'); 
    fwrite(fid,B,'uint8');
    fclose(fid);
end 

下面解释一下:

  • 首先指定一个路径,然后读取该路径下所有.bmp的文件,之后以文件数作为大循环;
  • 读取每一个文件,因为本身是位图,读进来就是逻辑型logical只有1和0的矩阵;
  • 转置这个矩阵,因为读进来时数据矩阵是128×64,matlab中把矩阵看做数组的话,元素顺序是一列一列排列的,而需要的数据格式是由左到右、由上到下排列的,也就是顺序是一行一行的,所以这里必须转置;
  • 点的数量是128×64=8192=8×1024也就是=1024Byte=1KB,串口一次传一字节,所以要将每八个数看做一字节数据,共1024字节;
  • 每字节的处理,用移位实现,注意之前一定将数据类型强制转成double型,否则不能移位,构造大小为1024的数组,其每个元素就是每个字节的数据;
  • 创建二进制文件,将数组存进去,进入下一个文件的循环。

已经写的很偷懒了,不过,把数组每八个元素合成一个字节,应该还有更精简的代码吧。总之,这样就得到了单片机认识的二进制文件的图片~

串口传输

相信玩过单片机的各位都用过串口调试助手这种神奇的东西,有些就具有将二进制文件的数据传给单片机的功能。然而我还是没找到能批量打开文件的串口调试助手。那就自己写呗,反正也不难。基本会点简单的桌面编程语言都能完成这种事儿,然而我只会点单片机上用的C,其他都很水。只好又拿强大的matlab完成这点小儿科的工作… 还是先上代码:

clc;
clear;

% Find a serial port object.
scom1 = instrfind('Type', 'serial', 'Port', 'COM1', 'Tag', '');

% Create the serial port object if it does not exist
% otherwise use the object that was found.
if isempty(scom1)
    scom1 = serial('COM1');
else
    fclose(scom1);
    scom1 = scom1(1);
end

scom1.baudrate=115200;          % 波特率
scom1.OutputBufferSize = 2048;  % 输出缓冲区
fopen(scom1);

for i = 1:2188
    filename =['D:\pic_data\',int2str(i),'.bin'];
    fid = fopen(filename,'r'); 
    C = fread(fid,[1,1024],'uint8');
    fwrite(scom1,C);
    response = fscanf(scom1,'%s');
    a = strcmp(response,'0');
    while(~a)
    end
    fclose(fid);
    response = '1';
end 

fclose(scom1);      %关闭串口                                                               
delete(scom1);      %删除串口对象
clear scom1;        %清除变量

简单解释一下:

  • 首先创建串口对象,之前自己写直接上来就创建,结果总出现连接失败的问题,于是参考串口所在工具箱里的一段示例代码,瞬间就好用了;
  • 之后设置波特率,必须最快的115200BPS,也就相当于14.4KB/s,理论上每秒能传14帧图(每帧1KB),发送缓冲区大小也必须修改到大于1KB;
  • 事先知道图片共2188张,这里也偷懒直接循环这么多次,然后打开文件,读文件,写到串口对象,之前写的循环1024次,每次写一个元素,发现特慢,后来一试才知道直接操作数组就行;
  • 然后等待单片机回应,单片机回应字符0 表示数据已收到并且显示完成,这时才能开始写下一帧的数据。

这段肯定也有更简单的写法啦,不过能完成功能就好嘛。

12864的驱动

上面费了这么大劲,没有单片机都白搭。所谓驱动12864,就是说如何给单片机编程使其能够与屏幕通信,控制屏幕将图像或文字显示出来。

这里要说说为何最后用了arduino这个开源硬件平台。
一开始是想拿stm32做的,也算是学习stm32的一个过程。于是找来之前在MSP430上好用的库,结合网上找的别人写的库文件,串行并行都调试一下。结果,弄了好几天吧,显示的就是有错位。以为是线没接好,结果不是,两个stm32 的板子,都有问题,网上说3.3V逻辑的stm32与5V的12864连接就是会有问题,但我照着人家说的把数据线改成推挽输出也是不行。430也是3.3V的咋就没这问题呢。
最后拉倒,改用430算了,这个熟。结果G2553内存只有0.5KB,这不逗呢,一帧就1K,怎么也不够啊。F5529呢倒是够,就是那两天连接又出问题,电脑就是不认,也算倒霉…
干脆,最后用上arduino nano,之前买来就是想体验一把玩玩,后来发现这个普及的还真是广,各种网友写的库简直应有尽有。拿来接好线一试,立马就亮了,一扫之前的郁闷干脆就用这个了。

这里用的是并行通信方式,因为并行在这种情况下毕竟还是快一些的嘛。具体用到的库文件和程序会在下面一并放出。主要代码放一下:

#include "LCD12864R.h"

unsigned char img_buffer[1024]={0};

void setup()
{
    LCDA.Initialise(); 
    delay(100);
    Serial.begin(115200);
}

void loop()
{
      while (Serial.available() > 0)
      {
          Serial.readBytes(img_buffer,1024);
          LCDA.DrawFullScreen(img_buffer);
          delay(1);
          Serial.print('0');
      }
}

主程序就这么短,因为这里没考虑时间上的同步问题,一句话描述就是:
收到一帧数据后显示出来,回应一下,再等待下一帧数据。

在初始化时,要设置为图片模式,只需要写三条命令:

    WriteCommand(0x36);        //扩展绘图显示
    WriteCommand(0x3e);        //关显示
    WriteCommand(0x01);        //清屏

这样写数据的同时便可显示出来,而不是写完一屏数据再显示,那样会很慢,而且会闪烁。
显示图片的函数:

void LCD12864R::DrawFullScreen(uchar *p)
{
    char x=0,y=0,i=0,j=0;
    for(i=0;i<2;i++)
    {      
        for(y=0;y<32;y++)           
        {                                
           WriteCommand(0x80+y);           
           WriteCommand(0x80+x);            
           for(j=0;j<16;j++)
               WriteData(*(p++));
         }
         x+=8;
    }     
}

大体就是,分为上下两部分,一行一行的写数据到专门存图像的GDRAM。
其实最好应该是每次只修改画面中变化的地方,但这样至少需要2K的RAM,存两帧的图才能比较吧,而且感觉这样每一帧用的时间不统一,同步就更困难了,然后我就没尝试。
程序部分大体就是这样,因为本人很懒,所以程序很多地方都很简单,也不完善,只是能够完成这个视频的程度,请见谅=。=

后期处理

接好单片机,打开matlab,运行串口传输程序,屏幕上就开始一帧帧的显示动画了。预期视频是10fps,然而实际只能做到3fps,所以想到了后期加速大法。。。
按理来说,假设每帧时间间隔均等,那么只要均衡的加速就可以了。然而事实并非如此。由于未知的原因,直接加速后发现,与原视频不同步严重,有的地方快有的地方慢。以为是录制时操作电脑的问题,重录一次发现也是这样,甚是苦恼。
最后就想到干脆分割视频,保证关键帧同步,调整每一段视频的时间流逝。最终3分39秒的视频大概分了20来段,人肉手工调整后,整体的效果就十分完美了。
这里视频处理软件可以是AE、Vegas、会声会影等等均可,之前用的AE发现用的精简版输出格式不全,其实本身对这软件也不熟,最后用的会声会影(这软件还真是傻瓜式操作…)

写在最后

原本录制的视频其实有10多分钟,用后期处理才达到效果。实际上3fps的速率并不是最快的,听说有人用12864可以做到7fps,如果在程序上再优化(比如忽略两边空白的数据)应该还可以提高帧数。但是,实际观察发现,3fps下的残影就已经不忍直视了,再提高速度,还没等完全显示好就又刷新了,视觉暂留 留下的全是淡淡的影儿,所以没想再提高帧数。也就是说这个屏本身的限制,效果也就这样儿了,这屏幕本身就不是放视频的,玩玩而已嘛。

Bad Apple!! ——12864版,完工于2016年2月14日,这个对于单身喵来说平常的一天,特此留念。

资料分享:http://pan.baidu.com/s/1eRr17xS


  • 25
    点赞
  • 84
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值