1.目的与效果介绍

树莓派作为一个功能丰富的嵌入式开发平台可以结合各种硬件、传感器做出很多有意思的应用。这篇博客介绍的就是利用树莓派来实现延时摄影。首先先放实现的效果。

您的浏览器不支持 HTML5 video 标签。

这个延时摄影拍摄的就是我机房窗外的景色。利用树莓派连续拍摄了24小时,每隔90秒拍一张,共拍了980张,视频帧率是11。由于拍摄当天的天气有雾,是阴天多云,只有中午有点太阳,所以太阳的光影变幻不是很明显,但也还行。

[2019-8-13更新]

利用上述代码在家里进行了为期两天(8月5日-8月7日)的延时摄影。每隔10秒拍摄一次,共拍摄了16225张照片,约32.4GB的数据量。观测了视频传到了油管,点击查看

[更新结束]

2.硬件准备

(1)树莓派一块

这个无需多说了,树莓派主板是必须的。我用的是最新的Model 3B+版本,四核A53,主频1.4Ghz。关于树莓派的电源问题,由于3B+版本硬件配置有所提升,所以耗电也有所增加。 最好的电源是5V2.5A也能运行,但是树莓派会提示电压不足。我用的是小米的手机快充头(5V2.5A),也是可以的。 利用树莓派进行延时摄影 - 图1

(2)摄像头一枚

既然要用树莓派拍照,摄像头是必须的。摄像头类型不限,总之只要树莓派能识别的就可以了。可以使用树莓派官方的CSI接口的摄像头,也可以使用USB摄像头。 不过建议摄像头不要太渣,不然拍出来效果不是很好。这里我用的是树莓派官方摄像头Module V2,800万像素,官网说明文档点击查看,里面介绍了一些相机的参数等信息。 利用树莓派进行延时摄影 - 图2 如果是CSI接口摄像头的话安装的时候稍微注意一下,把卡槽的两边往上提起后再将接线塞进去,然后再将卡槽按下去就行了。动作轻一点,防止损坏接口。

3.软件准备

(1)摄像头设置

如果你用的是USB摄像头,那么你可以跳过这一部分,可以直接使用。而如果你用的是CSI接口的摄像头,需要在树莓派中简单设置一下,开启摄像头。具体做法如下。 在树莓派中打开终端,然后输入sudo raspi-config,进入树莓派设置界面,如下。 利用树莓派进行延时摄影 - 图3 然后在项目中找到”Interfacing Options”,然后在里面找到”Camera”条目,如下图。 利用树莓派进行延时摄影 - 图4 选择”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的时候提示权限不够):

  1. mkdir build
  2. cd build
  3. cmake ..
  4. make -j4
  5. 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的拍照
  1. import cv2
  2. import sys
  3. import time
  4. import datetime
  5. import os
  6. if __name__ == '__main__':
  7. dir = "observation/"
  8. if not os.path.exists(dir):
  9. os.mkdir(dir)
  10. print "save path:", dir
  11. start_time = int(time.time())
  12. if sys.argv.__len__() == 2:
  13. interval = int(sys.argv[1])
  14. else:
  15. interval = 10
  16. print "start time:", datetime.datetime.strftime(datetime.datetime.now(), '%Y-%m-%d %H:%M:%S')
  17. shot_time = start_time + interval
  18. while True:
  19. now_time = int(time.time())
  20. if now_time == shot_time:
  21. cap = cv2.VideoCapture(0 + cv2.CAP_DSHOW)
  22. ret, frame = cap.read()
  23. save_str = datetime.datetime.strftime(datetime.datetime.now(), '%Y-%m-%d-%H-%M-%S')
  24. print "shot time:", save_str
  25. cv2.imwrite(dir + save_str + ".jpg", frame)
  26. shot_time = now_time + interval
  27. cap.release()

采用OpenCV实现的思路很简单,通过计时实现按时拍照功能,到时间就打开相机然后获取数据,完成后再释放。虽然在树莓派上配置OpenCV相对麻烦一些,但基于OpenCV的代码适用性比较广泛,不仅可以用于树莓派,只要是OpenCV能获取到摄像头数据,都可以使用。 脚本只有一个启动参数,就是拍摄间隔,若不指定,默认间隔为10s。

(2)基于Raspistill命令的拍照
  1. import sys
  2. import time
  3. import datetime
  4. import os
  5. if __name__ == '__main__':
  6. dir = "observation/"
  7. if not os.path.exists(dir):
  8. os.mkdir(dir)
  9. print "save path:", dir
  10. rot = "180"
  11. sa = "30"
  12. width = "1024"
  13. height = "768"
  14. start_time = int(time.time())
  15. if sys.argv.__len__() == 2:
  16. interval = int(sys.argv[1])
  17. else:
  18. interval = 10
  19. print "start time:", datetime.datetime.strftime(datetime.datetime.now(), '%Y-%m-%d %H:%M:%S')
  20. shot_time = start_time + interval
  21. while True:
  22. now_time = int(time.time())
  23. if now_time == shot_time:
  24. save_str = datetime.datetime.strftime(datetime.datetime.now(), '%Y-%m-%d-%H-%M-%S')
  25. print "shot time:", save_str
  26. os.system("raspistill -o " + dir + save_str + ".jpg " +
  27. "-rot " + rot +
  28. " -sa " + sa +
  29. " -w " + width +
  30. " -h " + height)
  31. shot_time = now_time + interval

