树莓派读取海康摄像头(用树莓派和Render构建一个物联网安全摄像头)(1)

译者 | 陈峻

如今,市场上的智能监控摄像头林林总总,它们往往对我们来说是一种看家护院的黑匣子,我们无法知晓其内部的工作机制。如果我们想一探究竟,则需要利用物联网的相关知识,去自行搭建监控系统。下面,我将从客户端、仪表板UI、以及服务器端等方面,从硬件组装和软件部署入手,和您深入讨论如何构建一个物联网安全摄像头。

1.构建的目标

我们希望新创建的家用智能摄像头监控系统,能够实现如下四个方面:1. 通过运动检测模块,系统会对检测到的运动物体进行拍照。2. 可将图像保存到远程服务器上。3. 通过访问服务器的仪表板,我们可以查看所有事件,包括照片和时间戳。4. 以滑动窗口的形式,保存最近20个事件,并清理所有旧的事件。

2.需要哪些组件?

硬件

一个Raspberry Pi(树莓派) 4、一个运动检测传感器、一个摄像头模块,以及如下物料清单(BOM)中的各种小组件。

软件

3.配置Raspberry Pi

第1步:为Raspberry Pi插入可靠的电源。最好使用BOM中指定的官方版本。毕竟有消息称,一些旧的Raspberry Pi 4型号存在着一些USB-C电缆和电源的适配问题。

第2步:安装Raspberry Pi OS(树莓派操作系统)。从官网上获取相关的指南和工具,包括如何使用SD卡等。

由于并不需要图形化界面,因此我们可以安装仅供专家使用的Raspberry Pi OS Lite版本。不过,如果您是首次使用Raspberry Pi进行开发的话,也可选用带有桌面的64 位版本的Raspberry Pi OS。

第3步:测试PIR运动传感器,以检测和捕捉房间中的运动物体。注意,传感器上有三根线,其中两根用于电源( 5V和接地),第三根用于从传感器读取数值,即:如果传感器检测到移动,就读取1,否则读取0。请使用pinout命令,查看Raspberry每个引脚的完整说明。

树莓派读取海康摄像头(用树莓派和Render构建一个物联网安全摄像头)(2)

在本例中,我们使用一根黑线将传感器的地线,连接到电路板的地线(PIN 6)上,一根红线连接到 5V(PIN 2)上,并将信号线连接到其中一个GPIO(PIN 11)上。下面的两张图像展示了组装的效果,当然,如果您不知道哪根线缆应当对应哪里的话,请取下传感器上的盖子,并仔细检查PCB上的标签。

树莓派读取海康摄像头(用树莓派和Render构建一个物联网安全摄像头)(3)

树莓派读取海康摄像头(用树莓派和Render构建一个物联网安全摄像头)(4)

4.检测运动

为了检测运动,我们需要通过软件来读取PIR的数值,并发送通知。GitHub上的 Python版本提供了针对此类应用的简单版本,请参考如下代码段:

Python

from gpiozero import MotionSensor from datetime import datetime from signal import pause pir = MotionSensor(17) def capture(): timestamp = datetime.now().isoformat() print('%s Detected movement' % timestamp) def not_moving(): timestamp = datetime.now().isoformat() print('%s All clear' % timestamp) pir.when_motion = capture pir.when_no_motion = not_moving pause()1.2.3.4.5.6.7.8.9.10.11.12.13.

注意,由于我们的运动检测器已插入GPIO17(尽管在物理板上,它对应的是引脚11),因此我们将17的值传递给MotionSensor(),并通过运行python pir_motion_sensor.py,来启动之,以实现对PIR时间的调整。

为了避免过于频繁地被运动触发,内部计时器会阻止系统持续发送运动信号,因此传感器存在着虽然能够每次检测到运动,但可能不会去通知系统的风险。由于计时器的范围是0-255秒(255是全部顺时针方向,0为所有逆时针),因此根据我的经验,只需将定时器配置在7-10秒之间,电位器便可以在几乎水平的位置,逆时针地转动。类似地,对于灵敏度电位器而言,顺时针方向表示灵敏度更高。其对应的命令输出会显示如下:

