树莓派的摄像头接口,用树莓派和Render构建一个物联网安全摄像头
树莓派的摄像头接口,用树莓派和Render构建一个物联网安全摄像头由于并不需要图形化界面,因此我们可以安装仅供专家使用的Raspberry Pi OS Lite版本。不过,如果您是首次使用Raspberry Pi进行开发的话,也可选用带有桌面的64 位版本的Raspberry Pi OS。第2步:安装Raspberry Pi OS(树莓派操作系统)。从官网上获取相关的指南和工具,包括如何使用SD卡等。一个Raspberry Pi(树莓派) 4、一个运动检测传感器、一个摄像头模块,以及如下物料清单(BOM)中的各种小组件。软件第1步:为Raspberry Pi插入可靠的电源。最好使用BOM中指定的官方版本。毕竟有消息称,一些旧的Raspberry Pi 4型号存在着一些USB-C电缆和电源的适配问题。
译者 | 陈峻
如今,市场上的智能监控摄像头林林总总,它们往往对我们来说是一种看家护院的黑匣子,我们无法知晓其内部的工作机制。如果我们想一探究竟,则需要利用物联网的相关知识,去自行搭建监控系统。下面,我将从客户端、仪表板UI、以及服务器端等方面,从硬件组装和软件部署入手,和您深入讨论如何构建一个物联网安全摄像头。
1.构建的目标我们希望新创建的家用智能摄像头监控系统,能够实现如下四个方面:1. 通过运动检测模块,系统会对检测到的运动物体进行拍照。2. 可将图像保存到远程服务器上。3. 通过访问服务器的仪表板,我们可以查看所有事件,包括照片和时间戳。4. 以滑动窗口的形式,保存最近20个事件,并清理所有旧的事件。
2.需要哪些组件?硬件
一个Raspberry Pi(树莓派) 4、一个运动检测传感器、一个摄像头模块,以及如下物料清单(BOM)中的各种小组件。
软件
- 一个用于部署、保存和显示摄像头图像的服务器端Render.com帐户。
- Git、python 3和一个代码编辑器。
第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每个引脚的完整说明。
在本例中,我们使用一根黑线将传感器的地线,连接到电路板的地线(PIN 6)上,一根红线连接到 5V(PIN 2)上,并将信号线连接到其中一个GPIO(PIN 11)上。下面的两张图像展示了组装的效果,当然,如果您不知道哪根线缆应当对应哪里的话,请取下传感器上的盖子,并仔细检查PCB上的标签。
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.
请选择“接口选项”菜单。
选择“启用/禁用传统摄像头支持”并确保将其已禁用。
最后,保存并重新启动。
6.Picamera2与PicameraPicamera2是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目录中,并保持文件名与摄像头捕获图像的时间相一致。下图便是我的摄像头所拍摄到的图像:
至此,我们只完成了项目的一半,毕竟这些都是在本地实现的,并未通过物联网进行远程监控,更谈不上防止有人访问我们的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.创建一个服务器来存储图像针对存储图像的服务器,我们希望:
- 支持Python代码,特别是Flask。
- 无需RDBMS或复杂的数据库,仅靠文件系统来存储图像。
- 提供如下简单API的REST接口:
/upload 上传图像。
/ 获取所有图像的列表。
/cleanup 删除旧的图像。
/download/<name>下载单个图像。
- 安全的TLS连接。
- 在完成身份验证的基础上,允许Raspberry Pi上传文件。
- 自动部署。
- 支持安全的环境变量,可用于存储用户凭据。
- 低成本(不超过几美元/月)。
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模式下,我们则可以使用浏览器来实现。
11.本地测试虽然您可以直接在Raspberry Pi 4中测试服务器,但是如果您有Linux或Mac系统,那么配置和启动它会更加容易。在本例中,我们首先需要创建一个.env文件,并将其放在与应用程序相同的目录中。.env文件将会存储服务器如下所需的信息:
- 用于管理Flask会话的密钥
- 保存图像的上传文件夹
- 可接受图像的最大尺寸
- 用于身份验证的用户名和密码
- 服务器本身的端点URL
下面展示的是.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.
下图展示了上传图像的请求已被成功受理。
12.将服务器部署到Render处至此,我们可以将服务器推送到一个真实、稳定且安全的环境中了。我们希望:
- 能够在磁盘上直接存储图像,而无需配置数据库。
- 为了避免在服务器上保留过多的图像,通过Cronjob运行某个API,以定期只保留最后20张图像。
- 简单地使用基于.env文件的密钥和变量
在注册到平台之前,我建议您通过单击屏幕右上角的“分叉(fork)”按钮,来分叉现有的GitHub存储库。
接着,您可以在Github上完成注册。
然后,请从仪表板中选择“新建Web服务”。
并搜索最近分叉的存储库(repo)。
为了配置服务器,您可以先选择一个免费的入门计划,并在后期按需选购永久性磁盘。其中会涉及到如下参数:
- 名称:可自由选择
- 环境:Python 3
- 地区:选择离您最近的一个
- 分支:main
- 构建命令 pip install -r requirements.txt
- 启动命令gunicorn main:app
现在让我们转到界面的高级部分,以设置密钥文件。您可以将其命名为.env,并粘贴以下的文本内容(您可以按需进行更改):
14.创建永久性磁盘在Render上创建永久性磁盘并不难,我们完全可以使用界面来完成。您只需单击左侧的磁盘部分,为其选择名称和安装路径即可。例如:
- 名称:图像
- 挂载路径:/var/img
- 大小:1GB
我们将会在“事件”选项卡中收到有关其状态的通知。
如果我们点击一个特定的事件,将能够看到所有的细节。
完成后,您将在页面的顶部看到服务器的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服务。
下面是针对Cronjob的设置:
名称:清理旧文件
- 地区:法兰克福
- 时间表:4 5 * * 2(我使用https://crontab.guru/来创建正确的字符串。)
- 命令:Python 3 auto_cleanup.py 20(最后一个参数是设定保留图像的数量)
- 构建命令:pip install -r requirements.txt(这是安装所有依赖项所必需的)
- 分支:main
- 自动部署:是
- Cronjob失败通知:用户帐户的相关通知设置
为了测试其效果,我们可以在Cronjob上手动触发其运行,而无需等待真实的时间表,即:单击页面顶部的“触发运行”按钮即可。Render界面的仪表板会显示如下信息:
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.
它会产生如下列表:
17.在外出时查看自己的仪表板物联网的好处不仅在于您可以安全地远程存储图像,而且能够避免因有人窃取或损坏您的Raspberry Pi,而丢失数据。也就是说,您可以身处世界任何地方,通过使用服务器.env文件中记录的用户名和密码,访问并登录Render的完整URL,以查看照片数据,并及时捕获设备前的运动事物。下面的一组照片来自我家的摄像头。其中的最后一张记录了我爱人对摄像头进行测试的场景。
18.小结在上文中,我向您介绍了如何以端到端的方式,从硬件设置到服务器部署,来创建一个廉价且实用的物联网摄像头应用。您可以使用Raspberry Pi 4、Python、Flask、Render等技术组件与服务,在短短几个小时内构建出具有远程图像上传功能的安全摄像头。
原文链接:https://dzone.com/articles/iot-security-camera-with-rasbperry-and-render
译者介绍陈峻 (Julian Chen),51CTO社区编辑,具有十多年的IT项目实施经验,善于对内外部资源与风险实施管控,专注传播网络与信息安全知识与经验;持续以博文、专题和译文等形式,分享前沿技术与新知;经常以线上、线下等方式,开展信息安全类培训与授课。
来源: 51CTO技术栈