Tina5 Linux开发

news2024/10/1 1:20:05

准备开发环境

首先准备一台 Ubuntu 20.04 / Ubuntu 18.04 / Ubuntu 16.04 / Ubuntu 14.04 的虚拟机或实体机,其他系统没有测试过出 BUG 不管。

更新系统,安装基础软件包

sudo apt-get update
sudo apt-get upgrade -y
sudo apt-get install build-essential subversion git libncurses5-dev zlib1g-dev gawk flex bison quilt libssl-dev xsltproc libxml-parser-perl mercurial bzr ecj cvs unzip lsof python3 python2 python3-dev android-tools-mkbootimg python2 libpython3-dev

安装完成后还需要安装 i386 支持,SDK 有几个打包固件使用的程序是 32 位的,如果不安装就等着 Segment fault 吧。

sudo dpkg --add-architecture i386
sudo apt-get update
sudo apt install gcc-multilib 
sudo apt install libc6:i386 libstdc++6:i386 lib32z1

下载 AWOL Tina Linux BSP

注册一个 AWOL 账号

下载 SDK 需要使用 AWOL 的账号,前往 https://bbs.aw-ol.com/ 注册一个就行。其中需要账号等级为 LV2,可以去这个帖子:https://bbs.aw-ol.com/topic/4158/share/1 水四条回复就有 LV2 等级了。

安装 repo 管理器

BSP 使用 repo 下载,首先安装 repo ,这里建议使用国内镜像源安装

mkdir -p ~/.bin
PATH="${HOME}/.bin:${PATH}"
curl https://mirrors.bfsu.edu.cn/git/git-repo > ~/.bin/repo
chmod a+rx ~/.bin/repo

请注意这里使用的是临时安装,安装完成后重启终端就没有了,需要再次运行下面的命令才能使用,如何永久安装请自行百度。

PATH="${HOME}/.bin:${PATH}"

安装使用 repo 的过程中会遇到各种错误,请百度解决。repo 是谷歌开发的,repo 的官方服务器是谷歌的服务器,repo 每次运行时需要检查更新然后卡死,这是很正常的情况,所以在国内需要更换镜像源提高下载速度。将如下内容复制到你的~/.bashrc

echo export REPO_URL='https://mirrors.bfsu.edu.cn/git/git-repo' >> ~/.bashrc
source ~/.bashrc

如果您使用的是 dash、hash、 zsh 等 shell,请参照 shell 的文档配置。环境变量配置一个 REPO_URL 的地址

配置一下 git 身份认证,设置保存 git 账号密码不用每次都输入。

git config --global credential.helper store

新建文件夹保存 SDK

使用 mkdir 命令新建文件夹,保存之后需要拉取的 SDK,然后 cd 进入到刚才新建的文件夹中。

mkdir tina-v853-open
cd tina-v853-open

初始化 repo 仓库

使用 repo init 命令初始化仓库,tina-v853-open 的仓库地址是 https://sdk.aw-ol.com/git_repo/V853Tina_Open/manifest.git 需要执行命令:

repo init -u https://sdk.aw-ol.com/git_repo/V853Tina_Open/manifest.git -b master -m tina-v853-open.xml

拉取 SDK

repo sync

创建开发环境

repo start devboard-v853-tina-for-awol --all

适配 TinyVision 板子

刚才下载到的 SDK 只支持一个板子,售价 1999 的 V853-Vision 开发板,这里要添加自己的板子的适配。

下载支持包:

版本下载地址
1.0https://github.com/YuzukiTsuru/YuzukiTsuru.GitHub.io/releases/download/2024-01-21-20240121/tina-bsp-tinyvision.tar.gz
1.1https://github.com/YuzukiHD/TinyVision/releases/download/tina.0.0.1/tina-bsp-tinyvision.tar.gz
1.2https://github.com/YuzukiHD/TinyVision/releases/download/tina.0.0.2/tina-bsp-tinyvision.tar.gz

或者可以在:https://github.com/YuzukiHD/TinyVision/tree/main/tina 下载到文件,不过这部分没预先下载软件包到 dl 文件夹所以编译的时候需要手动下载。

放到 SDK 的主目录下

在这里插入图片描述

运行解压指令

tar xvf tina-bsp-tinyvision.tar.gz

即可使 Tina SDK 支持 TinyVision 板子

在这里插入图片描述

初始化 SDK 环境

每次开发之前都需要初始化 SDK 环境,命令如下

source build/envsetup.sh

然后按 1 选择 TinyVision

在这里插入图片描述

适配 ISP

Tina SDK 内置一个 libAWispApi 的包,支持在用户层对接 ISP,但是很可惜这个包没有适配 V85x 系列,这里就需要自行适配。其实适配很简单,SDK 已经提供了 lib 只是没提供编译支持。我们需要加上这个支持。

前往 openwrt/package/allwinner/vision/libAWIspApi/machinfo 文件夹中,新建一个文件夹 v851se ,然后新建文件 build.mk 写入如下配置:

ISP_DIR:=isp600

在这里插入图片描述

对于 v851s,v853 也可以这样操作,然后 m menuconfig 勾选上这个包

在这里插入图片描述

开启 camerademo 测试摄像头

进入 m menuconfig 进入如下页面进行配置。

Allwinner  --->
	Vision  --->
		<*> camerademo........................................ camerademo test sensor  --->
			[*]   Enabel vin isp support

编译系统然后烧录系统,运行命令 camerademo ,可以看到是正常拍摄照片的

在这里插入图片描述

适配板载 SD NAND

设备树中的 SDC2 节点修改如下,之后线刷即可

