1. V4L2
v4l2(Video for Linux 2)는 리눅스 운영체제에서 비디오 장치를 제어하기 위한 커널 인터페이스이다. 주로 카메라, TV 수신기 등과 상호작용할때 사용된다. V4L2는 다양한 장치화 호환되며 비디오 스트리밍뿐만 아니라 장치 제어, 컨트롤 조정, 데이터 캡쳐 등을 가능하게 한다.
v4l2의 주요 개념을 아래에 서술하겠다.
- 디바이스 파일
- 비디오 장치는 일반적으로 리눅스에서 /dev/videoX 형식으로 연결된다. X는 장치의 번호이다.
- 해당 파일에 접근하여 프로그램에서 카메라와 통신하고 데이터를 캡쳐하거나 설정을 변경할 수 있다.
- IOCTL (Input/Output Control)
- v4l2는 IOCTL 시스템 호출을 통해 장치에 명령을 전달하고 데이터를 가져온다. IOCTL 호출을 통해 장치 설정(해상도, 포맷 등) 및 제어(밝기, 대비)를 할 수 있다.
- 포맷 설정
- 비디오 장치는 다양한 포맷을 지원하고 v4l2는 장치에 맞는 포맷을 설정할 수 있다. 널리 사용되는 포맷에는 MJPEG, YUV, RGB 등이 있다. 지원하는 포맷은 v4l2-ctl 명령어를 통해 확인이 가능하다.
- 장치에서 사용할 포맷을 지정하기 위해서는 V4L2_PIX_FMT_YUYV와 같은 변수가 사용된다.
- 버퍼 스트리밍
- v4l2는 비디오 프레임을 메모리 버퍼에 저장하고, 해당 버퍼에서 프레임을 가져오는 방식으로 동작한다. 버퍼링 방식은 MMAP(Memory Mapping), USERPTR(User Pointer) 방식이 있다.
- 비디오 캡쳐
- v4l2를 사용하여 프레임을 캡쳐하려면, 장치 파일에 접근하여 열고, IOCTL을 통해 설정한 후, 메모리 버퍼를 요청하여 데이터를 얻어온다. 얻어온 데이터는 cv::Mat과 같은 형식으로 변환하여 시각화 하거나 처리를 하는데에 사용이 가능하다.
- 비디오 장치 제어
- v4l2는 밝기, 대비 등과 같은 카메라의 다양한 설정을 조절할 수 있는 API를 제공한다. 장치별로 설정을 지원하는 정도는 다르다.
v4l2는 cli 명령어를 지원한다. 위에서 언급한 v4l2-ctl을 사용하여 장치, 지원하는 설정 등을 확인할 수 있고, 설정을 변경하는 것도 가능하다. v4l2-ctl을 설치하는 방법은 아래와 같다.
$ sudo apt-get install v4l2-ctl
아래에 예시 명령어를 몇가지 설명하겠다.
- 장치 목록 확인 - 연결된 비디오 장치 목록을 표시한다.
$ v4l2-ctl --list-devices
- 지원하는 포맷 목록 확인 - 장치가 지원하는 해상도, 포맷을 표시한다.
$ v4l2-ctl --list-formats-ext
- 프레임 캡쳐 - 장치에서 한 프레임을 캡쳐하여 파일로 저장한다. (--device : 장치, --stream-to : 파일명)
$ v4l2-ctl --device=/dev/video0 --stream-mmap --stream-count=1 --stream-to=frame.raw
- 해상도 및 포맷 설정 - 장치의 해상도와 포맷을 설정한다. (video=width,height= : 해상도, pixelformat= : 포맷)
$ v4l2-ctl --set-fmt-video=width=1280,height=720,pixelformat=MJPG
2. V4L2를 활용한 C++ GUI 프로그램 개발
v4l2를 사용하여 리눅스에서 사용 가능한 guvcview와 유사한 프로그램을 개발해 보았다.
우선 프로젝트 레포지토리는 다음과 같다.
https://github.com/mjlee111/v4l2_gui.git
GitHub - mjlee111/v4l2_gui: C++ GUI example for v4l2 device control. Inspired by guvcview.
C++ GUI example for v4l2 device control. Inspired by guvcview. - mjlee111/v4l2_gui
github.com
해당 레포지토리 README에 빌드 방법과 실행화면 등이 잘 설명해 두었다.
해당 프로젝트의 전체적인 구조는 v4l2를 사용하여 사용 가능한 장치와 각각의 장치에서 설정할 수 있는 옵션을 불러와 GUI에 출력하고, 다른 thread에서 streaming을 처리한다. 처리한 이미지 버퍼는 cv::Mat m_image public 변수에 저장되어 다른 객체에서 데이터 참조가 가능하도록 하였다. QTimer를 활용해 m_image를 GUI에 출력한다.
소스 코드 중 중요한 부분을 설명하겠다.
#include <iostream>
#include <vector>
#include <string>
#include <linux/videodev2.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <cstring>
#include <regex>
#include <dirent.h>
#include <thread>
#include <sys/mman.h>
#include <atomic>
#include <opencv2/opencv.hpp>
#include "debug.h"
include한 헤더는 다음과 같다. v4l2를 사용하여 이미지를 받기 위한 헤더와 이미지를 cv::Mat 형태로 사용하기 위해서 OpenCV를 include해 주었다.
struct deviceData
{
std::string path;
std::string device_name;
};
struct ResolutionInfo
{
std::pair<int, int> resolution;
std::vector<float> fps;
};
struct m_deviceInfo
{
std::string device_name;
std::string driver;
std::string bus_info;
std::vector<std::string> formats;
std::vector<ResolutionInfo> resolution_info;
};
struct m_deviceConfig
{
std::string path;
std::string device_name;
std::string format;
std::pair<int, int> resolution;
float fps;
};
v4l2로부터 디바이스 정보와 설정 등을 처리하기 위한 구조체이다.
deviceData : v4l2를 사용하여 현재 사용 가능한 장치의 목록을 받아오기 위한 구조체
ResolutionInfo : 해상도별로 지원하는 fps를 처리하기 위한 구조체
m_deviceInfo : 장치에서 지원하는 설정을 처리하기 위한 구조체
m_deviceConfig : 장치를 열 때 사용할 옵션들을 정의한 구조체
class usb_cam
{
public:
usb_cam();
~usb_cam();
std::vector<deviceData> find_device();
m_deviceInfo get_device_info(const std::string& devicePath);
void start_stream(const m_deviceConfig& config);
void stop_stream();
int set_control(int control_id, int value);
int get_control(int control_id);
bool query_control(int control_id, v4l2_queryctrl& queryctl);
void reset_controls_to_default();
cv::Mat m_image;
std::atomic<bool> streaming;
private:
std::vector<void*> buffers;
std::vector<size_t> buffer_lengths;
std::thread stream_thread;
int m_fd;
int xioctl(int fd, int request, void* arg);
std::string get_control_name(int control_id);
};
public 함수
- std::vector<deviceData> find_device() : 사용 가능한 장치의 이름과 경로를 리턴하는 함수
- m_deivceInfo get_device_info(const std::string& devicePath) : 장치의 경로를 인자로 받아 장치의 정보와 장치에서 사용 가능한 해상도, fps, 포맷 리턴
- void start_stream(const m_deviceConfig& config) : 장치의 설정값을 인자로 받아 장치 streaming 시작
- void stop_stream() : 현재 객체에서 streaming중인 장치 중지
- int set_control(int control_id, int value) : v4l2 장치 설정 변경
- int get_control(int control_id) : v4l2 장치 현재 설정값 리턴
- bool query_control(int control_id, v4l2_queryctrl& queryctrl) : v4l2 장치에서 설정이 사용 가능한지 확인 및 설정
- void reset_controls_to_default() : 기본 설정으로 초기화
public 변수
- cv::Mat m_image : 이미지 저장
- std::atomic<bool> streaming : streaming 여부
private 함수
- int xioctl(int fd, int request, void* arg) : 시스템 호출 수행, 인터럽트시 재시도
- std::string get_control_name(int control_id) : control_id가 어떤 설정을 하는 것인지 리턴
private 변수
- std::vector<void*> buffers : 버퍼 저장
- std::vector<size_t> buffer_lengths : 버퍼 길이
- std::thread stream_thread : streaming 쓰레드
- int m_fd : v4l2 값
GUI에는 장치 선택, 해상도, 포맷, fps 선택 탭과 stream 버튼이 있다. 또한 Image Control, Device Control 탭을 사용해서 다양한 설정을 조절할 수 있다.
현재 위 프로젝트를 활용해서 ROS2 환경에서 image_recognition이라는 프로젝트를 추가로 진행 중이다.
2024.10.10 - [ROS/ROS2] - [ROS2] image_recognition 프로젝트 001 - usb_camera 패키지
[ROS2] image_recognition 프로젝트 001 - usb_camera 패키지
1. 서론ROS2를 사용하여 다양한 이미지 처리를 할 수 있는 package stack을 개발하고, 블로그에 개발 일지를 작성하고자 한다. 다양한 기능을 지원하고 CI/CD를 적용하여 비전을 사용하는 다양한 개발
menggu1234.tistory.com
'C, C++ > QT' 카테고리의 다른 글
[QT] 003 : QT c++ 사용법 및 UI 구성 (3) | 2024.09.26 |
---|---|
[QT] 002 : QT 설치 (1) | 2024.09.25 |
[QT] 001 : QT란? (7) | 2024.09.25 |