1.目的与效果介绍
树莓派作为一个功能丰富的嵌入式开发平台可以结合各种硬件、传感器做出很多有意思的应用。这篇博客介绍的就是利用树莓派来实现延时摄影。首先先放实现的效果。
这个延时摄影拍摄的就是我机房窗外的景色。利用树莓派连续拍摄了24小时,每隔90秒拍一张,共拍了980张,视频帧率是11。由于拍摄当天的天气有雾,是阴天多云,只有中午有点太阳,所以太阳的光影变幻不是很明显,但也还行。
[2019-8-13更新]
利用上述代码在家里进行了为期两天(8月5日-8月7日)的延时摄影。每隔10秒拍摄一次,共拍摄了16225张照片,约32.4GB的数据量。观测了视频传到了油管,点击查看。
[更新结束]
2.硬件准备
(1)树莓派一块
这个无需多说了,树莓派主板是必须的。我用的是最新的Model 3B+版本,四核A53,主频1.4Ghz。关于树莓派的电源问题,由于3B+版本硬件配置有所提升,所以耗电也有所增加。 最好的电源是5V~3A的电源,如果实在没有的话5V~2.5A也能运行,但是树莓派会提示电压不足。我用的是小米的手机快充头(5V~3A),也测试过小米移动电源(5V~2.5A),也是可以的。
(2)摄像头一枚
既然要用树莓派拍照,摄像头是必须的。摄像头类型不限,总之只要树莓派能识别的就可以了。可以使用树莓派官方的CSI接口的摄像头,也可以使用USB摄像头。 不过建议摄像头不要太渣,不然拍出来效果不是很好。这里我用的是树莓派官方摄像头Module V2,800万像素,官网说明文档点击查看,里面介绍了一些相机的参数等信息。 如果是CSI接口摄像头的话安装的时候稍微注意一下,把卡槽的两边往上提起后再将接线塞进去,然后再将卡槽按下去就行了。动作轻一点,防止损坏接口。
3.软件准备
(1)摄像头设置
如果你用的是USB摄像头,那么你可以跳过这一部分,可以直接使用。而如果你用的是CSI接口的摄像头,需要在树莓派中简单设置一下,开启摄像头。具体做法如下。
在树莓派中打开终端,然后输入sudo raspi-config
,进入树莓派设置界面,如下。
然后在项目中找到”Interfacing Options”,然后在里面找到”Camera”条目,如下图。
选择”Enable”,重启即可开启相机。树莓派系统也提供了一些命令可供使用,如raspistill
(拍照)、raspivid
(拍视频)、raspiyuv
(拍照返回原始数据)命令,每个命令都有很多参数可用,可以查看树莓派官方文档,点击查看。
另一个可能出现的问题是相机硬件、连接都没有问题,打开相机出现select timeout
错误,那么很有可能是相机供电不足导致的。我出现这个情况是在树莓派连接了一个HDMI显示器(并对其供电)、一个键盘、一个摄像头的情况下出现的。因此只需要把用不到的硬件拔掉就好了。
(2)环境配置
在软件方面由于是自己编写代码,所以并不需要额外准备什么,用系统自带的Python环境即可。
如果你使用CSI摄像头,你可以有两种拍照方式,一种是基于树莓派自带拍照命令raspistill
命令的,一种是基于OpenCV的。
如果选用第一种方式,你不需要配置什么。如果你想用OpenCV调用CSI接口的树莓派摄像头,除了上面说的在系统中开启摄像头之外,还需要再做些设置,因为CSI接口的树莓派摄像头不是标准的驱动,所以OpenCV识别不了。
在控制台中输入sudo gedit /etc/modules-load.d/rpi-camera.conf
,在打开的文件末尾添加bcm2835-v4l2
,这个意思是系统在启动时加载bcm2835-v4l2
,重启系统之后,就可以使用OpenCV的CaptureVideo(0)
函数获取影像了。
此外,还需要给Python安装OpenCV库,建议源码安装。可以在OpenCV官网下载源码,然后一路cmake即可,切换到解压好的OpenCV文件夹后,输入如下命令(这些命令最好是以root权限运行,否则在make install的时候提示权限不够):
mkdir build
cd build
cmake ..
make -j4
make install
这样安装以后,C++和Python的OpenCV库就自动都装好了。 OpenCV虽然依赖的库比较多,但其实都比较好安装。一般情况下只要按着步骤一步步先把依赖库配好了,编译安装OpenCV基本不会有什么问题。 如果遇到问题可以参考这篇博客,写的挺详细的。 另一个可能出现的问题在之前的博客中也提到过,就是cmake的时候可能会下载一些文件,而由于是外网,所以经常下载失败。针对这种情况一定要根据出错提示进行解决,可以查看CMakeDownload的Log文件。
这里再做个小科普,关于如何卸载make install
安装的库,方法很简单,还是在生成的文件夹内输入make uninstall
即可。所以一般不建议安装完成后就把源码给删掉。
假如你已经把源码删掉了,那就只能再把源码下一遍,然后make install
看看它都拷贝了哪些文件到什么地方,手动把那些文件删除。
还需要提示的是关于make
的线程数问题,由于树莓派硬件资源实在有限,内存只有1G,所以在编译时需要关注一下内存使用情况。如果出现了编译到某个地方突然卡住了,看CPU使用率也不高,那十有八九就是内存用完了。
这时就不要用多线程编了,老老实实make
就行了。因为多线程编就意味着会同时生成多份内容,所占内存与线程数成正比。
某些库的编译特别耗内存,之前在单线程编译G2O库的时候,875MB的内存,最高占用到了845MB。这还只是单线程编译,开多线程就直接卡死了。因为在电脑上make -j4
写习惯了,所以到树莓派上还是这样写,结果一开始编译总是很慢,卡在某个地方就不动了,CPU使用率也很低。
当时一度怀疑是树莓派性能太差导致的。直到后来偶然打开了任务管理器,才发现原来是内存不足导致的。虽然可以通过增加虚拟内存的办法扩展内存(扩展内存方法可参考这里),但还是没有那么有效果。
关于如何降低编译时消耗内存较大问题,除了增加swap内存,另一个解决策略是,不编译示例代码,在CMake设置中取消”Build examples”,只编译核心代码,这样可以减少编译的工作量和消耗内存。
所以在用树莓派的时候,不能想当然的觉得它也是Linux系统,就可以一切都按照电脑的来,还是要多学,不能想当然,不然容易出错。
如果你使用USB摄像头,则可以使用OpenCV拍照方式,树莓派自带拍照命令好像只对CSI接口相机有用。 配置OpenCV读取摄像头和安装库和上面介绍的一样。
至此,需要用到的开发环境就准备完成了,下面就可以进入代码编写环节了。
4.代码编写
(1)基于OpenCV的拍照
# coding=utf-8
import cv2
import sys
import time
import datetime
import os
if __name__ == '__main__':
dir = "observation/"
if not os.path.exists(dir):
os.mkdir(dir)
print "save path:", dir
start_time = int(time.time())
if sys.argv.__len__() == 2:
interval = int(sys.argv[1])
else:
interval = 10
print "start time:", datetime.datetime.strftime(datetime.datetime.now(), '%Y-%m-%d %H:%M:%S')
shot_time = start_time + interval
while True:
now_time = int(time.time())
if now_time == shot_time:
# windows下特定参数CAP_DSHOW,否则windows会报警告(但也可以运行),但在Linux下不需要此参数,否则没有数据
cap = cv2.VideoCapture(0 + cv2.CAP_DSHOW)
ret, frame = cap.read()
save_str = datetime.datetime.strftime(datetime.datetime.now(), '%Y-%m-%d-%H-%M-%S')
print "shot time:", save_str
cv2.imwrite(dir + save_str + ".jpg", frame)
shot_time = now_time + interval
cap.release()
采用OpenCV实现的思路很简单,通过计时实现按时拍照功能,到时间就打开相机然后获取数据,完成后再释放。虽然在树莓派上配置OpenCV相对麻烦一些,但基于OpenCV的代码适用性比较广泛,不仅可以用于树莓派,只要是OpenCV能获取到摄像头数据,都可以使用。 脚本只有一个启动参数,就是拍摄间隔,若不指定,默认间隔为10s。
这里再简单提一下,os
模块中有mkdir
和mkdirs
两个函数。作用稍微有些区别。mkdir
函数顾名思义就是根据给定的路径创建文件夹,但问题是如果当前要创建的文件夹的父目录都不存在的话,它会直接报错,但mkdirs
就不会。因此简单来说mkdir
只能在已有的目录下创建文件夹,而mkdirs
可以在不存在的目录中递归地创建文件夹。
(2)基于Raspistill命令的拍照
# coding=utf-8
import sys
import time
import datetime
import os
if __name__ == '__main__':
dir = "observation/"
if not os.path.exists(dir):
os.mkdir(dir)
print "save path:", dir
# 旋转角度
rot = "180"
# 饱和度,-100 - 100
sa = "30"
# 宽度
width = "1024"
# 高度
height = "768"
# 更多参数参见摄像头文档http://shumeipai.nxez.com/2014/09/21/raspicam-documentation.html
start_time = int(time.time())
if sys.argv.__len__() == 2:
interval = int(sys.argv[1])
else:
interval = 10
print "start time:", datetime.datetime.strftime(datetime.datetime.now(), '%Y-%m-%d %H:%M:%S')
shot_time = start_time + interval
while True:
now_time = int(time.time())
if now_time == shot_time:
save_str = datetime.datetime.strftime(datetime.datetime.now(), '%Y-%m-%d-%H-%M-%S')
print "shot time:", save_str
os.system("raspistill -o " + dir + save_str + ".jpg " +
"-rot " + rot +
" -sa " + sa +
" -w " + width +
" -h " + height)
shot_time = now_time + interval
相比于OpenCV的代码,这个代码的核心就是构造命令然后定时执行。当然raspistill
命令有很多参数可选,想了解更多可以看上面提到的官方文档,很全面。
相比于OpenCV,这种方式可以拍摄到更加高清的影像,还可以指定饱和度等等,毕竟是系统自带的命令,应该是对摄像头专门做过适配的,用起来会比通用的OpenCV方式效果好一些。
脚本只有一个启动参数,就是拍摄间隔,若不指定,默认间隔为10s。其它的参数可以直接在脚本中修改。
(3)基于照片生成延时视频
# coding=utf-8
import cv2
import os.path
# 用户输入存放影像的文件夹目录,如E:\L0
rootdir = raw_input("Input the parent path of images:\n") + "\\"
# 输出视频路径
output = raw_input("Input output video path:\n")
# 考虑到适用性,增加了影像文件类型的选择,如tif、jpg、png等
type = raw_input("Input file type of images:\n")
# 考虑原始影像可能非常大,如4096*3072,直接拼接成视频可能不方便观看
# 因此设置了缩放因子,这样可以指定输出视频的大小
scale = input("Input scale of video(0-1):\n")
# 用于控制输出视频帧率
fps = input("Input fps of video:\n")
print "OK...Processing...\n"
# list,用于存放遍历得到的用户指定类型的影像文件
paths = []
names = []
# 遍历
for parent, dirname, filenames in os.walk(rootdir):
for filename in filenames:
# 判断,如果是用户指定的类型则添加到list,否则什么也不做
# 这里用endswith更好一些,因为如果用contains,有可能有些文件是".tif.xml"
# 这样即使不是tif,但是还是会被加进来,但用endswith就不会了
if filename.endswith(type):
paths.append(parent + '\\' + filename)
names.append(filename)
if paths.__len__() is not 0:
for path in paths:
print path
print paths.__len__(), "frames were found."
# 读取第一张影像,获取其大小,然后计算输出视频的大小
tem = cv2.imread(paths[0])
width = int(scale * tem.shape[1])
height = int(scale * tem.shape[0])
# 指定输出视频的编码器以及相关参数
fourcc = cv2.VideoWriter_fourcc(*'XVID')
out = cv2.VideoWriter(output, fourcc, fps, (width, height))
# 用于统计进度的循环变量
count = 0
# 循环处理每一帧,组成视频
for i in range(paths.__len__()):
frame = cv2.imread(paths[i])
str_time = names[i].split(".")[0].split('-')
year = str_time[0]
month = str_time[1]
day = str_time[2]
hour = str_time[3]
minute = str_time[4]
second = str_time[5]
frame = cv2.resize(frame, (width, height), interpolation=cv2.INTER_AREA)
frame = cv2.putText(frame, year + "-" + month + "-" + day + " " + hour + ":" + minute + ":" + second,
(10, height - 25), cv2.FONT_HERSHEY_SIMPLEX, 0.5,
(255, 255, 255), 1, cv2.LINE_AA)
out.write(frame)
count += 1
print paths[i] + " " + ((count * 1.0 / paths.__len__()) * 100).__str__() + " %"
# 释放VideoWriter对象
out.release()
print "--------------------------------"
print "Output Video Information:"
print "Output path:" + output
print "Width:" + width.__str__()
print "Height:" + height.__str__()
print "FPS:" + fps.__str__()
print "Frames:" + paths.__len__().__str__()
print "Time:" + (paths.__len__() * 1.0 / fps * 1.0).__str__() + " s"
print "--------------------------------"
这部分代码来自于这篇博客,只是对每一帧进行了额外的处理,添加了拍摄时间,如果不需要时间的话,那就完全一样了。 可以控制输出视频大小、FPS等参数,按提示输入即可。
(4)打开摄像头预览
为了方便调试,写了个工具类的脚本,用于查看摄像头预览画面,以便合适地摆放位置,选择好角度。
# coding=utf-8
import cv2
import sys
if __name__ == '__main__':
cap = cv2.VideoCapture(0)
fps = 10
waitTime = 20
width = int(cap.get(3))
height = int(cap.get(4))
print width, height
if sys.argv.__len__() == 1:
while cap.isOpened():
ret, frame = cap.read()
if frame is None:
break
else:
cv2.imshow("frames", frame)
k = cv2.waitKey(waitTime) & 0xFF
if k == 27:
break
cap.release()
elif sys.argv.__len__() == 2:
angle = int(sys.argv[1])
M = cv2.getRotationMatrix2D((width / 2, height / 2), angle, 1)
while cap.isOpened():
ret, frame = cap.read()
if frame is None:
break
else:
rotate = cv2.warpAffine(frame, M, (width, height))
cv2.imshow("frames", rotate)
k = cv2.waitKey(waitTime) & 0xFF
if k == 27:
break
cap.release()
脚本只有一个启动参数,就是旋转角度,若不指定则不旋转,若指定如180,则表示选择180度。
5.总结
总的来说使用树莓派进行延时摄影难度并不是很大,可能前期的难点在于树莓派上OpenCV环境的搭建。因为树莓派是ARM架构,直接用pip是找不到合适的安装包的,只能自己编译。 如果有兴趣,可以研究OpenCV的交叉编译,但我看了几天还是有些问题没有解决,所以并没有使用。最终还是老老实实选择了在树莓派上直接编译,虽说会慢一些,但也还行,编译完大约1个小时左右,还可以接受。
最后,把所有代码都放到了Github上,感兴趣可以查看,欢迎下载测试,拍摄属于自己的延时摄影。
本文作者原创,未经许可不得转载,谢谢配合