&sdc2 {
	non-removable;
	bus-width = <4>;
	mmc-ddr-3_3v;
	/*mmc-hs200-1_8v;*/
	/*mmc-hs400-1_8v;*/
	no-sdio;
	/delete-property/ no-sd;
	no-mmc;
	ctl-spec-caps = <0x8>;
	cap-mmc-highspeed;
	sunxi-signal-vol-sw-without-pmu;
	sunxi-power-save-mode;
	/*sunxi-dis-signal-vol-sw;*/
	max-frequency = <50000000>;
	/*vmmc-supply = <&reg_dcdc1>;*/
	/*emmc io vol 3.3v*/
	/*vqmmc-supply = <&reg_aldo1>;*/
	/*emmc io vol 1.8v*/
	/*vqmmc-supply = <&reg_eldo1>;*/
	status = "okay";
};

适配 OpenCV

勾选 OpenCV 包

m menuconfig 进入软件包配置,勾选

OpenCV  --->
	<*> opencv....................................................... opencv libs
	[*]   Enabel sunxi vin isp support

OpenCV 适配过程

本部分的操作已经包含在 tina-bsp-tinyvision.tar.gz 中了,已经适配好了,如果不想了解如何适配 OpenCV 可以直接跳过这部分

OpenCV 的多平面视频捕获支持

一般来说,如果不适配 OpenCV 直接开摄像头,会得到一个报错:

[  702.464977] [VIN_ERR]video0 has already stream off
[  702.473357] [VIN_ERR]gc2053_mipi is not used, video0 cannot be close!
VIDEOIO ERROR: V4L2: Unable to capture video memory.VIDEOIO ERROR: V4L: can't open camera by index 0
/dev/video0 does not support memory mapping
Could not open video device.

这是由于 OpenCV 的 V4L2 实现是使用的 V4L2_CAP_VIDEO_CAPTURE 标准,而 sunxi-vin 驱动的 RAW Sensor 平台使用的是 V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE ,导致了默认 OpenCV 的配置错误。

V4L2_CAP_VIDEO_CAPTURE_MPLANEV4L2_BUF_TYPE_VIDEO_CAPTURE是 Video4Linux2(V4L2)框架中用于视频捕获的不同类型和能力标志。

  1. V4L2_CAP_VIDEO_CAPTURE_MPLANE: 这个标志指示设备支持多平面(multi-plane)视频捕获。在多平面捕获中,图像数据可以分解成多个平面(planes),每个平面包含不同的颜色分量或者图像数据的不同部分。这种方式可以提高效率和灵活性,尤其适用于处理涉及多个颜色分量或者多个图像通道的视频流。
  2. V4L2_BUF_TYPE_VIDEO_CAPTURE: 这个类型表示普通的单平面(single-plane)视频捕获。在单平面捕获中,图像数据以单个平面的形式存储,即所有的颜色分量或者图像数据都保存在一个平面中。

因此,区别在于支持的数据格式和存储方式。V4L2_CAP_VIDEO_CAPTURE_MPLANE表示设备支持多平面视频捕获,而V4L2_BUF_TYPE_VIDEO_CAPTURE表示普通的单平面视频捕获。

这里就需要通过检查capability.capabilities中是否包含V4L2_CAP_VIDEO_CAPTURE标志来确定是否支持普通的视频捕获类型。如果支持,那么将type设置为V4L2_BUF_TYPE_VIDEO_CAPTURE

如果不支持普通的视频捕获类型,那么通过检查capability.capabilities中是否包含V4L2_CAP_VIDEO_CAPTURE_MPLANE标志来确定是否支持多平面视频捕获类型。如果支持,那么将type设置为V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE

例如如下修改:

-    form.type                = V4L2_BUF_TYPE_VIDEO_CAPTURE;
-    form.fmt.pix.pixelformat = palette;
-    form.fmt.pix.field       = V4L2_FIELD_ANY;
-    form.fmt.pix.width       = width;
-    form.fmt.pix.height      = height;
+    if (capability.capabilities & V4L2_CAP_VIDEO_CAPTURE) {
+		form.type                = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+		form.fmt.pix.pixelformat = palette;
+		form.fmt.pix.field       = V4L2_FIELD_NONE;
+		form.fmt.pix.width       = width;
+		form.fmt.pix.height      = height;
+	} else if (capability.capabilities & V4L2_CAP_VIDEO_CAPTURE_MPLANE) {
+        form.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
+        form.fmt.pix_mp.width = width;
+        form.fmt.pix_mp.height = height;
+        form.fmt.pix_mp.pixelformat = palette;
+        form.fmt.pix_mp.field = V4L2_FIELD_NONE;
+	}

这段代码是在设置视频捕获的格式和参数时进行了修改。

原来的代码中,直接设置了form.typeV4L2_BUF_TYPE_VIDEO_CAPTURE,表示使用普通的视频捕获类型。然后设置了其他参数,如像素格式(pixelformat)、帧字段(field)、宽度(width)和高度(height)等。

修改后的代码进行了条件判断,根据设备的能力选择合适的视频捕获类型。如果设备支持普通的视频捕获类型(V4L2_CAP_VIDEO_CAPTURE标志被设置),则使用普通的视频捕获类型并设置相应的参数。如果设备支持多平面视频捕获类型(V4L2_CAP_VIDEO_CAPTURE_MPLANE标志被设置),则使用多平面视频捕获类型并设置相应的参数。

对于普通的视频捕获类型,设置的参数与原来的代码一致,只是将帧字段(field)从V4L2_FIELD_ANY改为V4L2_FIELD_NONE,表示不指定特定的帧字段。

对于多平面视频捕获类型,设置了新的参数,如多平面的宽度(pix_mp.width)、高度(pix_mp.height)、像素格式(pix_mp.pixelformat)和帧字段(pix_mp.field)等。

通过这个修改,可以根据设备的能力选择适当的视频捕获类型,并设置相应的参数,以满足不同设备的要求。

OpenCV 的 ISP 支持

OpenCV 默认不支持开启 RAW Sensor,不过现在需要配置为 OpenCV 开启 RAW Sensor 抓图,然后通过 OpenCV 送图到之前适配的 libAWispApi 库进行 ISP 处理。在这里增加一个函数作为 RAW Sensor 抓图的处理。

