基于LVGL的嵌入式智能相机系统

314 阅读5分钟

1. 整体代码结构

1749175299058.png

1749175396922.png

2. 摄像头采集模块

camera_100ask_dev.c/.h —— 摄像头采集模块

负责摄像头设备的初始化、图像采集、格式转换、缩放、亮度调节、保存图像。

1. 初始化函数

int camera_100ask_dev_init(char * dev)

完成如下操作:

  • 初始化显示模块和获取 LCD 分辨率;
  • 初始化摄像头设备并获取图像格式;
  • 初始化图像格式转换模块;
  • 启动摄像头;
  • 创建采集线程 thread_camera_work

2. 图像采集线程

static void *thread_camera_work(void *args)

该线程持续循环执行图像采集和处理流程:

  1. 采集一帧图像;

  2. 格式转换(如 YUYV → RGB);

  3. 如果图像尺寸大于屏幕则缩放;

  4. 图像可合并到 framebuffer;

  5. 执行命令选项,如:

    • 调整亮度(CAMERA_100ASK_OPT_UPDATE_BRIGHTNESS
    • 拍照保存 BMP 图像(CAMERA_100ASK_OPT_TAKE_PHOTOS

可调参数与接口

  • camera_100ask_dev_set_brightness(int) 设置亮度
  • camera_100ask_dev_set_opt(enum) 设置当前操作(拍照、调亮度等)
  • camera_100ask_dev_get_video_buf_cur() 获取当前图像缓冲区指针

为啥需要camera_100ask_dev.c/.h?

采集、转换、缩放、合并显示的代码已经实现,为啥还要camera_100ask_dev.c/.h?

功能角度来看,camera_100ask_dev.c/.h 并不是必需的。但它的价值主要体现在 结构封装、接口抽象和线程管理 三个层面:

1. 模块化封装(让代码更清晰)

camera_100ask_dev 提供了一个干净的接口层,把以下逻辑封装起来:

  • 设备初始化(摄像头 + 显示 + 转换器)
  • 图像处理线程(采集 + 转换 + 缩放 + 保存)
  • 操作选项(拍照、调亮度)

➡️ 它把“工作逻辑”和“UI逻辑”分开,减少耦合。UI层不需要直接操作 V4L2,只要调用: camera_100ask_dev_set_opt(CAMERA_100ASK_OPT_TAKE_PHOTOS);

2. 统一线程管理(采集线程集中管理)

这个文件自己启动了一个线程 thread_camera_work,并负责:

  • 持续采集图像
  • 根据 UI 指令触发亮度调节、拍照、保存等操作
  • 控制帧缓冲输出(注释中的 framebuffer 合成可以打开)

➡️ 避免 UI 线程做耗时操作,保持系统响应流畅。

3. 状态管理 & 全局控制变量

提供全局状态变量(例如亮度、当前操作),并保证通过 pthread_mutex 进行同步,避免并发冲突。

1749176552358.png

3. 用户界面模块(LVGL)

camera_100ask_ui.c —— 用户界面模块(LVGL)

负责显示界面、用户操作处理,包括拍照按钮、亮度滑动条、图片浏览器

1. UI组件

  • 启动动画 lv_100ask_boot_animation
  • 中央摄像头图像区域
  • 拍照按钮(调用 camera_100ask_dev_set_opt(TAKE_PHOTOS)
  • 设置按钮 + 亮度滑块
  • 本地 BMP 图像浏览器(左右切换)

2. 核心事件处理

  • btn_capture_event_handler:拍照命令下发
  • btn_setting_event_handler:亮度滑块显示/隐藏
  • slider_setting_event_cb:亮度值变化触发设置
  • camera_work_timer:定时刷新摄像头图像到 UI
  • btn_open_photo_browser_event_handler:打开图片浏览界面
  • btn_photo_browser_event_handler:切换上一张/下一张图片

1749175973670.png

4. 面试问题总结

🧠 一、V4L2 图像采集与处理(底层)

  1. V4L2 数据采集流程中,常见的四个 ioctl 调用分别是什么?作用是什么?
  2. V4L2 中常见的图像格式有哪些?你如何处理 YUYV 格式图像?
  3. 如何通过 V4L2 设置摄像头的亮度?用到哪些 ioctl 命令?
  4. 你是如何判断当前摄像头支持的像素格式和分辨率的?
  5. 在图像采集线程中,如何判断是否采集成功?失败了如何处理?
  6. 在 camera_100ask_dev.c 中,为什么需要图像格式转换?调用了哪个模块?
  7. 缩放图像时如何保持宽高比?怎么计算缩放后的分辨率?
  8. 为什么缩放后的图像需要重新申请内存?这块内存是在哪里释放的?
  9. 解释 camera_100ask_dev 中 thread_camera_work 的主流程和用途?
  10. 解释 VideoBuf、ConvertBuf、ZoomBuf、FrameBuf 四个缓冲区的用途和关系。

🧠 二、LVGL UI 与交互逻辑

  1. 如何用 LVGL 创建一个圆形按钮?有哪些必要的 style 设置?
  2. 如何将摄像头采集到的图像显示到 LVGL 的 lv_img 组件中?
  3. 解释 camera_work_timer 是如何触发 UI 刷新的?数据从哪来?
  4. LVGL 中如何处理滑块(slider)事件?滑动时如何实时更新文本标签?
  5. 如何使用 LVGL 构建图片浏览器?切换上一张/下一张时如何实现逻辑跳转?
  6. 为什么要用 pthread_mutex_lock 包围图像读取/显示操作?
  7. 如果图像采集和 UI 更新不同步,会出现什么问题?如何解决?
  8. LVGL 中图片显示为何可能出现“颜色错乱”?和 LV_COLOR_DEPTH 有什么关系?
  9. 你是否使用了 LVGL 的动画系统?举一个 UI 动画的应用例子。
  10. UI 初始化顺序是什么?哪些组件必须先创建?

🧠 三、图像保存与 BMP 文件操作

  1. BMP 文件格式的头部结构是什么?图像数据在文件中是如何排布的?
  2. convert_to_bmp_file.c 中如何将 RGB 数据写入文件?注意了哪些对齐或反向问题?
  3. BMP 保存函数中如何命名图像文件?文件名包含哪些信息?
  4. 如何实现“拍照”功能?在哪个线程里做了保存操作?
  5. 你如何避免频繁拍照时出现保存冲突或丢帧?

🧠 四、系统结构、模块封装与线程逻辑

  1. camera_100ask_dev.c 是如何解耦 UI 和采集逻辑的?好处是什么?
  2. 列出 camera_100ask_dev 提供的外部 API,它们的职责分别是什么?
  3. 你是如何控制线程退出、重启或资源释放的?目前设计是否支持热插拔摄像头?
  4. 如果系统需要支持多个摄像头设备,你会如何扩展 camera_100ask_dev
  5. 整个系统模块之间的调用关系是怎样的?请画一个简图或描述数据流动路径。