跳转至

多摄像头使用指南

一、演示功能概述

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表示客户端热点的密码。 设置完ssidpassword的参数后,通过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_dcpin_pwrpin_rst分别表示LCD数据/命令选择引脚、LCD背光引脚(可选)、LCD复位引脚,wh用于调整 LCD屏幕的水平、竖直分辨率,directionxoffsetyoffset用于调整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 启用联网功能

先将这一部分代码的注释取消,然后将ssidpassword的参数修改为自己客户端(手机、路由器等)热点名称和密码即可。

-- 联网函数, 可自行删减
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开发板,实现多摄像头数据采集与处理功能。