#ifdef __USE_VIN_ISP__
bool CvCaptureCAM_V4L::RAWSensor()
{
    struct v4l2_control ctrl;
    struct v4l2_queryctrl qc_ctrl;

    memset(&ctrl, 0, sizeof(struct v4l2_control));
    memset(&qc_ctrl, 0, sizeof(struct v4l2_queryctrl));
    ctrl.id = V4L2_CID_SENSOR_TYPE;
    qc_ctrl.id = V4L2_CID_SENSOR_TYPE;

    if (-1 == ioctl (deviceHandle, VIDIOC_QUERYCTRL, &qc_ctrl)){
        fprintf(stderr, "V4L2: %s QUERY V4L2_CID_SENSOR_TYPE failed\n", deviceName.c_str());
        return false;
    }

    if (-1 == ioctl(deviceHandle, VIDIOC_G_CTRL, &ctrl)) {
        fprintf(stderr, "V4L2: %s G_CTRL V4L2_CID_SENSOR_TYPE failed\n", deviceName.c_str());
        return false;
    }

    return ctrl.value == V4L2_SENSOR_TYPE_RAW;
}
#endif

这段代码的功能是检查V4L2摄像头设备的传感器类型是否为RAW格式。它使用了V4L2的ioctl函数来查询和获取传感器类型信息。具体步骤如下:

  1. 定义了两个v4l2_control结构体变量ctrlqc_ctrl,并初始化为零
  2. ctrl.idqc_ctrl.id分别设置为V4L2_CID_SENSOR_TYPE,表示要查询的控制和查询ID
  3. 使用ioctl函数的VIDIOC_QUERYCTRL命令来查询传感器类型的控制信息,并将结果保存在qc_ctrl
  4. 如果查询失败(ioctl返回-1),则输出错误信息并返回false
  5. 使用ioctl函数的VIDIOC_G_CTRL命令来获取传感器类型的当前值,并将结果保存在ctrl
  6. 如果获取失败(ioctl返回-1),则输出错误信息并返回false
  7. 检查ctrl.value是否等于V4L2_SENSOR_TYPE_RAW,如果相等,则返回true,表示传感器类型为RAW格式;否则返回false

并且使用了#ifdef __USE_VIN_ISP__指令。这表示只有在定义了__USE_VIN_ISP__宏时,才会编译和执行这段代码

然后在 OpenCV 的 bool CvCaptureCAM_V4L::streaming(bool startStream) 捕获流函数中添加 ISP 处理

#ifdef __USE_VIN_ISP__
	RawSensor = RAWSensor();

	if (startStream && RawSensor) {
		int VideoIndex = -1;

		sscanf(deviceName.c_str(), "/dev/video%d", &VideoIndex);

		IspPort = CreateAWIspApi();
		IspId = -1;
		IspId = IspPort->ispGetIspId(VideoIndex);
		if (IspId >= 0)
			IspPort->ispStart(IspId);
	} else if (RawSensor && IspId >= 0 && IspPort) {
		IspPort->ispStop(IspId);
		DestroyAWIspApi(IspPort);
		IspPort = NULL;
		IspId = -1;
	}
#endif

这段代码是在条件编译__USE_VIN_ISP__的情况下进行了修改。

  • 首先,它创建了一个RawSensor对象,并检查startStreamRawSensor是否为真。如果满足条件,接下来会解析设备名称字符串,提取出视频索引号。

  • 然后,它调用CreateAWIspApi()函数创建了一个AWIspApi对象,并初始化变量IspId为-1。接着,通过调用ispGetIspId()函数获取指定视频索引号对应的ISP ID,并将其赋值给IspId。如果IspId大于等于0,表示获取到有效的ISP ID,就调用ispStart()函数启动ISP流处理。

  • 如果不满足第一个条件,即startStream为假或者没有RawSensor对象,那么会检查IspId是否大于等于0并且IspPort对象是否存在。如果满足这些条件,说明之前已经启动了ISP流处理,此时会调用ispStop()函数停止ISP流处理,并销毁IspPort对象。最后,将IspPort置为空指针,将IspId重置为-1。

这段代码主要用于控制图像信号处理(ISP)的启动和停止。根据条件的不同,可以选择在开始视频流捕获时启动ISP流处理,或者在停止视频流捕获时停止ISP流处理,以便对视频数据进行处理和增强。

至于其他包括编译脚本的修改,全局变量定义等操作,可以参考补丁文件 openwrt/package/thirdparty/vision/opencv/patches/0004-support-sunxi-vin-camera.patch

使用 OpenCV 捕获摄像头并且输出到屏幕上

快速测试

这个 DEMO 也已经包含在 tina-bsp-tinyvision.tar.gz 中了,可以快速测试这个 DEMO

运行 m menuconfig

OpenCV  --->
	<*> opencv....................................................... opencv libs
	[*]   Enabel sunxi vin isp support
	<*> opencv_camera.............................opencv_camera and display image

源码详解

编写一个程序,使用 OpenCV 捕获摄像头输出并且显示到屏幕上,程序如下:

#include <fcntl.h>
#include <fstream>
#include <iostream>
#include <linux/fb.h>
#include <signal.h>
#include <stdint.h>
#include <sys/ioctl.h>

#include <opencv2/opencv.hpp>

#define DISPLAY_X 240
#define DISPLAY_Y 240

static cv::VideoCapture cap;

struct framebuffer_info {
    uint32_t bits_per_pixel;
    uint32_t xres_virtual;
};

struct framebuffer_info get_framebuffer_info(const char* framebuffer_device_path)
{
    struct framebuffer_info info;
    struct fb_var_screeninfo screen_info;
    int fd = -1;
    fd = open(framebuffer_device_path, O_RDWR);
    if (fd >= 0) {
        if (!ioctl(fd, FBIOGET_VSCREENINFO, &screen_info)) {
            info.xres_virtual = screen_info.xres_virtual;
            info.bits_per_pixel = screen_info.bits_per_pixel;
        }
    }
    return info;
};

/* Signal handler */
static void terminate(int sig_no)
{
    printf("Got signal %d, exiting ...\n", sig_no);
    cap.release();
    exit(1);
}