Plain Text pi@raspberrypi:~/raspberry-pi-security-camera-client $ python pir_motion_sensor.py 2022-04-21T15:35:35.275947 Detected movement 2022-04-21T15:35:41.607265 All clear1.2.3.4.

5.添加摄像头

在Raspberry Pi处于关闭状态,以及断开了与任何电源的连接时,我们将摄像头安装在右侧。而在完成后,请重启Raspberry Pi,并确保已拥有最新的摄像头栈(camera stack)。

然后,请打开控制台并输入如下内容:

Shell $ sudo raspi-config1.2.

请选择“接口选项”菜单。

树莓派读取海康摄像头(用树莓派和Render构建一个物联网安全摄像头)(5)

选择“启用/禁用传统摄像头支持”并确保将其已禁用。

树莓派读取海康摄像头(用树莓派和Render构建一个物联网安全摄像头)(6)

树莓派读取海康摄像头(用树莓派和Render构建一个物联网安全摄像头)(7)

最后,保存并重新启动。

6.Picamera2与Picamera

Picamera2是libcamera的新式Python端口。其对应的旧项目--Picamera虽然基于不同的系统,但是其接受度颇高。

7.测试摄像头

为了测试摄像头,我使用Picamera2创建了一个简短的脚本。鉴于Picamera2项目仍处于预览阶段,其安装并不容易。下面,我们先运行example_picamera2.py脚本,来验证摄像头是否已设置正确:

Shell $ python example_picamera2.py1.2.

而example_picamera2.py的具体内容如下:

Python

from gpiozeroimport MotionSensor frompicamera2.picamera2 import * fromdatetime import datetime fromsignal import pause pir = MotionSensor(17) camera = Picamera2() camera.start_preview(Preview.NULL) config = camera.still_configuration() camera.configure(config) defcapture(): camera.start() timestamp = datetime.now().isoformat() print('%s Detected movement' % timestamp) metadata = camera.capture_File('/home/pi/%s.jpg' % timestamp) print(metadata) camera.stop() defnot_moving(): timestamp = datetime.now().isoformat() print('%s All clear' % timestamp) pir.when_motion = capture pir.when_no_motion = not_moving pause()1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.

每当移动检测PIR传感器检测到物体移动时,该文件都会执行快照,并将图像放置在/home/pi目录中,并保持文件名与摄像头捕获图像的时间相一致。下图便是我的摄像头所拍摄到的图像:

树莓派读取海康摄像头(用树莓派和Render构建一个物联网安全摄像头)(8)

至此,我们只完成了项目的一半,毕竟这些都是在本地实现的,并未通过物联网进行远程监控,更谈不上防止有人访问我们的Raspberry Pi、移除SD卡、并带走监控记录。

8.编写客户端代码并在本地进行测试

下面,我们准备在客户端上实现以下软件逻辑:

(1)使用Picamera2设置摄像头。

(2)初始化运动传感器。

(3) 当检测到物体运动时,读取事件并调用以下函数:

a. 捕获图像并将其保存到本地文件系统上的一个文件中。

b. 将图像上传到远程服务器上。

c. 如果上传正确,则删除本地文件,以避免填满Raspberry Pi上的所有空间。

(4) 当由于超时(在本例子中为6-7秒)而不再检测到运动时,开始读取事件,并打上带有“All clear”消息的时间戳。5. 等待下一个事件。

下面是对应的高级别(high-level)代码:

Python

def init(settings): camera = setup_camera() pir = MotionSensor(settings.get('PIR_GPIO')) pir.when_motion = picture_when_motion(pir, camera, settings) pir.when_no_motion = not_moving pause()1.2.3.4.5.6.

其中,最复杂的函数是picture_when_motion。当设备从非运动状态变为运动状态时,when_motion便会开始执行。我们可以设置为不接受其他参数,或仅接受单个强制参数。我将通过下面的代码,将其转换为一个函数,并创建一个回调(callback)来返回它。

Python

