步骤/目录:
0.需求介绍与准备
1.延时摄影
(1)安装摄像头
(2)开启摄像头权限
(3)32位Pi OS
(4)64位Pi OS
(5)一些优化
(6)crontab设置
2.其它

本文首发于个人博客https://lisper517.top/index.php/archives/83/,转载请注明出处。
本篇文章的目的是使用树莓派进行延时摄影。
本文实验日期为2023年5月1日。使用的是树莓派4B(内存8G版),系统为Pi OS 64位桌面版(2022年4月4日更新,镜像名 2022-04-04-raspios-bullseye-arm64.img )。
本文主要参考了 这篇文章这篇文章

0.需求介绍与准备

需要对种植的植物进行延时摄影,观察生长情况。
购买树莓派官方摄像头,大概是pi camera 2,购买摄像头支架。另外树莓派最好使用UPS供电,连接到wifi。

1.延时摄影

(1)安装摄像头

在树莓派断电状态下将摄像头排线接到树莓派。

(2)开启摄像头权限

raspi-config中找到Interface Options -> Legacy CameraLegacy Camera也可能只是Camera),允许打开摄像头。重启后,可以找到/dev/video0这个设备,说明摄像头已准备就绪。

(3)32位Pi OS

在32位版本的Pi OS里,自带了raspistill命令用于拍照、raspivid用于视频,不过发布64位的Pi OS以后官方已开始慢慢舍弃这2个命令(也可能是64位系统还未加入这些非核心功能?)。树莓派使用摄像头的方法主要有3种:

a.raspistill、raspivid命令,32位系统

b.python,picamera库

c.python,opencv库

如果你使用的是32位的Pi OS并安装了python,可以如下使用picamera库来调用摄像头(笔者未实践):

pip install picamera
mkdir ~/mybash/python
nano ~/mybash/python/time_lapse_photo.py

写入如下内容:

#!/usr/bin/python
from picamera import PiCamera

if __name__=="__main__":
    camera=PiCamera()
    camera.capture("/temp/test.jpg")

如果运行正常,应该可在/temp下找到test.jpg,之后使用crontab定时即可。

(4)64位Pi OS

笔者使用的是64位系统,from picamera import PiCamera时出现如下报错:

OSError: libmmal.so: cannot open shared object file: No such file or directory

据说这是因为这个.so文件没有64位的版本。不过也可以使用opencv库来操作摄像头:

apt install python3-opencv
mkdir ~/mybash/python
nano ~/mybash/python/time_lapse_photo.py

写入如下内容:

#!/usr/bin/python
import cv2 as cv

# 读取设备
cap = cv.VideoCapture('/dev/video0', cv.CAP_V4L)

# 设置分辨率
cap.set(cv.CAP_PROP_FRAME_WIDTH, 1920)
cap.set(cv.CAP_PROP_FRAME_HEIGHT, 1080)

# 截图
ret, frame = cap.read()
if ret:
    cv.imwrite('/temp/test.jpg', frame) 

# 必须释放摄像头
cap.release()

此时可以在/temp目录下找到test.jpg,说明已经可以使用摄像头了。

(5)优化及定时运行

延时摄影在后期处理时需要使用图片生成视频。在参考文章中提到摄像头在晚上拍摄的图片太黑看不清,笔者修改后的python脚本如下(需要pip install numpy):

#!/usr/bin/python
import cv2 as cv
import numpy as np
import time, os, shutil

RESO = (1920, 1080)  #图片和视频分辨率
IMG_DIR = r'/temp/test_photoes'  #图片存放目录
IMG_DIR_NAS = r'/temp/photoes'  #NAS上图片存放目录
IMG_DIR_ORDERED = r'/temp/ordered'  #排序后图片存放目录
VIDEO_DIR = r'/temp/test_videoes'  #视频存放目录
NAME_FORMAT = '%Y-%m-%d-%H-%M-%S'  #命名时采用的时间格式
#以上的全局参数自行修改