static void install_sig_handler(void)
{
    signal(SIGBUS, terminate);
    signal(SIGFPE, terminate);
    signal(SIGHUP, terminate);
    signal(SIGILL, terminate);
    signal(SIGINT, terminate);
    signal(SIGIOT, terminate);
    signal(SIGPIPE, terminate);
    signal(SIGQUIT, terminate);
    signal(SIGSEGV, terminate);
    signal(SIGSYS, terminate);
    signal(SIGTERM, terminate);
    signal(SIGTRAP, terminate);
    signal(SIGUSR1, terminate);
    signal(SIGUSR2, terminate);
}

int main(int, char**)
{
    const int frame_width = 480;
    const int frame_height = 480;
    const int frame_rate = 30;

    install_sig_handler();

    framebuffer_info fb_info = get_framebuffer_info("/dev/fb0");

    cap.open(0);

    if (!cap.isOpened()) {
        std::cerr << "Could not open video device." << std::endl;
        return 1;
    }

    std::cout << "Successfully opened video device." << std::endl;
    cap.set(cv::CAP_PROP_FRAME_WIDTH, frame_width);
    cap.set(cv::CAP_PROP_FRAME_HEIGHT, frame_height);
    cap.set(cv::CAP_PROP_FPS, frame_rate);

    std::ofstream ofs("/dev/fb0");

    cv::Mat frame;
    cv::Mat trams_temp_fream;
    cv::Mat yuv_frame;

    while (true) {
        cap >> frame;
        if (frame.depth() != CV_8U) {
            std::cerr << "Not 8 bits per pixel and channel." << std::endl;
        } else if (frame.channels() != 3) {
            std::cerr << "Not 3 channels." << std::endl;
        } else {
            cv::transpose(frame, frame);
            cv::flip(frame, frame, 0);
            cv::resize(frame, frame, cv::Size(DISPLAY_X, DISPLAY_Y));
            int framebuffer_width = fb_info.xres_virtual;
            int framebuffer_depth = fb_info.bits_per_pixel;
            cv::Size2f frame_size = frame.size();
            cv::Mat framebuffer_compat;
            switch (framebuffer_depth) {
            case 16:
                cv::cvtColor(frame, framebuffer_compat, cv::COLOR_BGR2BGR565);
                for (int y = 0; y < frame_size.height; y++) {
                    ofs.seekp(y * framebuffer_width * 2);
                    ofs.write(reinterpret_cast<char*>(framebuffer_compat.ptr(y)), frame_size.width * 2);
                }
                break;
            case 32: {
                std::vector<cv::Mat> split_bgr;
                cv::split(frame, split_bgr);
                split_bgr.push_back(cv::Mat(frame_size, CV_8UC1, cv::Scalar(255)));
                cv::merge(split_bgr, framebuffer_compat);
                for (int y = 0; y < frame_size.height; y++) {
                    ofs.seekp(y * framebuffer_width * 4);
                    ofs.write(reinterpret_cast<char*>(framebuffer_compat.ptr(y)), frame_size.width * 4);
                }
            } break;
            default:
                std::cerr << "Unsupported depth of framebuffer." << std::endl;
            }
        }
    }
}

第一部分,处理 frame_buffer 信息:

// 引入头文件
#include <fcntl.h>
#include <fstream>
#include <iostream>
#include <linux/fb.h>
#include <signal.h>
#include <stdint.h>
#include <sys/ioctl.h>

#include <opencv2/opencv.hpp>

// 定义显示屏宽度和高度
#define DISPLAY_X 240
#define DISPLAY_Y 240

static cv::VideoCapture cap; // 视频流捕获对象

// 帧缓冲信息结构体
struct framebuffer_info {
    uint32_t bits_per_pixel; // 每个像素的位数
    uint32_t xres_virtual; // 虚拟屏幕的宽度
};

// 获取帧缓冲信息函数
struct framebuffer_info get_framebuffer_info(const char* framebuffer_device_path)
{
    struct framebuffer_info info;
    struct fb_var_screeninfo screen_info;
    int fd = -1;

    // 打开帧缓冲设备文件
    fd = open(framebuffer_device_path, O_RDWR);
    if (fd >= 0) {
        // 通过 ioctl 获取屏幕信息
        if (!ioctl(fd, FBIOGET_VSCREENINFO, &screen_info)) {
            info.xres_virtual = screen_info.xres_virtual; // 虚拟屏幕的宽度
            info.bits_per_pixel = screen_info.bits_per_pixel; // 每个像素的位数
        }
    }
    return info;
}

这段代码定义了一些常量、全局变量以及两个函数,并给出了相应的注释说明。具体注释如下:

  • #define DISPLAY_X 240:定义显示屏的宽度为240。
  • #define DISPLAY_Y 240:定义显示屏的高度为240。
  • static cv::VideoCapture cap;:定义一个静态的OpenCV视频流捕获对象,用于捕获视频流。
  • struct framebuffer_info:定义了一个帧缓冲信息的结构体。
    • uint32_t bits_per_pixel:每个像素的位数。
    • uint32_t xres_virtual:虚拟屏幕的宽度。
  • struct framebuffer_info get_framebuffer_info(const char* framebuffer_device_path):获取帧缓冲信息的函数。
    • const char* framebuffer_device_path:帧缓冲设备文件的路径。
    • int fd = -1;:初始化文件描述符为-1。
    • fd = open(framebuffer_device_path, O_RDWR);:打开帧缓冲设备文件,并将文件描述符保存在变量fd中。
    • if (fd >= 0):检查文件是否成功打开。
    • if (!ioctl(fd, FBIOGET_VSCREENINFO, &screen_info)):通过ioctl获取屏幕信息,并将信息保存在变量screen_info中。
      • FBIOGET_VSCREENINFO:控制命令,用于获取屏幕信息。
      • &screen_info:屏幕信息结构体的指针。
    • info.xres_virtual = screen_info.xres_virtual;:将屏幕的虚拟宽度保存在帧缓冲信息结构体的字段xres_virtual中。
    • info.bits_per_pixel = screen_info.bits_per_pixel;:将每个像素的位数保存在帧缓冲信息结构体的字段bits_per_pixel中。
    • return info;:返回帧缓冲信息结构体。