defpicture_when_motion(pir, camera, settings): setup_path(settings.get('IMG_PATH')) def capture_and_upload_picture(): if camera: file_path = capture(camera, settings.get('IMG_PATH')) server_settings = settings.get('SERVER') uploaded = upload_picture(file_path, server_settings) if uploaded: cleanup(file_path) else: print("Camera not defined") return capture_and_upload_picture1.2.3.4.5.6.7.8.9.10.11.12.

上述代码中的捕获函数类似于前面用于测试摄像头的函数,而upload_picture函数是将软件从本地转换为物联网应用的核心。下面让我们来对其进行分析:Python

def upload_picture(file_path, server_settings): if server_settings.get('base_url'): url = urljoin(server_settings.get('base_url'), 'upload') if server_settings.get('user') and server_settings.get('password'): user = server_settings.get('user') password = server_settings.get('password') files = {'file': open(file_path, 'rb')} print('Uploading file %s to URL: %s' %(file_path, url)) try: r = requests.post(url, files=files, auth=HTTPBasicAuth(user, password)) image_path = r.json().get('path') except e: print(e) if not image_path or not r.ok: print('Error uploading image') return False print('Image available at: {}'.format(image_path)) return True1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.

理想情况下,我们让服务器使用用户名和密码的验证方式,接受作为POST请求的负载文件。其对应的命令为:

Shell curl \ -F "file=@/home/user/Desktop/test.jpg" \ http://localhost:5000/upload1.2.3.4.

由于它们是使用开源的MIT许可证发布的,因此您既可以随意复制它们,也可以使用python main.py来执行之。

9.创建一个服务器来存储图像

针对存储图像的服务器,我们希望:

/upload 上传图像。

/ 获取所有图像的列表。

/cleanup 删除旧的图像。

/download/<name>下载单个图像。

GitHub上提供了在本地、或在服务器上运行代码的相关说明。

10.Flask应用

Flask是一个简单灵活的Python框架,可用于快速创建以REST API为主的Web应用。同时,我们可以将主要代码放在main.py文件。首先,我们需要初始化Flask应用,并声明身份验证的方法。对此,我会声明一个名为setup的函数,以读取本地机器上的各种可用环境变量。同时,我也会创建一个包含了所有环境变量的.env文件。接着,我声明了一个verify_password函数,来验证提供给服务器的密码是否正确。然后,我通过函数upload_file,来支持上传新的文件,并访问/upload端点,将图像存储在文件系统中,其具体内容如下:

Python

defupload_file(): if request.method == 'POST': # check if the post request has the file part if 'file' not in request.files: flash('No file part') return redirect(request.url) file = request.files['file'] # If the user does not select a file, the browser submits an # empty file without a filename. if file.filename == '': flash('No selected file') return redirect(request.url) if file and allowed_file(file.filename): filename = secure_filename(file.filename) file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename)) return jsonify(success=True, filename=filename, path=urljoin(request.host_url, url_for('download_file', name=filename))) return ''' <!doctype html> <title>Upload new File</title> <h2>Upload new File</h2> <form method=post enctype=multipart/form-data> <input type=file name=file> <input type=submit value=Upload> </form> '''1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.

该函数在GET和POST模式下均可有效。其中,运行在POST中时,我们可以从文本客户端、或其他应用程序处上传文件;而在GET模式下,我们则可以使用浏览器来实现。

树莓派读取海康摄像头(用树莓派和Render构建一个物联网安全摄像头)(9)

11.本地测试

虽然您可以直接在Raspberry Pi 4中测试服务器,但是如果您有Linux或Mac系统,那么配置和启动它会更加容易。在本例中,我们首先需要创建一个.env文件,并将其放在与应用程序相同的目录中。.env文件将会存储服务器如下所需的信息:

下面展示的是.env-example-local文件的内容。您可以将其用作模板,复制、重命名、并按需予以修改。

属性文件 SECRET_KEY='change-this-to-something-unlikely-to-guess' UPLOAD_FOLDER = './img' MAX_CONTENT_LENGTH = 16000000 USERNAME = 'admin' PASSWORD = 'change-this-to-your-unique-password' SERVER='http://127.0.0.0:5001/'1.2.3.4.5.6.7.

