在二次元圈子里有这样一句话
有屏幕的地方,就有Bad Apple
比如笔者最近入手了一个物联网开发板HaaS EDU K1,就萌发了在这些小的开发板上实现播放BadApple的想法。
说干就干!!!
本文将给大家展示下如何实现开发板的OLED播放BadApple,比如是基于Python轻应用。Python轻应用是阿里云IoT设备智能平台研发部推出的一个低代码的IoT开发框架,它包含了oled的驱动组件,也包含framebuf这类基础的绘图库。那接下来我就给介绍具体基于Python轻应用实现BasApple的播放。
先上效果
硬件连接
HaaS EDU K1自带一块OLED屏幕,使用HaaS EDU K1的用户无需关心硬件连接,在board.json中添加如下配置即可:
-
{
-
"version": "1.0.0",
-
"io": {
-
"oled_spi": {
-
"type": "SPI",
-
"port": 1,
-
"mode": "master",
-
"freq": 26000000
-
},
-
-
"oled_dc": {
-
"type": "GPIO",
-
"port": 28,
-
"dir": "output",
-
"pull": "pullup"
-
},
-
-
"oled_res": {
-
"type": "GPIO",
-
"port": 30,
-
"dir": "output",
-
"pull": "pullup"
-
},
-
},
-
"debugLevel": "ERROR",
-
"repl": "disable"
-
}
软件实现
要实现屏幕显示,肯定需要屏幕驱动模块,本案例中用到的是SH1106 OLED屏幕驱动。液晶屏驱动类提供一个显示器对象,此对象基于FrameBuf帧缓冲区类进行派生,可继承其绘画像素、线、矩形、文本的能力。也提供了绘制XBM图的能力。
其中draw_XBM()就是我们主要用到的方法。
在帧缓冲上的给定位置绘制XBM 位图。在使用这个方法之前,我们需要知道 XBM 是个啥。
X-BitMap (XBM)介绍
X-Bitmap(XBM)是一种非常古老但通用的图像文件格式,它与现在的许多Web浏览器都兼容。X-Windows图形界面(UNIX和Linux常用的GUI)的C代码库xlib中有一个组件专门描述了它的规范。XBM图形的实质上是使用16进制数组来表示二进制图像的C源代码文件。
我们举一个例子来说明,XBM如何表达一张图片。
显示图片
- 原图
当我们想将一张图片显示在屏幕上时,我们需要考虑到图片像素与屏幕分辨率之间的匹配。例如我们尝试将这下图显示在OLED上。它的分辨率是1088*426。很显然,它是远远大于屏幕支持的分辨率的。因此,我们需要通过一些方法将这张图片的分辨率尽可能收缩到我们希望的分辨率,同时尽可能保留其表达的信息。
- 像素化
这一步就是将图片缩放到目标尺寸,例如右图中的23*9,同时使用像素绘制出该图片。对于一些较为精致的图片,建议手动绘制。像素化完成后,即得到一张BMP (Bitmap 位图) 图片。
对于右图而言,它的分辨率仅仅只有23*9,且每个像素只有黑/白两种颜色,对应OLED上的亮和灭,因此每个像素都可以用0/1表示。
在XBM中,规定了这样一种表达方式:
其规定将BitMap中的像素进行了一个映射,其中一个字节的位为水平映射。每个字节占据8个水平像素,而7位位于屏幕的最左边。 后续字节在连续的水平位置出现,直至到达最右侧的边缘。更多字节在下一行低1个像素显示。
最终生成的文件如下所示:大家可以自己对应图片来数一数
-
#define haas_width 23
-
-
#define haas_height 9
-
-
static unsigned char haas_bits[] = {
-
-
0xff, 0xff, 0xfe, 0x 80, 0x 00, 0x 02, 0xa 4, 0xc 6, 0x 7a, 0xa 5, 0x 29, 0x 42, 0xbd, 0xef,
-
-
0x7a, 0xa 5, 0x 29, 0x 0a, 0xa 5, 0x 29, 0x 7a, 0x 80, 0x 00, 0x 02, 0xff, 0xff, 0xfe };
我们再在python交互模式中验证一下,输入如下脚本:
-
from driver import SPI
-
from driver import GPIO
-
-
import sh 1106
-
import utime
-
-
-
oled_spi = SPI()
-
oled_spi.open( "oled_spi")
-
-
-
oled_res = GPIO()
-
oled_res.open( "oled_res")
-
-
-
oled_dc = GPIO()
-
oled_dc.open( "oled_dc")
-
-
-
oled = sh 1106.SH 1106_SPI( 132, 64, oled_spi, oled_dc, oled_res)
-
-
-
oled.fill( 0)
-
-
-
HAAS_XBM_width= 23
-
HAAS_XBM_height= 9
-
-
-
HAAS_XBM=[ 0xff, 0xff, 0xfe, 0x 80, 0x 00, 0x 02, 0xa 4, 0xc 6, 0x 7a, 0xa 5, 0x 29, 0x 42, 0xbd, 0xef, 0x 7a, 0xa 5, 0x 29, 0x 0a, 0xa 5, 0x 29, 0x 7a, 0x 80, 0x 00, 0x 02, 0xff, 0xff, 0xfe]
-
-
oled.draw_XBM( 20, 20,HAAS_XBM_width,HAAS_XBM_height,HAAS_XBM)
-
-
-
oled.show()
可以看到出现了一个小小的HaaS图标,证明我们的实现成功了!接下来可以大刀阔斧地显示视频了!
显示视频
由视频生成XBM序列
刚刚说到,XBM是一种图片格式,如果要显示视频,我们需要把视频变成一帧帧图片,再把图片变成XBM数组,最后在我们的OLED上播放。
为了实现以上功能,我们还是请出无所不能的Python,不过这一次,需要在PC上运行全功能的Pythpn。
视频帧采样
-
import cv2 # open-cv库 用于处理视频和图片
-
-
-
badapple_frame = [] # 创建一个空的数组,用来接收每一帧的XBM数据
-
cap = cv2.VideoCapture('badapple.mp4') # 打开视频文件
-
-
-
i = 0
-
# 由于原视频有30帧,太高的帧率会导致数据量过大,在oled上也看不出太大区别,因此这里做了抽帧
-
step = 10 # 每隔10个视频帧抽取一张XBM图片
-
while(cap.isOpened()):
-
ret, frame = cap.read() # 读出一张图片放在frame里
-
-
-
if (ret == True ):
-
i = i+1
-
-
-
if(i%step):
-
continue
-
-
-
if(i > 1000 ): # 只取视频前1000帧图像
-
break
-
-
-
xbm_wight, xbm_height, xbm_bit = cv_2_xbm(frame, (128, 64 ), 150 ) # 将图像转换为XBM
-
badapple_frame.append(xbm_bit) # 存入badapple_frame
-
-
-
else:
-
break
-
-
-
cap.release()
图片转XBM
-
import cv 2
-
-
-
# cv2_img open-cv图片对象
-
# size 目标XBM大小 e.g.(128,64)
-
# thresh 图片二值化 阈值
-
def cv_ 2_xbm(cv 2_img, size, thresh):
-
if(len(cv 2_img.shape) > 2): # 如果是三通道(彩色)图片
-
cv2_img = cv 2.cvtColor(img, cv 2.COLOR_BGR 2GRAY) # 转换为灰度图片
-
-
-
img = cv 2.resize(cv 2_img, size, interpolation=cv 2.INTER_LINEAR) # 改变修改目标分辨率
-
ret, threshimg = cv 2.threshold(img, thresh, 255, cv 2.THRESH_BINARY) # 二值化
-
-
-
# cv2.imwrite('threshimg.bmp', threshimg) # 写入图片文件
-
-
-
xbm_height = size[ 1]
-
xbm_wight = size[ 0]// 8 + (size[ 0]% 8 != 0)
-
xbm_bit =
-
-
-
mask = 0b 10000000
-
byte = 0
-
-
-
# 开始映射像素
-
for (pos_y, row) in enumerate(threshimg):
-
for (pos_x, pixel) in enumerate(row):
-
-
-
if(mask == 0):
-
mask = 0b 10000000
-
xbm_bit.append(byte)
-
byte = 0
-
-
-
if pos_x == len(row) - 1:
-
mask = 0
-
-
-
if pixel > thresh:
-
byte |= mask
-
-
-
mask = mask >> 1
-
-
-
xbm_bit.append(byte|mask|( 1 if threshimg[- 1][- 1]>thresh else 0))
-
-
-
return size[ 0],size[ 1],xbm_bit
于是,我们得到了一个非常宏大的数组:
接下来,我们仔逐帧显示这些XBM图片
播放
-
import badapple
-
from driver import SPI
-
from driver import GPIO
-
import sh 1106
-
import utime
-
-
-
oled_spi = SPI()
-
oled_spi.open( "oled_spi")
-
-
-
oled_res = GPIO()
-
oled_res.open( "oled_res")
-
-
-
oled_dc = GPIO()
-
oled_dc.open( "oled_dc")
-
-
-
oled = sh 1106.SH 1106_SPI( 132, 64, oled_spi, oled_dc, oled_res)
-
-
-
oled.fill( 0)
-
oled.show()
-
-
-
f = 0 # 帧索引
-
-
-
while( 1):
-
if(f == (len(badapple.badapple_frame) - 1)): # 如果播放到结尾,再从头播放
-
f = 0
-
oled.fill( 0) # 清除帧缓冲区
-
oled.draw_XBM( 2, 0,
-
badapple.badapple_frame_width,
-
badapple.badapple_frame_height,
-
badapple.badapple_frame[f]) # 绘制XBM
-
-
oled.show() # 绘制到屏幕
-
f = f + 1
-
utime.sleep_ms( 30) # 睡 30ms 否则看起来很快
技术交流
Python轻应用继承了Python易学易用的特点,同时提供了基于嵌入式硬件的基础库封装,让开发者可以很方便的通过交互式的环境,实时进行嵌入式开发,让嵌入式开发也变得简单方便。如需更多技术支持,可加入钉钉开发者群,享受一对一的技术支持。
</div>