第二部分,注册信号处理函数,用于 ctrl-c 之后关闭摄像头,防止下一次使用摄像头出现摄像头仍被占用的情况。

/* Signal handler */
static void terminate(int sig_no)
{
    printf("Got signal %d, exiting ...\n", sig_no);
    cap.release();
    exit(1);
}

static void install_sig_handler(void)
{
    signal(SIGBUS, terminate); // 当程序访问一个不合法的内存地址时发送的信号
    signal(SIGFPE, terminate); // 浮点异常信号
    signal(SIGHUP, terminate); // 终端断开连接信号
    signal(SIGILL, terminate); // 非法指令信号
    signal(SIGINT, terminate); // 中断进程信号
    signal(SIGIOT, terminate); // IOT 陷阱信号
    signal(SIGPIPE, terminate); // 管道破裂信号
    signal(SIGQUIT, terminate); // 停止进程信号
    signal(SIGSEGV, terminate); // 无效的内存引用信号
    signal(SIGSYS, terminate); // 非法系统调用信号
    signal(SIGTERM, terminate); // 终止进程信号
    signal(SIGTRAP, terminate); // 跟踪/断点陷阱信号
    signal(SIGUSR1, terminate); // 用户定义信号1
    signal(SIGUSR2, terminate); // 用户定义信号2
}

这段代码定义了两个函数,并给出了相应的注释说明。具体注释如下:

  • static void terminate(int sig_no):信号处理函数。
    • int sig_no:接收到的信号编号。
    • printf("Got signal %d, exiting ...\n", sig_no);:打印接收到的信号编号。
    • cap.release();:释放视频流捕获对象。
    • exit(1);:退出程序。
  • static void install_sig_handler(void):安装信号处理函数。
    • signal(SIGBUS, terminate);:为SIGBUS信号安装信号处理函数。
    • signal(SIGFPE, terminate);:为SIGFPE信号安装信号处理函数。
    • signal(SIGHUP, terminate);:为SIGHUP信号安装信号处理函数。
    • signal(SIGILL, terminate);:为SIGILL信号安装信号处理函数。
    • signal(SIGINT, terminate);:为SIGINT信号安装信号处理函数。
    • signal(SIGIOT, terminate);:为SIGIOT信号安装信号处理函数。
    • signal(SIGPIPE, terminate);:为SIGPIPE信号安装信号处理函数。
    • signal(SIGQUIT, terminate);:为SIGQUIT信号安装信号处理函数。
    • signal(SIGSEGV, terminate);:为SIGSEGV信号安装信号处理函数。
    • signal(SIGSYS, terminate);:为SIGSYS信号安装信号处理函数。
    • signal(SIGTERM, terminate);:为SIGTERM信号安装信号处理函数。
    • signal(SIGTRAP, terminate);:为SIGTRAP信号安装信号处理函数。
    • signal(SIGUSR1, terminate);:为SIGUSR1信号安装信号处理函数。
    • signal(SIGUSR2, terminate);:为SIGUSR2信号安装信号处理函数。

这段代码的功能是安装信号处理函数,用于捕获和处理不同类型的信号。当程序接收到指定的信号时,会调用terminate函数进行处理。

具体而言,terminate函数会打印接收到的信号编号,并释放视频流捕获对象cap,然后调用exit(1)退出程序。

install_sig_handler函数用于为多个信号注册同一个信号处理函数terminate,使得当这些信号触发时,都会执行相同的处理逻辑。

第三部分,主函数:

int main(int, char**)
{
    const int frame_width = 480;
    const int frame_height = 480;
    const int frame_rate = 30;

    install_sig_handler(); // 安装信号处理函数

    framebuffer_info fb_info = get_framebuffer_info("/dev/fb0"); // 获取帧缓冲区信息

    cap.open(0); // 打开摄像头

    if (!cap.isOpened()) {
        std::cerr << "Could not open video device." << std::endl;
        return 1;
    }

    std::cout << "Successfully opened video device." << std::endl;
    cap.set(cv::CAP_PROP_FRAME_WIDTH, frame_width);
    cap.set(cv::CAP_PROP_FRAME_HEIGHT, frame_height);
    cap.set(cv::CAP_PROP_FPS, frame_rate);

    std::ofstream ofs("/dev/fb0"); // 打开帧缓冲区

    cv::Mat frame;
    cv::Mat trams_temp_fream;
    cv::Mat yuv_frame;

    while (true) {
        cap >> frame; // 读取一帧图像
        if (frame.depth() != CV_8U) { // 判断是否为8位每通道像素
            std::cerr << "Not 8 bits per pixel and channel." << std::endl;
        } else if (frame.channels() != 3) { // 判断是否为3通道
            std::cerr << "Not 3 channels." << std::endl;
        } else {
            cv::transpose(frame, frame); // 图像转置
            cv::flip(frame, frame, 0); // 图像翻转
            cv::resize(frame, frame, cv::Size(DISPLAY_X, DISPLAY_Y)); // 改变图像大小
            int framebuffer_width = fb_info.xres_virtual;
            int framebuffer_depth = fb_info.bits_per_pixel;
            cv::Size2f frame_size = frame.size();
            cv::Mat framebuffer_compat;
            switch (framebuffer_depth) {
            case 16:
                cv::cvtColor(frame, framebuffer_compat, cv::COLOR_BGR2BGR565);
                for (int y = 0; y < frame_size.height; y++) {
                    ofs.seekp(y * framebuffer_width * 2);
                    ofs.write(reinterpret_cast<char*>(framebuffer_compat.ptr(y)), frame_size.width * 2);
                }
                break;
            case 32: {
                std::vector<cv::Mat> split_bgr;
                cv::split(frame, split_bgr);
                split_bgr.push_back(cv::Mat(frame_size, CV_8UC1, cv::Scalar(255)));
                cv::merge(split_bgr, framebuffer_compat);
                for (int y = 0; y < frame_size.height; y++) {
                    ofs.seekp(y * framebuffer_width * 4);
                    ofs.write(reinterpret_cast<char*>(framebuffer_compat.ptr(y)), frame_size.width * 4);
                }
            } break;
            default:
                std::cerr << "Unsupported depth of framebuffer." << std::endl;
            }
        }
    }

    return 0;
}

