多摄像头使用指南
一、演示功能概述
Air8101工业引擎支持DVP和UVC两种不同协议标准的摄像头,DVP摄像头通过24PIN FPC连接器接入开发板,UVC摄像头通过USB-A接入开发板。
本文章用于帮助用户快速上手Air8101开发板通过USB HUB扩展坞外挂多个UVC摄像头的使用。助力用户高效实现多摄像头数据采集与处理功能。
需要注意的是,在使用UVC摄像头时,需要将J29上的VBAT与VUVC进行短接,从而用VBAT给UVC供电,另外还可以用软件控制GPIO P28(2.8V_EN网络)控制UVC供电通断。 同样需要注意,如果摄像头需要支持5V时,可在J29上使用跳线帽短接+5V和VUVC。
二、准备硬件环境
“古人云:‘工欲善其事,必先利其器。’在深入介绍本功能示例之前,我们首先需要确保以下硬件环境的准备工作已经完成。”
1. 参考:硬件环境清单,准备以及组装好基本硬件环境。
2. 本文以外挂四个UVC摄像头为例进行演示,硬件连接图如下:
三、准备软件环境
“凡事预则立,不预则废。”在详细阐述本功能示例之前,我们需先精心筹备好以下软件环境。
1. Luatools 工具;
2. 内核固件文件(底层 core 固件文件):https://www.air32.cn/air8101/LuatOS-SoC_V1003_Air8101_20250313_164346.soc。
3. LuatOS需要的脚本和资源文件
脚本和资源文件:https://gitee.com/openLuat/LuatOS-Air8101/tree/master/demo/camera/usb_cam;
lib 脚本文件:使用Luatools烧录时,勾选“添加默认 lib”选项,使用默认lib脚本文件;
准备好软件环境之后,接下来查看如何烧录项目文件到 Air8101 开发板,将本篇文章中演示使用的项目文件烧录到Air8101开发板中。
四、多摄像头基本用法
4.1 UVC 摄像头介绍
UVC摄像头是一种通过USB接口连接到电脑或其他设备的摄像头,也可被称为USB摄像头。它遵循USB视频类设备(UVC)标准,具有即插即用、兼容性强、功能多样、应用场景广泛等特点。
4.1 本文章将会实现的功能
1. 搭配LCD显示屏,在LCD屏幕上实时显示摄像头拍摄到的画面;
2. 通过Air8101开发板上的KEY3按键进行拍照,图像可以保存到内部文件系统、BUFF或者上传到服务器;
3. 通过Air8101开发板上的KEY5、KEY6按键可以切换USB端口号,从而切换USB摄像头。
4.2 文章内容应用
Air8101开发板软硬件资料:https://docs.openluat.com/air8101/luatos/quick_start/board_usege/
API接口参考:https://docs.openluat.com/air8101/luatos/api/core/camera/
4.3 API 接口介绍
camera.on(id, event, func)
注册摄像头事件回调。
参数
参数 | 类型 | 释义 | 取值 |
---|---|---|---|
id | int | camera id | camera 0写0, camera 1写1 |
event | string | 事件名称 | 字符串 |
func | function | 回调函数 | 回调函数 |
返回值
返回值 | 类型 | 释义 | 取值 |
---|---|---|---|
nil | nil | 无返回值 | nil |
例子
-- 注册摄像头回调函数
-- 注册 camera_id 的 "scanned" 事件
camera.on(camera_id, "scanned", function(id, str)
log.info("scanned", id, str)
if type(str) == 'string' then -- 如果是扫码模式(使用摄像头对二维码、条形码或其他类型的图案进行扫描和识别)
log.info("扫码结果", str)
elseif str == false then -- 如果摄像头没有正常工作
log.error("摄像头没有数据")
else -- 如果摄像头正常工作,并且不是扫码模式
log.info("摄像头数据", str)
sys.publish("capture done", true)
end
end)
camera.capture(id, save_path, quality)
启动摄像头拍照功能。
参数
参数 | 类型 | 释义 | 取值 |
---|---|---|---|
id | int | camera id | camera 0写0, camera 1写1 |
save_path | string/zbuff/nil | 摄像头拍照后的数据存放路径 | string/nil:文件保存路径,空则写在上次路径里,默认是/capture.jpg; zbuff:将图片保存在buff内不写入文件系统 |
quality | int | jpeg压缩质量 | 1最差,占用空间小,3最高,占用空间最大而且费时间,默认1 |
返回值
返回值 | 类型 | 释义 | 取值 |
---|---|---|---|
onoff | boolean | 是否成功启动摄像头拍照功能。完成后通过camera.on设置的回调函数回调接收到的长度 | 成功返回true,否则返回false |
例子
-- 将摄像头拍照的数据存入 "/testCamera.jpg" 文件中
-- jpeg压缩质量1最差,占用空间小,3最高,占用空间大,2和3需要非常多非常多的psram,尽量不要用
camera.capture(camera_id, "/testCamera.jpg", 1)
camera.close(id)
完全关闭指定的 camera,释放相应的IO资源。
参数
参数 | 类型 | 释义 | 取值 |
---|---|---|---|
id | int | camera id | camera 0写0, camera 1写1 |
返回值
返回值 | 类型 | 释义 | 取值 |
---|---|---|---|
onoff | boolean | 是否成功关闭指定摄像头 | 成功返回true,否则返回false |
例子
camera.close(0)
camera.stop(id)
暂停摄像头捕获数据。
参数
参数 | 类型 | 释义 | 取值 |
---|---|---|---|
id | int | camera id | camera 0写0, camera 1写1 |
返回值
返回值 | 类型 | 释义 | 取值 |
---|---|---|---|
onoff | boolean | 是否成功暂停指定摄像头 | 成功返回true,否则返回false |
例子
camera.stop(0)
五、多摄像头功能演示
5.1 源码
源码参考:https://gitee.com/openLuat/LuatOS-Air8101/tree/master/demo/camera/usb_cam
5.2 代码分析
5.2.1 联网功能代码分析
这一部分是联网功能的代码,默认打开了注释,用户可根据需要自行取消注释。
ssid
表示客户端热点的名称,password
表示客户端热点的密码。 设置完ssid
和password
的参数后,通过wlan.init()
函数进行WLAN初始化操作,通过wlan.connect(ssid, password, 1)
函数连接指定的客户端热点。 当联网成功获取到IP地址后,内核固件会发布一个“IP_READY”消息,可以通过sys.waitUntil("IP_READY")
函数等待这个消息,同时这个函数有两个返回值,第一个返回值是true/false,用于确定是否等到对应消息,第二个返回值是对应消息所携带的数据,此处若等到“IP_READY”消息,则第二个返回值会返回联网后获取到的IP地址。 最后通过sys.publish("net_ready")
发布一个"net_ready"消息,用于表示网络已经准备好。
-- -- 联网函数, 可自行删减
-- sys.taskInit(function()
-- -----------------------------
-- -- 统一联网函数, 可自行删减
-- ----------------------------
-- if wlan and wlan.connect then
-- -- wifi 联网, Air8101系列均支持
-- local ssid = "HONOR_100_Pro"
-- local password = "12356789"
-- log.info("wifi", ssid, password)
-- wlan.init()
-- wlan.connect(ssid, password, 1)
-- --等待WIFI联网结果,WIFI联网成功后,内核固件会产生一个"IP_READY"消息
-- local result, data = sys.waitUntil("IP_READY")
-- log.info("wlan", "IP_READY", result, data)
-- end
-- log.info("已联网")
-- sys.publish("net_ready")
-- end)
5.2.2 LCD 屏幕初始代码分析
这一部分是LCD屏幕初始化的代码,默认也是打开了注释,用户可根据需要自行取消注释。 main.lua文件中列举了四种不同型号的LCD屏幕初始化的示例代码。用户可以根据自己的LCD屏幕型号选择对应的示例代码(如果您的LCD型号不包括在这四个之中,也可以自己编写代码,前提是需要Air8101开发板支持您的LCD型号),本文章使用HX8282(1024*600)型号的LCD屏幕进行测试。 在
lcd.init
函数中,第一个参数表示LCD的型号,port
表示驱动端口,通过lcd.RGB
获得,pin_dc
、pin_pwr
、pin_rst
分别表示LCD数据/命令选择引脚、LCD背光引脚(可选)、LCD复位引脚,w
、h
用于调整 LCD屏幕的水平、竖直分辨率,direction
、xoffset
、yoffset
用于调整LCD屏幕的显示方向和偏移。 LCD屏幕有时候会因为硬件设计的原因导致显示颜色相反。如果遇到这种情况,可以取消注释lcd.invoff()
函数来关闭反色显示,以恢复正常显示颜色。
local port, pin_reset, bl = lcd.RGB, 36, 25
-----------------------------初始化LCD屏幕------------------------------------
-- -- Air8101开发板配套LCD屏幕 分辨率800*480
-- lcd.init("h050iwv",
-- {port = port, pin_dc = 0xff, pin_pwr = bl, pin_rst = pin_reset,
-- direction = 0, w = 800, h = 480, xoffset = 0, yoffset = 0})
-- -- Air8101开发板配套LCD屏幕 分辨率1024*600
-- lcd.init("hx8282",
-- {port = port,pin_pwr = bl, pin_rst = pin_reset,
-- direction = 0,w = 1024,h = 600,xoffset = 0,yoffset = 0})
-- -- Air8101开发板配套LCD屏幕 分辨率720*1280
-- lcd.init("nv3052c",
-- {port = port,pin_pwr = bl, pin_rst = pin_reset,
-- direction = 0,w = 720,h = 1280,xoffset = 0,yoffset = 0})
-- Air8101开发板配套LCD屏幕 分辨率480*854
-- lcd.init("st7701sn",
-- {port = port,pin_pwr = bl, pin_rst = pin_reset,
-- direction = 0,w = 480,h = 854,xoffset = 0,yoffset = 0})
------------------------------------------------------------------------------
-- 如果显示颜色相反,请解开下面一行的注释,关闭反色
-- lcd.invoff()
5.2.3 USB 摄像头参数配置代码分析
这一部分是UVC摄像头参数配置的代码,UVC摄像头是一种基于USB视频类设备(USB Video Class)标准的摄像头,因此也被称为USB摄像头。 在下方代码中,通过
camera.USB
将摄像头ID配置为USB类型,由于本文章是外挂多个USB摄像头,因此需要一个usb_port
变量用于设置 USB 端口号,默认端口号为1。接着创建一个名为usb_camera_set
的表,在表中包括有四种不同配置的摄像头参数,每个配置项包含摄像头ID、传感器宽度、传感器高度以及 USB端口号,然后通过usb_camera_set[1]
默认选择第一种配置,即分辨率为1280 * 720的摄像头参数。camera.on
函数为摄像头注册一个事件回调函数,该函数会在摄像头扫描到数据时被调用。回调函数接受两个参数:id
(摄像头ID)和str
(扫描结果)。 根据str
的类型,记录不同的日志信息。如果str
是字符串,则认为是扫码结果;如果是false
,则认为摄像头没有数据;如果是其他类型,则认为是摄像头数据,并发布一个名为"capture done"
的消息来通知拍照完成。 最后定义一个布尔变量camera_init_ok
,用于记录摄像头初始化是否成功,初始值为false,该变量会在后面的camera.init
函数调用。
local camera_id = camera.USB -- 选择USB摄像头
local usb_port = 1 -- USB摄像头端口号
-- 设置摄像头参数,30W-200W像素
-- 配置1:1280 * 720 分辨率
-- 配置2:864 * 480 分辨率
-- 配置3:800 * 480 分辨率
-- 配置4:640 * 480 分辨率
local usb_camera_set = {
{ id = camera_id, sensor_width = 1280, sensor_height = 720 , usb_port = usb_port}, -- 配置1
{ id = camera_id, sensor_width = 864, sensor_height = 480 , usb_port = usb_port}, -- 配置2
{ id = camera_id, sensor_width = 800, sensor_height = 480 , usb_port = usb_port}, -- 配置3
{ id = camera_id, sensor_width = 640, sensor_height = 480 , usb_port = usb_port} -- 配置4
}
local usb_camera_table = usb_camera_set[1] -- 选择摄像头参数
-- 注册摄像头回调函数
camera.on(camera_id, "scanned", function(id, str)
log.info("scanned", id, str)
if type(str) == 'string' then
log.info("扫码结果", str)
elseif str == false then
log.error("摄像头没有数据")
else
log.info("摄像头数据", str)
sys.publish("capture done", true)
end
end)
local camera_init_ok = false -- 定义一个局部变量camera_init_ok,用于记录摄像头初始化是否成功。
5.2.4 按键处理函数代码分析
这一部分是按键处理函数的代码。 Air8101开发板在进行硬件设计时,将KEY3~KEY6四个按键设计用于ADC14(GPIO12)的电压检测测试,因此需要判断ADC14(GPIO12)的电压值才能间接判断是哪个按键按下。 在本文章中,将KEY3按键用于拍照操作,将KEY5、KEY6按键用于切换USB端口号操作,KEY5按键是切换为上一个USB端口号,KEY6按键是切换为下一个USB端口号。又因为Air8101开发板最多只能支持四个USB摄像头,所以在代码中将USB端口号的范围限制在了1~4,最小为1,最大为4。
-- 按键处理函数
-- 通过检测ADC14的电压值来判断特定按键(KEY6、KEY5、KEY3)是否被按下,并根据按键的操作来控制USB摄像头的行为。
sys.taskInit(function()
while true do
-- 当检测到ADC14的电压值在1300到2000之间时,认为KEY6被按下。
-- 此时,代码会切换USB摄像头的端口号到下一个端口。
if adc.get(adc_pin_14) < 2000 and adc.get(adc_pin_14) > 1300 then
log.info("ADC14电压大于1.3V,小于2.0V")
log.info("KEY6按下")
if usb_camera_table.usb_port == 4 then
log.info("已经是最后一个端口了")
else
usb_camera_table.usb_port = usb_camera_table.usb_port + 1
camera.close(camera_id)
camera_init_ok = camera.init(usb_camera_table)
log.info("摄像头初始化", camera_init_ok)
log.info("USB_PORT端口切换: ", usb_camera_table.usb_port)
end
while true do
if adc.get(adc_pin_14) > 2000 then
break
end
end
end
-- 当检测到ADC14的电压值在1000到1300之间时,认为KEY5被按下。
-- 此时,代码会切换USB摄像头的端口号到上一个端口。
if adc.get(adc_pin_14) < 1300 and adc.get(adc_pin_14) > 1000 then
log.info("ADC14电压大于1.0V,小于1.3V")
log.info("KEY5按下")
if usb_camera_table.usb_port == 1 then
log.info("已经是第一个端口了")
else
usb_camera_table.usb_port = usb_camera_table.usb_port - 1
camera.close(camera_id)
camera_init_ok = camera.init(usb_camera_table)
log.info("摄像头初始化", camera_init_ok)
log.info("USB_PORT端口切换: ", usb_camera_table.usb_port)
end
while true do
if adc.get(adc_pin_14) > 2000 then
break
end
end
end
-- 当检测到ADC14的电压值小于100时,认为KEY3被按下。
-- 此时,代码会发布一个名为"PRESS"的消息,携带参数"KEY3",在这个main.lua文件中用于触发拍照功能。
if adc.get(adc_pin_14) < 100 then
log.info("ADC14电压小于1.0V")
log.info("KEY3按下")
sys.publish("PRESS", "KEY3")
while true do
if adc.get(adc_pin_14) > 2000 then
break
end
end
end
sys.wait(100)
end
end)
5.2.5 摄像头开机初始化及图像拍照、保存功能代码分析
这一部分是摄像头初始化(开机后的摄像头初始化,只运行一次)及图像拍照、保存功能的代码。 目前代码中只写了三种图像保存方式,分别为1. 保存到内部文件系统;2. 保存到ZBUFF中;3. 上传到服务器。默认是保存在ZBUFF中,用户可自行选择保存方式。 本文章将会使用第二、三保存方式进行演示。
-- 创建zbuff
local rawbuff, err = zbuff.create(200 * 1024, 0, zbuff.HEAP_PSRAM)
-- 初始化摄像头并处理拍照和上传图片的操作
sys.taskInit(function()
-- -- 开启缓冲区, 刷屏速度会加快, 但也消耗2倍屏幕分辨率的内存
-- lcd.setupBuff(nil, true) -- 使用sys内存, 只需要选一种
-- lcd.autoFlush(false)
if rawbuff == nil then
while true do
sys.wait(1000)
end
log.info("zbuff创建失败", err)
end
camera_init_ok = camera.init(usb_camera_table)
log.info("摄像头初始化", camera_init_ok)
log.info(rtos.meminfo("sys")) -- 打印系统内存信息
log.info(rtos.meminfo("psram")) -- 打印psram内存信息
while 1 do
local result, param = sys.waitUntil("PRESS", 5000)
log.info("PRESS", result, param)
if param == "KEY3" and camera_init_ok == 0 then
camera.start(camera_id) --开始指定的camera
-- camera.capture(camera_id, "/abc.jpg", 1) --camera拍照并保存到指定路径
camera.capture(camera_id, rawbuff, 1)
result, data = sys.waitUntil("capture done", 30000)
log.info(rawbuff:used())
camera.stop(camera_id) --停止指定的camera
-- camera.close(camera_id) --关闭指定的camera,释放相应的IO资源;完全关闭摄像头才用这个
-- rawbuff:resize(200 * 1024)
-- 通过串口发送图片数据
-- uart.tx(uartid, rawbuff) --找个能保存数据的串口工具保存成文件就能在电脑上看了, 格式为JPG
-- 通过WIFI网络上传到服务器查看
-- 上传到upload.air32.cn, 数据访问页面是 https://www.air32.cn/upload/data/
-- local code, resp = httpplus.request({
-- url = "http://upload.air32.cn/api/upload/jpg",
-- method = "POST",
-- body = rawbuff
-- })
-- log.info("http", code)
-- -- 打印内存信息, 调试用
-- log.info("sys", rtos.meminfo())
-- log.info("sys", rtos.meminfo("sys"))
-- log.info("psram", rtos.meminfo("psram"))
elseif param == "KEY3" then
log.info("KEY3按下,但是摄像头初始化失败")
end
end
end)
5.3 功能演示
5.3.1 启用联网功能
先将这一部分代码的注释取消,然后将
ssid
和password
的参数修改为自己客户端(手机、路由器等)热点名称和密码即可。
-- 联网函数, 可自行删减
sys.taskInit(function()
-----------------------------
-- 统一联网函数, 可自行删减
----------------------------
if wlan and wlan.connect then
-- wifi 联网, Air8101系列均支持
local ssid = "HONOR_100_Pro"
local password = "12356789"
log.info("wifi", ssid, password)
wlan.init()
wlan.connect(ssid, password, 1)
--等待WIFI联网结果,WIFI联网成功后,内核固件会产生一个"IP_READY"消息
local result, data = sys.waitUntil("IP_READY")
log.info("wlan", "IP_READY", result, data)
end
log.info("已联网")
sys.publish("net_ready")
end)
5.3.2 启用 LCD 屏幕初始化
本文章使用的LCD屏幕型号为HX8282,所以需要将第二个参数配置代码的注释取消。
-----------------------------初始化LCD屏幕------------------------------------
-- -- Air8101开发板配套LCD屏幕 分辨率800*480
-- lcd.init("h050iwv",
-- {port = port, pin_dc = 0xff, pin_pwr = bl, pin_rst = pin_reset,
-- direction = 0, w = 800, h = 480, xoffset = 0, yoffset = 0})
-- -- Air8101开发板配套LCD屏幕 分辨率1024*600
lcd.init("hx8282",
{port = port,pin_pwr = bl, pin_rst = pin_reset,
direction = 0,w = 1024,h = 600,xoffset = 0,yoffset = 0})
-- -- Air8101开发板配套LCD屏幕 分辨率720*1280
-- lcd.init("nv3052c",
-- {port = port,pin_pwr = bl, pin_rst = pin_reset,
-- direction = 0,w = 720,h = 1280,xoffset = 0,yoffset = 0})
-- Air8101开发板配套LCD屏幕 分辨率480*854
-- lcd.init("st7701sn",
-- {port = port,pin_pwr = bl, pin_rst = pin_reset,
-- direction = 0,w = 480,h = 854,xoffset = 0,yoffset = 0})
------------------------------------------------------------------------------
-- 如果显示颜色相反,请解开下面一行的注释,关闭反色
-- lcd.invoff()
5.3.3 启用上传服务器功能
将这一部分代码中上传服务器部分的代码注释取消,需要注意的是,只有联网后才能将图像上传到服务器。
-- 创建zbuff
local rawbuff, err = zbuff.create(200 * 1024, 0, zbuff.HEAP_PSRAM)
-- 初始化摄像头并处理拍照和上传图片的操作
sys.taskInit(function()
-- -- 开启缓冲区, 刷屏速度会加快, 但也消耗2倍屏幕分辨率的内存
-- lcd.setupBuff(nil, true) -- 使用sys内存, 只需要选一种
-- lcd.autoFlush(false)
if rawbuff == nil then
while true do
sys.wait(1000)
end
log.info("zbuff创建失败", err)
end
camera_init_ok = camera.init(usb_camera_table)
log.info("摄像头初始化", camera_init_ok)
log.info(rtos.meminfo("sys")) -- 打印系统内存信息
log.info(rtos.meminfo("psram")) -- 打印psram内存信息
while 1 do
local result, param = sys.waitUntil("PRESS", 5000)
log.info("PRESS", result, param)
if param == "KEY3" and camera_init_ok == 0 then
camera.start(camera_id) --开始指定的camera
-- camera.capture(camera_id, "/abc.jpg", 1) --camera拍照并保存到指定路径
camera.capture(camera_id, rawbuff, 1)
result, data = sys.waitUntil("capture done", 30000)
log.info(rawbuff:used())
camera.stop(camera_id) --停止指定的camera
-- camera.close(camera_id) --关闭指定的camera,释放相应的IO资源;完全关闭摄像头才用这个
-- rawbuff:resize(200 * 1024)
-- 通过串口发送图片数据
-- uart.tx(uartid, rawbuff) --找个能保存数据的串口工具保存成文件就能在电脑上看了, 格式为JPG
-- 通过WIFI网络上传到服务器查看
-- 上传到upload.air32.cn, 数据访问页面是 https://www.air32.cn/upload/data/
local code, resp = httpplus.request({
url = "http://upload.air32.cn/api/upload/jpg",
method = "POST",
body = rawbuff
})
log.info("http", code)
-- -- 打印内存信息, 调试用
-- log.info("sys", rtos.meminfo())
-- log.info("sys", rtos.meminfo("sys"))
-- log.info("psram", rtos.meminfo("psram"))
elseif param == "KEY3" then
log.info("KEY3按下,但是摄像头初始化失败")
end
end
end)
5.3.4 程序烧录
1. 参考第二、三章节完成硬件与软件环境的搭建。
2. 按照如下步骤进行烧录程序。
3. 按照如下步骤将core文件和demo例程烧录到Air8101模组中。
5.3.5 现象展示
烧录成功后,正常情况下LCD屏幕会实时显示摄像头拍到的画面。
通过打印的日志信息可以看出开发板已经联网,并且获取到的IP地址为“192.168.27.59”
K3按键按下进行拍照,并自动上传到服务器(https://www.air32.cn/upload/data/)
在服务器网页端中找到上传的图片链接,点开即可查看和保存。
K6按键按下后,USB端口切换为下一个。
K5按键按下后,USB端口切换为上一个。
六、总结
本文为用户提供了一个从硬件准备到软件环境搭建,再到代码示例和操作步骤的完整指南,帮助用户快速上手Air8101开发板,实现多摄像头数据采集与处理功能。