通过运行python main.py,服务器将被启动,并进入主动调试模式,以便我们观察到后台发生的情况。

Shell $ python main.py * Serving Flask app 'main' (lazy loading) * Environment: production WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. * Debug mode: on * Running on all addresses (0.0.0.0) WARNING: This is a development server. Do not use it in a production deployment. * Running on http://127.0.0.1:5001 * Running on http://192.168.123.228:5001 (Press CTRL C to quit) * Restarting with stat * Debugger is active! * Debugger PIN: 332-932-8291.2.3.4.5.6.7.8.9.10.11.12.13.14.

让我们首先通过CURL的方式来测试上传文件。您也可以使用Postman之类的工具来进行测试。假设您想从路径/Users/luca/Pictures/image.jpeg处上传图像,请使用如下命令:

Shell curl \ -F "file=@/Users/luca/Pictures/image.jpeg" \ -u 'admin:password' \ 'http://127.0.0.1:5001/upload' { "filename": "image.jpeg", "path": "http://127.0.0.1:5001/download/image.jpeg", "success": true }1.2.3.4.5.6.7.8.9.10.

下图展示了上传图像的请求已被成功受理。

树莓派读取海康摄像头(用树莓派和Render构建一个物联网安全摄像头)(10)

12.将服务器部署到Render处

至此,我们可以将服务器推送到一个真实、稳定且安全的环境中了。我们希望:

13.注册并配置首个服务

在注册到平台之前,我建议您通过单击屏幕右上角的“分叉(fork)”按钮,来分叉现有的GitHub存储库。

树莓派读取海康摄像头(用树莓派和Render构建一个物联网安全摄像头)(11)

接着,您可以在Github上完成注册。

树莓派读取海康摄像头(用树莓派和Render构建一个物联网安全摄像头)(12)

然后,请从仪表板中选择“新建Web服务”。

树莓派读取海康摄像头(用树莓派和Render构建一个物联网安全摄像头)(13)

并搜索最近分叉的存储库(repo)。

树莓派读取海康摄像头(用树莓派和Render构建一个物联网安全摄像头)(14)

为了配置服务器,您可以先选择一个免费的入门计划,并在后期按需选购永久性磁盘。其中会涉及到如下参数:

树莓派读取海康摄像头(用树莓派和Render构建一个物联网安全摄像头)(15)

现在让我们转到界面的高级部分,以设置密钥文件。您可以将其命名为.env,并粘贴以下的文本内容(您可以按需进行更改):

树莓派读取海康摄像头(用树莓派和Render构建一个物联网安全摄像头)(16)

树莓派读取海康摄像头(用树莓派和Render构建一个物联网安全摄像头)(17)

14.创建永久性磁盘

在Render上创建永久性磁盘并不难,我们完全可以使用界面来完成。您只需单击左侧的磁盘部分,为其选择名称和安装路径即可。例如:

树莓派读取海康摄像头(用树莓派和Render构建一个物联网安全摄像头)(18)

我们将会在“事件”选项卡中收到有关其状态的通知。

树莓派读取海康摄像头(用树莓派和Render构建一个物联网安全摄像头)(19)

如果我们点击一个特定的事件,将能够看到所有的细节。

树莓派读取海康摄像头(用树莓派和Render构建一个物联网安全摄像头)(20)

完成后,您将在页面的顶部看到服务器的URL。

现在,是时候开始从我们的Raspberry Pi客户端处上传一些真实的图像了。首先,我们需要更改Raspberry客户端中的.env文件。下面展示了其环境变量的信息:

属性文件 PIR_GPIO=17 USERNAME='admin' PASSWORD='change-me-with-a-real-password-please' API_SERVER='https://your-api-address.onrender.com/' IMG_PATH='img'1.2.3.4.5.6.

接着,请使用Python 3启动main.py的服务。

如果您在PIR传感器的区域内移动,摄像头将会拍摄照片并将其上传到服务器上。我们可以通过获取图像的URL,实现浏览器下载照片。

15.定期清理图像

为了避免在服务器上存储太多的图像,我人为地设定为最多保留20张。为此,我们需要创建一个额外的Cronjob服务,来定期调用API。