这段代码主要实现了从摄像头获取图像并将其显示在帧缓冲区中。具体流程如下:

  • 定义了常量frame_widthframe_heightframe_rate表示图像的宽度、高度和帧率。
  • 调用install_sig_handler()函数安装信号处理函数。
  • 调用get_framebuffer_info("/dev/fb0")函数获取帧缓冲区信息。
  • 调用cap.open(0)打开摄像头,并进行错误检查。
  • 调用cap.set()函数设置摄像头的参数。
  • 调用std::ofstream ofs("/dev/fb0")打开帧缓冲区。
  • 循环读取摄像头的每一帧图像,对其进行转置、翻转、缩放等操作,然后将其写入帧缓冲区中。

如果读取的图像不是8位每通道像素或者不是3通道,则会输出错误信息。如果帧缓冲区的深度不受支持,则也会输出错误信息。

使用 Python3 操作 OpenCV

勾选 OpenCV-Python3 包

m menuconfig 进入软件包配置,勾选

OpenCV  --->
	<*> opencv....................................................... opencv libs
	[*]   Enabel sunxi vin isp support
	[*]   Enabel opencv python3 binding support

在这里插入图片描述

然后编译固件即可,请注意 Python3 编译非常慢,而且需要编译机有16G以上内存,需要耐心等待下。

编写一个 Python 脚本,执行上面的相同操作

import cv2
import numpy as np

DISPLAY_X = 240
DISPLAY_Y = 240

frame_width = 480
frame_height = 480
frame_rate = 30

cap = cv2.VideoCapture(0) # 打开摄像头

if not cap.isOpened():
    print("Could not open video device.")
    exit(1)

print("Successfully opened video device.")
cap.set(cv2.CAP_PROP_FRAME_WIDTH, frame_width)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, frame_height)
cap.set(cv2.CAP_PROP_FPS, frame_rate)

ofs = open("/dev/fb0", "wb") # 打开帧缓冲区

while True:
    ret, frame = cap.read() # 读取一帧图像
    if frame.dtype != np.uint8 or frame.ndim != 3:
        print("Not 8 bits per pixel and channel.")
    elif frame.shape[2] != 3:
        print("Not 3 channels.")
    else:
        frame = cv2.transpose(frame) # 图像转置
        frame = cv2.flip(frame, 0) # 图像翻转
        frame = cv2.resize(frame, (DISPLAY_X, DISPLAY_Y)) # 改变图像大小
        framebuffer_width = DISPLAY_X
		_ = open("/sys/class/graphics/fb0/bits_per_pixel", "r")
		framebuffer_depth = int(_.read()[:2])
		_.close()
        frame_size = frame.shape
        framebuffer_compat = np.zeros(frame_size, dtype=np.uint8)
        if framebuffer_depth == 16:
            framebuffer_compat = cv2.cvtColor(frame, cv2.COLOR_BGR2BGR565)
            for y in range(frame_size[0]):
                ofs.seek(y * framebuffer_width * 2)
                ofs.write(framebuffer_compat[y].tobytes())
        elif framebuffer_depth == 32:
            split_bgr = cv2.split(frame)
            split_bgr.append(np.full((frame_size[0], frame_size[1]), 255, dtype=np.uint8))
            framebuffer_compat = cv2.merge(split_bgr)
            for y in range(frame_size[0]):
                ofs.seek(y * framebuffer_width * 4)
                ofs.write(framebuffer_compat[y].tobytes())
        else:
            print("Unsupported depth of framebuffer.")

cap.release()
ofs.close()

编译系统

初始化 SDK 环境。

source build/envsetup.sh

然后就是编译 SDK 输出固件

mp -j32

如果出现错误,请再次运行

mp -j1 V=s 

以单线程编译解决依赖关系,并且输出全部编译 LOG 方便排查错误。

sidebar_position: 13

线刷固件

修改 U-boot 支持线刷固件

U-Boot 默认配置的是使用 SDC2 也就是 TinyVision 的 SD-NAND 刷写固件。同时也支持使用 SDC0 也就是 TF 卡烧写固件,但是需要手动配置一下 U-Boot。否则会出现如下问题,U-Boot 去初始化不存在的 SD NAND 导致刷不进系统。

在这里插入图片描述

前往文件夹 brandy/brandy-2.0/u-boot-2018/drivers/sunxi_flash/mmc/sdmmc.c

找到第 188 行,将 return sdmmc_init_for_sprite(0, 2); 修改为 return sdmmc_init_for_sprite(0, 0);

在这里插入图片描述

修改后需要重新编译固件。插入空白的 TF 卡,如果不是空白的 TF 卡可能出现芯片不进入烧录模式。

在这里插入图片描述

出现 try card 0 开始下载到 TF 卡内

USB 摄像头输入

有些场景需要使用 USB 摄像头输入,配置如下

开启 USB UVC 支持 m kernel_menuconfig

-> Device Drivers
	-> Multimedia support (MEDIA_SUPPORT [=y])
		-> Media USB Adapters (MEDIA_USB_SUPPORT [=y])
			-> USB Video Class (UVC) (USB_VIDEO_CLASS [=y]) 

之后编译系统,启动,将 USB 切换为 HOST 模式(默认是 Device)

cat /sys/devices/platform/soc/usbc0/usb_host

这里测试使用的是采集卡,输出如下,可以看到 Video0 已经出来了

在这里插入图片描述

测试摄像头,运行 camerademo 拍照,拍摄的照片位于 /tmp 文件夹下

在这里插入图片描述