相比于OpenCV的代码,这个代码的核心就是构造命令然后定时执行。当然raspistill命令有很多参数可选,想了解更多可以看上面提到的官方文档,很全面。 相比于OpenCV,这种方式可以拍摄到更加高清的影像,还可以指定饱和度等等,毕竟是系统自带的命令,应该是对摄像头专门做过适配的,用起来会比通用的OpenCV方式效果好一些。 脚本只有一个启动参数,就是拍摄间隔,若不指定,默认间隔为10s。其它的参数可以直接在脚本中修改。

(3)基于照片生成延时视频
  1. import cv2
  2. import os.path
  3. rootdir = raw_input("Input the parent path of images:\n") + "\\"
  4. output = raw_input("Input output video path:\n")
  5. type = raw_input("Input file type of images:\n")
  6. scale = input("Input scale of video(0-1):\n")
  7. fps = input("Input fps of video:\n")
  8. print "OK...Processing...\n"
  9. paths = []
  10. names = []
  11. for parent, dirname, filenames in os.walk(rootdir):
  12. for filename in filenames:
  13. if filename.endswith(type):
  14. paths.append(parent + '\\' + filename)
  15. names.append(filename)
  16. if paths.__len__() is not 0:
  17. for path in paths:
  18. print path
  19. print paths.__len__(), "frames were found."
  20. tem = cv2.imread(paths[0])
  21. width = int(scale * tem.shape[1])
  22. height = int(scale * tem.shape[0])
  23. fourcc = cv2.VideoWriter_fourcc(*'XVID')
  24. out = cv2.VideoWriter(output, fourcc, fps, (width, height))
  25. count = 0
  26. for i in range(paths.__len__()):
  27. frame = cv2.imread(paths[i])
  28. str_time = names[i].split(".")[0].split('-')
  29. year = str_time[0]
  30. month = str_time[1]
  31. day = str_time[2]
  32. hour = str_time[3]
  33. minute = str_time[4]
  34. second = str_time[5]
  35. frame = cv2.resize(frame, (width, height), interpolation=cv2.INTER_AREA)
  36. frame = cv2.putText(frame, year + "-" + month + "-" + day + " " + hour + ":" + minute + ":" + second,
  37. (10, height - 25), cv2.FONT_HERSHEY_SIMPLEX, 0.5,
  38. (255, 255, 255), 1, cv2.LINE_AA)
  39. out.write(frame)
  40. count += 1
  41. print paths[i] + " " + ((count * 1.0 / paths.__len__()) * 100).__str__() + " %"
  42. out.release()
  43. print "--------------------------------"
  44. print "Output Video Information:"
  45. print "Output path:" + output
  46. print "Width:" + width.__str__()
  47. print "Height:" + height.__str__()
  48. print "FPS:" + fps.__str__()
  49. print "Frames:" + paths.__len__().__str__()
  50. print "Time:" + (paths.__len__() * 1.0 / fps * 1.0).__str__() + " s"
  51. print "--------------------------------"

这部分代码来自于这篇博客,只是对每一帧进行了额外的处理,添加了拍摄时间,如果不需要时间的话,那就完全一样了。 可以控制输出视频大小、FPS等参数,按提示输入即可。

(4)打开摄像头预览

为了方便调试,写了个工具类的脚本,用于查看摄像头预览画面,以便合适地摆放位置,选择好角度。

  1. import cv2
  2. import sys
  3. if __name__ == '__main__':
  4. cap = cv2.VideoCapture(0)
  5. fps = 10
  6. waitTime = 20
  7. width = int(cap.get(3))
  8. height = int(cap.get(4))
  9. print width, height
  10. if sys.argv.__len__() == 1:
  11. while cap.isOpened():
  12. ret, frame = cap.read()
  13. if frame is None:
  14. break
  15. else:
  16. cv2.imshow("frames", frame)
  17. k = cv2.waitKey(waitTime) & 0xFF
  18. if k == 27:
  19. break
  20. cap.release()
  21. elif sys.argv.__len__() == 2:
  22. angle = int(sys.argv[1])
  23. M = cv2.getRotationMatrix2D((width / 2, height / 2), angle, 1)
  24. while cap.isOpened():
  25. ret, frame = cap.read()
  26. if frame is None:
  27. break
  28. else:
  29. rotate = cv2.warpAffine(frame, M, (width, height))
  30. cv2.imshow("frames", rotate)
  31. k = cv2.waitKey(waitTime) & 0xFF
  32. if k == 27:
  33. break
  34. cap.release()

脚本只有一个启动参数,就是旋转角度,若不指定则不旋转,若指定如180,则表示选择180度。

5.总结

总的来说使用树莓派进行延时摄影难度并不是很大,可能前期的难点在于树莓派上OpenCV环境的搭建。因为树莓派是ARM架构,直接用pip是找不到合适的安装包的,只能自己编译。 如果有兴趣,可以研究OpenCV的交叉编译,但我看了几天还是有些问题没有解决,所以并没有使用。最终还是老老实实选择了在树莓派上直接编译,虽说会慢一些,但也还行,编译完大约1个小时左右,还可以接受。

最后,把所有代码都放到了Github上,感兴趣可以查看,欢迎下载测试,拍摄属于自己的延时摄影。

本文作者原创,未经许可不得转载,谢谢配合