class camera():
    """摄像头类,实现了拍照、视频等功能。"""

    def __init__(self) -> None:
        """读取设备,设置分辨率"""
        cap = cv.VideoCapture('/dev/video0', cv.CAP_V4L)
        cap.set(cv.CAP_PROP_FRAME_WIDTH, RESO[0])
        cap.set(cv.CAP_PROP_FRAME_HEIGHT, RESO[1])
        self.cap = cap

    @staticmethod
    def light_balance(img):
        """对图片进行光照处理,主要是提高夜间能见度"""
        img = cv.normalize(img,
                           dst=None,
                           alpha=255,
                           beta=30,
                           norm_type=cv.NORM_MINMAX)
        return img

    def take_a_pic(self,
                   img_name: str = '',
                   light_balance: bool = False) -> str:
        """拍一张照片并存储。
        :param img_name: 图片名称
        :param light_balance: 是否进行光照处理
        :return :图片绝对路径"""
        cap = self.cap
        ret, frame = cap.read()
        if img_name == '':
            img_name = '{}.jpg'.format(time.strftime(NAME_FORMAT))
        img_fullpath = os.path.join(IMG_DIR, img_name)
        if ret:
            if light_balance == True:
                frame = camera().light_balance(frame)
            cv.imwrite(img_fullpath, frame)
        return img_fullpath

    def take_a_video(self,
                     video_name: str = '',
                     frames: int = 150,
                     light_balance: bool = False) -> str:
        """录制一段视频并储存,可指定帧数。
        :param video_name: 视频名称
        :param frames: 视频总帧数
        :param light_balance: 是否进行光照处理
        :return : 视频绝对路径"""
        cap = self.cap
        fps = cap.get(cv.CAP_PROP_FPS)  #设置摄像头的帧率。笔者的摄像头帧率为30
        #如果要通过秒数来指定视频长度,可用秒数*帧率
        if video_name == '':
            video_name = '{}.avi'.format(time.strftime(NAME_FORMAT))
        video_fullpath = os.path.join(VIDEO_DIR, video_name)
        video = cv.VideoWriter(video_fullpath,
                               cv.VideoWriter_fourcc('I', '4', '2', '0'), fps,
                               RESO)  #编解码器I420可输出avi格式视频
        for i in range(frames):
            ret, frame = cap.read()
            if ret:
                if light_balance == True:
                    frame = camera().light_balance(frame)
                video.write(frame)
        return video_fullpath

    @staticmethod
    def copy_delete_order_photoes() -> None:
        """将本机的文件夹上传到NAS后清空,然后对NAS上的图片进行排序、重命名。
        """
        dir_1 = os.path.dirname(IMG_DIR) + os.path.sep + '*'
        dir_2 = os.path.dirname(IMG_DIR_NAS)
        os.system('cp -r {} {}'.format(dir_1, dir_2))
        dir_1 = IMG_DIR + os.path.sep + '*'
        dir_2 = VIDEO_DIR + os.path.sep + '*'
        os.system('rm {}'.format(dir_1))
        os.system('rm {}'.format(dir_2))
        for dirpath, dirnames, filenames in os.walk(IMG_DIR_NAS):
            if dirpath != IMG_DIR_NAS:  #仅对第一层排序
                break
            filenames.sort()
            for filename in filenames:
                src_abs_path = os.path.join(dirpath, filename)
                dst_abs_path = os.path.join(
                    IMG_DIR_ORDERED,
                    str(filenames.index(filename) + 1) + '.jpg')
                shutil.copy(src_abs_path, dst_abs_path)

    def __del__(self) -> None:
        self.cap.release()  #释放摄像头


if __name__ == '__main__':
    cam = camera()
    print(cam.cap.get(cv.CAP_PROP_FPS))  #打印帧数
    cam.take_a_pic(light_balance=True)
    start_time = time.time()
    cam.take_a_video(frames=150, light_balance=True)
    print('{:.2f}'.format(time.time() - start_time))  #观察视频写入所需时间

需要注意的是,在1920*1080的分辨率下,图片或视频都比较大,笔者实验时一张图片520-600KB,一段150帧(5秒)的视频445MB,而笔者树莓派4B使用的TF卡读取、写入速度分别为100MB/s、10MB/s。之前笔者组建过家庭内的NAS,如果视频不在树莓派上储存而是通过wifi传到NAS需要55s左右。笔者实际使用时没有用到视频功能,如果需要使用可以适当降低分辨率。

在实际环境中,把存储目录换一下,修改主程序如下:

if __name__ == '__main__':
    cam = camera()
    hour = time.localtime().tm_hour
    minute = time.localtime().tm_min
    if hour >= 5 and hour <= 21:
        cam.take_a_pic(light_balance=False)
    elif hour == 0 and minute == 20:
        cam.take_a_pic(light_balance=True)
        camera.copy_delete_order_photoes()
    else:
        cam.take_a_pic(light_balance=True)

最后,使用crontab定时执行python脚本:

crontab -e

写入新的一行:

*/20 * * * * python3 /root/mybash/python/time_lapse_photo.py

每隔20min照一张照片。
并且在每天的0:20,将树莓派上存储的图片、视频上传到NAS上,删除树莓派上的文件,并对NAS上的图片进行排序。排序是为了便于做延时摄影的视频。

2.其它

(1)将图片拼接为视频

首先对图片进行排序,如上文所示。
然后使用ffmpeg或者pr将图片转换为视频。以ffmpeg为例(ffmpeg的安装在 Python,爬虫与深度学习(17)——电影的信息爬取、下载与处理(二) 中稍提到过):

cd /temp/ordered
ffmpeg -r 24 -f image2 -i %d.jpg test.mp4

-r 24 指定视频帧率为24,也就是24张图片1s; -f image2 指定当前使用图像生成视频; -i %d.jpg 指定图片的名称,如果学习过C就会知道%d是整型的占位符。
这样生成的视频比较朴素,可以使用各种视频剪辑软件加上BGM或者特效等。

(2)将树莓派改造为监控摄像头

使用树莓派的摄像头,还可以将树莓派改造为监控摄像头,可通过网页查看,也可用于直播等。这里给出一些参考文章:
树莓派+Flask实现视频流媒体WEB服务器
工业树莓派结合USB摄像头实现远程网络监控
在树莓派上安装 ZoneMinder 实现摄像头监控
基于树莓派 CribSense 的婴儿监视器
用树莓派做 RTMP 流直播服务器,可推送至斗鱼直播
树莓派制造日视/夜视串流直播摄像头

标签: 树莓派, 摄影

添加新评论