搭建 RTSP 服务作为网络摄像头

来自:使用tinyvision制作简单的网络摄像机IPC https://bbs.aw-ol.com/topic/5484/share/1

用v4l2读取摄像头图像 然后用硬件编码器把图像编码 最后把编码数据传给rtsp服务器 这样外部就可以直接拉流播放了

提供的系统里有个摄像头测试程序camerademo 能用v4l2读取摄像头图像 sdk里有源码 把源码简单修改一下接口对接

提供的系统里有个硬件编码器测试程序encodertest 能把图像编码成h264数据 直接运行是不能使用的 它的参数解析有问题 需要修改源码的长宽和文件输入输出路径 重新编译才能使用
注意 sdk里面有多个编码器操作例子 只看到一个是接口符合sdk里面的编码器操作接口 其他都是不能用的老接口
能用的encodertest源码路径

openwrt/package/allwinner/multimedia/tina_multimedia_demo/encodertest/mpp_src

rtsp部分是网上找的一个编程实现的简单的rtsp服务器 相当于推流加服务器 外部直接拉流就行

源码附件:使用tinyvision制作简单的网络摄像机IPC附件.zip

下载后有三个文件:包括应用程序,测试工具,个源码工程

在这里插入图片描述

使用预编译的程序测试 RTSP

先用adb把程序传进板子

adb push tinyvisionIpcV1 /root

使用命令添加执行权限

chmod +x tinyvisionIpcV1

使用ifconfig配网

ifconfig eth0 192.168.2.17 broadcast 192.168.2.255 netmask 255.255.255.0 up
ifconfig lo 127.0.0.1 up
route add default gw 192.168.2.1

仅支持一种参数格式 参数为 长 宽 帧率,执行例子

./tinyvisionIpcV1 640 480 30

执行时不加参数时默认参数为 640 480 30

当参数不支持时v4l2会打印出不同的参数 不会自动调整为相近的适合参数

v4l2打印的帧率有时候不对 以程序每秒打印的摄像头帧率为准

验证过的参数:

1920 1080 20
1280 720 30
640 480 30

摄像头读图像帧使用v4l2框架 输出格式是NV21 参数不支持基本上是摄像头不支持导致的

默认操作设备/dev/video0 使用前检查有没这个设备 接了摄像头 摄像头驱动加载成功基本都会有这个设备
可以使用系统自带的camerademo排查操作摄像头有没问题

编码器是用的sdk提供的硬编码 输入NV21输出H264

程序运行时会每秒打印编码帧率 这个帧率不是编码器最大帧率 是工作时的帧率 摄像头帧率低会导致编码器帧率低
可以使用系统自带的encodertest排查编码器有没问题

rtsp是网上找的一个编程实现的简单的rtsp服务器 相当于推流加服务器 外部直接拉流就行

rtsp端口为554 路径为/live
拉流流例子 ip要换成板子的ip
rtsp://192.168.2.17/live

ffmpeg拉流使用方法

在pc上解压ffmpeg压缩包 用cmd进入ffmpeg bin目录执行命令 记得换ip

ffplay.exe -rtsp_transport tcp  rtsp://192.168.2.17/live

参数-rtsp_transport tcp的意思是以tcp的方式建立rtsp链接 不写默认是udp 用tcp可以减少丢包花屏情况

自行编译

工程使用cmake构建

需要安装cmake

apt-get install cmake

需要修改CMakeLists.txt里指定的编译器路径和头文件库文件路径

然后在CMakeLists.txt所在路径执行一次命令

cmake .

产生makefile 然后执行

make

注意 sdk的gcc使用时要求导出变量STAGING_DIR

export STAGING_DIR="/root/tina-v853-open/out/v851se/tinyvision/openwrt/staging_dir/"

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2066013.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

保护网站安全,了解常见的几种网络攻击

随着互联网技术的迅猛发展&#xff0c;网站作为信息展示和交互的重要平台&#xff0c;面临着日益复杂的网络攻击威胁。从简单的口令入侵到复杂的分布式拒绝服务&#xff08;DDoS&#xff09;攻击&#xff0c;网络攻击手段层出不穷&#xff0c;给网站所有者带来了巨大的挑战。今…

sqlilabs less21-25关手工注入

第21关 一.登录页面 二 .Burp Suite 抓包&#xff0c;进入重放器 三.查询数据库 先进行编码 )and updatexml(1,concat(1,database()),1)# 四.查表&#xff0c;先进行编码 )and updatexml(1,concat(1,(select group_concat(table_name) from information_schema.tables where…

王老师 linux c++ 通信架构 笔记(五)编译后生成的 nginx 可执行程序的启动与 nginx 选项

&#xff08;22&#xff09; 启动 nginx &#xff1a; 上网测试一下&#xff1a; 端口号 介绍&#xff1a; &#xff08;23&#xff09; 因为 nginx 监听知名端口号 80 &#xff0c;http 服务。也可以知名端口号&#xff0c;格式如下&#xff1a; 生产环境下可以设置 ngi…

扬中网站建设创建网站

扬中网站建设&#xff1a;打造企业更强大的数字名片 随着互联网的发展&#xff0c;网站已经成为企业不可或缺的一部分。对于扬中市的企业而言&#xff0c;建设一个专业的网站不仅能提升品牌形象&#xff0c;更能拓展市场&#xff0c;提高竞争力。本文将为您介绍扬中网站建设的重…

【jetson交叉编译(1)】nvidia jetson 系列开发板交叉编译方法,CUDA依赖程序

资源 Toolchain Information jetson-linux jetpack 文章目录 资源1 方案1 qemu-aarch64-static和docker 容器编译jetson2 方案2 模拟器交叉编译器2.1 应对库缺失的情况&#xff0c;进行环境准备2.1.1 模拟器(方案1)2.1.2 在jetson上面进行安装&#xff08;方案2&#xff09; 2…

yolov8行人车辆检测与计数系统