首先,我创建了一个名为/cleanup的服务器路由,它会调用keep_last_images()函数。该函数的定义如下:

Shell $ curl-v -d '{"keep": "20"}' -H "Content-Type: application/json" -u 'username:password' -X POST http://127.0.0.1:5001/cleanup/1.2.

此函数会按照创建图像的时间对图像进行排序,并保留POST请求有效负载中所指示的X数量的图像。请使用如下命令测试CURL的执行效果:

Shell $ curl-v -d '{"keep": "20"}' -H "Content-Type: application/json" -u 'username:password' -X POST http://127.0.0.1:5001/cleanup/1.2.

通过定期(如每周)调用上述函数,我们将能够清理所有比最近20张更旧的图像。

接着,我在Render的仪表板中创建了一个新的Cronjob服务。

树莓派读取海康摄像头(用树莓派和Render构建一个物联网安全摄像头)(21)

下面是针对Cronjob的设置:

名称:清理旧文件

树莓派读取海康摄像头(用树莓派和Render构建一个物联网安全摄像头)(22)

为了测试其效果,我们可以在Cronjob上手动触发其运行,而无需等待真实的时间表,即:单击页面顶部的“触发运行”按钮即可。Render界面的仪表板会显示如下信息:

树莓派读取海康摄像头(用树莓派和Render构建一个物联网安全摄像头)(23)

16.创建事件的仪表板

为了实现对摄像头的安全管理,我们可以使用list_files()函数查询文件系统,并按照创建日期列出所有的图像文件。请参考如下代码段:

Python

# List endpoint, get an HTML page listing all the uploaded files link @app.route('/') @auth.login_required def list_files(): files = get_list_of_img_path(path=app.config['UPLOAD_FOLDER'], reverse=True) images_url = [] for file in files: images_url.append(urljoin(request.host_url, url_for('download_file', name=os.path.basename(file)))) return render_template('imglist.html', images_url=images_url)1.2.3.4.5.6.7.8.9.

上述函数会调用与操作系统相关的API,并返回按创建时间排序的文件列表。接着,它会使用jinja模板,将数据返回到imglist.html文件中。该文件的基本部分为:

HTML <ul> {% for image in images_url %} <li><a href="{{image}}">{{image}}</a></li> {% else %} <li>No images uploaded yet</li> {% endfor %} </ul>1.2.3.4.5.6.7.8.

它会产生如下列表:

树莓派读取海康摄像头(用树莓派和Render构建一个物联网安全摄像头)(24)

17.在外出时查看自己的仪表板

物联网的好处不仅在于您可以安全地远程存储图像,而且能够避免因有人窃取或损坏您的Raspberry Pi,而丢失数据。也就是说,您可以身处世界任何地方,通过使用服务器.env文件中记录的用户名和密码,访问并登录Render的完整URL,以查看照片数据,并及时捕获设备前的运动事物。下面的一组照片来自我家的摄像头。其中的最后一张记录了我爱人对摄像头进行测试的场景。

树莓派读取海康摄像头(用树莓派和Render构建一个物联网安全摄像头)(25)

树莓派读取海康摄像头(用树莓派和Render构建一个物联网安全摄像头)(26)

树莓派读取海康摄像头(用树莓派和Render构建一个物联网安全摄像头)(27)

18.小结

在上文中,我向您介绍了如何以端到端的方式,从硬件设置到服务器部署,来创建一个廉价且实用的物联网摄像头应用。您可以使用Raspberry Pi 4、Python、Flask、Render等技术组件与服务,在短短几个小时内构建出具有远程图像上传功能的安全摄像头。

原文链接:https://dzone.com/articles/iot-security-camera-with-rasbperry-and-render

译者介绍

陈峻 (Julian Chen),51CTO社区编辑,具有十多年的IT项目实施经验,善于对内外部资源与风险实施管控,专注传播网络与信息安全知识与经验;持续以博文、专题和译文等形式,分享前沿技术与新知;经常以线上、线下等方式,开展信息安全类培训与授课。

来源: 51CTO技术栈

,