yolov8行人车辆检测与计数系统&#xff08;PythonYOLOv8deepsort车辆追踪深度学习模型清新界面&#xff09; &#xff08;1&#xff09;YOL v8算法实现&#xff0c;模型一键切换更新&#xff1b; &#xff08;2&#xff09;检测图片、视频等图像中的各目标数目&#xff1b; &am…

2024数学建模国赛水奖攻略(也可适用其他比赛)

很多同学私信我&#xff0c;问学校要求参加数模比赛&#xff0c;但对建模和编程不太擅长&#xff0c;又不想浪费时间&#xff0c;该怎么办。今天我想和大家分享一些关于数学建模比赛的经验&#xff0c;特别是如何水奖的攻略。 一、选题技巧 有句话说得好&#xff0c;选择比努力…

彻底解决win7系统文件夹选项高级设置是空白

需求背景 win7系统的文件夹选项-高级设置里面是空白的,效果图如下。 解决方法 1、新建txt文本文档 2、文档内容 复制下面的内容到txt文件中,然后保存。 Windows Registry Editor Version 5.00[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Ad…

第七届强网杯-PWN-【WTOA】

文章目录 参考WebAssemblyWasmtime调试逆向源码exp 参考 WebAssembly实践指南——C和Rust通过wasmtime实现相互调用实例 WebAssembly WebAssembly是一种可移植的二进制指令集格式&#xff0c;其本身与平台无关&#xff0c;类似于Java的class文件字节码。 WebAssembly本来的…

Springsecurity中的Eureka报错:Cannot execute request on any known server

完整报错信息&#xff1a; com.netflix.discovery.shared.transport.TransportException: Cannot execute request on any known server 报错体现&#xff1a; 访问eureka控制面板&#xff1a; 访问测试地址&#xff1a; 控制台报错&#xff1a; 可能的报错原因&#xff…

Python之列表解析和用matplotlib模块绘制图形

列表解析用matplotlib模块绘制成图形导入模块numpy阵列将列表转为阵列arrange函数创建阵列广播 使用matplotilib将阵列绘制成图 列表解析 列表解析式用紧凑的方式来构造列表。 语法&#xff1a; [表达式 for循环 条件] 例如&#xff1a;要得到0-20的偶数列表。需要在0-20中循环…

CyberScraper-2077+simple-one-api:使用大模型爬虫

大模型相关目录 大模型&#xff0c;包括部署微调prompt/Agent应用开发、知识库增强、数据库增强、知识图谱增强、自然语言处理、多模态等大模型应用开发内容 从0起步&#xff0c;扬帆起航。 swift与Internvl下的多模态大模型分布式微调指南&#xff08;附代码和数据&#xff…

IT6113: 高速MIPI DSI 4通道转8通道转接芯片资料

1、产品概述&#xff1a; 芯片将 SOC输出的4通道高速 Mipi DSI 视频转换为8通道半速 DSI 显示器&#xff0c;用于平板电脑、智能手机、笔记本电脑、 VR、智能手表等应用&#xff0c;分割器模式也可用。 2、产品说明 &#xff1a; IT6113 MIPI DSI 视频桥是一个4通道进、8通道出…

2024安卓iOS免签封装源码/解决APP误报毒/可打包和上传APP/自动实现5分钟随机更换签名

源码简介&#xff1a; 2024全新安卓iOS免签封装源码&#xff0c;它可处理apk报毒&#xff0c;解决APP误报毒。不仅可打包APP&#xff0c;也可上传APK。自动实现5分钟随机更换包名和签名系统源码。 解决app误报毒&#xff0c;可打包APP可上传APK&#xff0c;并且自动实现5分钟…

ShardingSphere导致的NPE

1. 项目背景 工程内使用shardingsphere支持分库分表&#xff0c;上层使用的MybatisPlus&#xff0c;有一张表的操作总是报NullPointException。 2. 异常堆栈 ### SQL: INSERT INTO t_tg_message ( update_id, from_id, from_user_name, chat_id, type, data, text, create_…

领夹麦克风哪个品牌音质最好?一文揭秘国内最好的麦克风品牌

在数字化直播与Vlog盛行的今天&#xff0c;无线领夹麦克风凭借其便捷性与高音质&#xff0c;成为了内容创作者们的必备好物。但随着市场的迅速扩张&#xff0c;无线领夹麦克风领域也悄然滋生了一系列乱象。品牌众多&#xff0c;质量却良莠不齐&#xff0c;消费者稍有不慎便可能…

Ollama是什么?安装部署指南

Ollama是什么&#xff1f; 一个本地部署运行大模型的工具&#xff0c;一行命令一键启动&#xff0c;支持linux、windows和mac操作系统的安装。 Ollama支撑哪些大模型&#xff1f; 访问&#xff1a;https://ollama.com/library&#xff0c;列表下的大模型&#xff0c;它都支持…

centos7 redis安装+开机启动(6379+6380)

redis安装 步骤1-下载 下载地址&#xff1a;Index of /releases/ 步骤2-安装 Centos安装Redis-CSDN博客 下载包到 /usr/local/redis解压 tar -zxvf redis-7.0.0.tar.gz切路径 /usr/local/redis/redis-7.0.0编译 make切路径 /usr/local/redis/redis-7.0.0/src安装make instal…

Transformer动画讲解 - Softmax函数

Transformer的Softmax函数 Transformer的Softmax函数&#xff1a; 用于将原始注意力分数转换为输入标记的概率分布。这种分布将较高的注意力权重分配给更相关的标记&#xff0c;并将较低的权重分配给不太相关的标记。Transformers通过Softmax在生成输出时&#xff0c;使用注意…

Find My激光笔|苹果Find My技术与激光笔结合,智能防丢,全球定位

激光笔通常用于指示特定的方向或位置。激光笔在教学领域应用广泛。老师可以利用激光笔在教室中远距离展示教学材料上的内容&#xff0c;。在户外活动中&#xff0c;激光笔也发挥着重要作用&#xff0c;发出求救信号或与其他营地建立联系。激光笔还可以用于娱乐和创意表达。 在…