Compare commits

4 Commits

Author SHA1 Message Date
zhangpeng
57d3bc4912 实现opencv 2025-06-26 18:36:25 +08:00
zhangpeng
62d49d4dc8 实现opencv 2025-06-26 18:32:53 +08:00
zhangpeng
2a4d3516ca QT编译Makefile 2025-06-26 17:27:35 +08:00
zhangpeng
947e52854d 采集彩色相机,目前卡顿加400ms延迟 2025-06-25 18:23:28 +08:00
14 changed files with 916 additions and 48 deletions

View File

@@ -2,7 +2,7 @@ PROJECT_NAME = AsynchronousGrabQt
PROJECT_DIR = ../.. PROJECT_DIR = ../..
EXAMPLES_DIR = $(PROJECT_DIR)/../.. EXAMPLES_DIR = $(PROJECT_DIR)/../..
VIMBASDK_DIR = $(EXAMPLES_DIR)/../.. VIMBASDK_DIR = $(EXAMPLES_DIR)/../..
MAKE_INCLUDE_DIR = $(CURDIR)/$(EXAMPLES_DIR)/Build/Make MAKE_INCLUDE_DIR = $(CURDIR)/$(EXAMPLES_DIR)/Build/Make
include $(MAKE_INCLUDE_DIR)/Common.mk include $(MAKE_INCLUDE_DIR)/Common.mk
@@ -13,47 +13,55 @@ BIN_DIR = binary/$(CONFIG_DIR)
OBJ_DIR = object/$(CONFIG_DIR) OBJ_DIR = object/$(CONFIG_DIR)
BIN_PATH = $(BIN_DIR)/$(BIN_FILE) BIN_PATH = $(BIN_DIR)/$(BIN_FILE)
# Qt 工具路径
MOC = /usr/lib/aarch64-linux-gnu/qt5/bin/moc
UIC = /usr/lib/aarch64-linux-gnu/qt5/bin/uic
RCC = /usr/lib/aarch64-linux-gnu/qt5/bin/rcc
# 使用 pkg-config 获取 Qt 编译标志
QT_CFLAGS := $(shell pkg-config --cflags Qt5Core Qt5Gui Qt5Widgets)
QT_LIBS := $(shell pkg-config --libs Qt5Core Qt5Gui Qt5Widgets)
all: $(BIN_PATH) all: $(BIN_PATH)
include $(MAKE_INCLUDE_DIR)/VimbaCPP.mk include $(MAKE_INCLUDE_DIR)/VimbaCPP.mk
include $(MAKE_INCLUDE_DIR)/VimbaImageTransform.mk include $(MAKE_INCLUDE_DIR)/VimbaImageTransform.mk
include $(MAKE_INCLUDE_DIR)/Qt.mk
SOURCE_DIR = $(PROJECT_DIR)/Source SOURCE_DIR = $(PROJECT_DIR)/Source
INCLUDE_DIRS = -I$(SOURCE_DIR) \ INCLUDE_DIRS = -I$(SOURCE_DIR) \
-I$(EXAMPLES_DIR) \ -I$(EXAMPLES_DIR) \
-I$(OBJ_DIR) -I$(OBJ_DIR) \
-I/usr/include/aarch64-linux-gnu/qt5 \
-I/usr/include/aarch64-linux-gnu/qt5/QtCore \
-I/usr/include/aarch64-linux-gnu/qt5/QtGui \
-I/usr/include/aarch64-linux-gnu/qt5/QtWidgets
LIBS = $(VIMBACPP_LIBS) \ LIBS = $(VIMBACPP_LIBS) \
$(VIMBAIMAGETRANSFORM_LIBS) \ $(VIMBAIMAGETRANSFORM_LIBS) \
$(QTCORE_LIBS) \ $(QT_LIBS)
$(QTGUI_LIBS)
DEFINES = DEFINES =
CFLAGS = $(COMMON_CFLAGS) \ CFLAGS = $(COMMON_CFLAGS) \
$(VIMBACPP_CFLAGS) \ $(VIMBACPP_CFLAGS) \
$(VIMBAIMAGETRANSFORM_CFLAGS) \ $(VIMBAIMAGETRANSFORM_CFLAGS) \
$(QTCORE_CFLAGS) \ $(QT_CFLAGS)
$(QTGUI_CFLAGS)
OBJ_FILES = $(OBJ_DIR)/ApiController.o \ OBJ_FILES = $(OBJ_DIR)/ApiController.o \
$(OBJ_DIR)/AsynchronousGrab.o \ $(OBJ_DIR)/AsynchronousGrab.o \
$(OBJ_DIR)/CameraObserver.o \ $(OBJ_DIR)/CameraObserver.o \
$(OBJ_DIR)/FrameObserver.o \ $(OBJ_DIR)/FrameObserver.o \
$(OBJ_DIR)/main.o \ $(OBJ_DIR)/main.o \
$(OBJ_DIR)/moc_AsynchronousGrab.o \ $(OBJ_DIR)/moc_AsynchronousGrab.o \
$(OBJ_DIR)/moc_CameraObserver.o \ $(OBJ_DIR)/moc_CameraObserver.o \
$(OBJ_DIR)/moc_FrameObserver.o \ $(OBJ_DIR)/moc_FrameObserver.o \
$(OBJ_DIR)/qrc_AsynchronousGrab.o $(OBJ_DIR)/qrc_AsynchronousGrab.o
GEN_HEADERS = $(OBJ_DIR)/ui_AsynchronousGrab.h GEN_HEADERS = $(OBJ_DIR)/ui_AsynchronousGrab.h
DEPENDENCIES = VimbaCPP \ DEPENDENCIES = VimbaCPP \
VimbaImageTransform \ VimbaImageTransform
QtCore \
QtGui
$(OBJ_DIR)/moc_%.cpp: $(SOURCE_DIR)/%.h $(OBJ_DIR) $(OBJ_DIR)/moc_%.cpp: $(SOURCE_DIR)/%.h $(OBJ_DIR)
$(MOC) -o $@ $< $(MOC) -o $@ $<
@@ -81,4 +89,6 @@ $(OBJ_DIR):
$(MKDIR) -p $(OBJ_DIR) $(MKDIR) -p $(OBJ_DIR)
$(BIN_DIR): $(BIN_DIR):
$(MKDIR) -p $(BIN_DIR) $(MKDIR) -p $(BIN_DIR)
.PHONY: all clean

View File

@@ -0,0 +1,94 @@
PROJECT_NAME = AsynchronousGrabQt
PROJECT_DIR = ../..
EXAMPLES_DIR = $(PROJECT_DIR)/../..
VIMBASDK_DIR = $(EXAMPLES_DIR)/../..
MAKE_INCLUDE_DIR = $(CURDIR)/$(EXAMPLES_DIR)/Build/Make
include $(MAKE_INCLUDE_DIR)/Common.mk
CONFIG_DIR = $(ARCH)_$(WORDSIZE)bit
BIN_FILE = $(PROJECT_NAME)
BIN_DIR = binary/$(CONFIG_DIR)
OBJ_DIR = object/$(CONFIG_DIR)
BIN_PATH = $(BIN_DIR)/$(BIN_FILE)
# Qt 工具路径
MOC = /usr/lib/aarch64-linux-gnu/qt5/bin/moc
UIC = /usr/lib/aarch64-linux-gnu/qt5/bin/uic
RCC = /usr/lib/aarch64-linux-gnu/qt5/bin/rcc
# 使用 pkg-config 获取 Qt 编译标志
QT_CFLAGS := $(shell pkg-config --cflags Qt5Core Qt5Gui Qt5Widgets)
QT_LIBS := $(shell pkg-config --libs Qt5Core Qt5Gui Qt5Widgets)
all: $(BIN_PATH)
include $(MAKE_INCLUDE_DIR)/VimbaCPP.mk
include $(MAKE_INCLUDE_DIR)/VimbaImageTransform.mk
SOURCE_DIR = $(PROJECT_DIR)/Source
INCLUDE_DIRS = -I$(SOURCE_DIR) \
-I$(EXAMPLES_DIR) \
-I$(OBJ_DIR) \
-I/usr/include/aarch64-linux-gnu/qt5 \
-I/usr/include/aarch64-linux-gnu/qt5/QtCore \
-I/usr/include/aarch64-linux-gnu/qt5/QtGui \
-I/usr/include/aarch64-linux-gnu/qt5/QtWidgets
LIBS = $(VIMBACPP_LIBS) \
$(VIMBAIMAGETRANSFORM_LIBS) \
$(QT_LIBS)
DEFINES =
CFLAGS = $(COMMON_CFLAGS) \
$(VIMBACPP_CFLAGS) \
$(VIMBAIMAGETRANSFORM_CFLAGS) \
$(QT_CFLAGS)
OBJ_FILES = $(OBJ_DIR)/ApiController.o \
$(OBJ_DIR)/AsynchronousGrab.o \
$(OBJ_DIR)/CameraObserver.o \
$(OBJ_DIR)/FrameObserver.o \
$(OBJ_DIR)/main.o \
$(OBJ_DIR)/moc_AsynchronousGrab.o \
$(OBJ_DIR)/moc_CameraObserver.o \
$(OBJ_DIR)/moc_FrameObserver.o \
$(OBJ_DIR)/qrc_AsynchronousGrab.o
GEN_HEADERS = $(OBJ_DIR)/ui_AsynchronousGrab.h
DEPENDENCIES = VimbaCPP \
VimbaImageTransform
$(OBJ_DIR)/moc_%.cpp: $(SOURCE_DIR)/%.h $(OBJ_DIR)
$(MOC) -o $@ $<
$(OBJ_DIR)/ui_%.h: $(SOURCE_DIR)/res/%.ui $(OBJ_DIR)
$(UIC) -o $@ $<
$(OBJ_DIR)/qrc_%.cpp: $(SOURCE_DIR)/res/%.qrc $(OBJ_DIR)
$(RCC) -o $@ $<
$(OBJ_DIR)/%.o: $(SOURCE_DIR)/%.cpp $(OBJ_DIR) $(GEN_HEADERS)
$(CXX) -c $(INCLUDE_DIRS) $(DEFINES) $(CFLAGS) -o $@ $<
$(OBJ_DIR)/%.o: $(OBJ_DIR)/%.cpp $(OBJ_DIR) $(GEN_HEADERS)
$(CXX) -c $(INCLUDE_DIRS) $(DEFINES) $(CFLAGS) -o $@ $<
$(BIN_PATH): $(DEPENDENCIES) $(OBJ_FILES) $(BIN_DIR)
$(CXX) $(ARCH_CFLAGS) -o $(BIN_PATH) $(OBJ_FILES) $(LIBS) -Wl,-rpath,'$$ORIGIN'
clean:
$(RM) binary -r -f
$(RM) object -r -f
$(OBJ_DIR):
$(MKDIR) -p $(OBJ_DIR)
$(BIN_DIR):
$(MKDIR) -p $(BIN_DIR)
.PHONY: all clean

View File

@@ -0,0 +1,60 @@
PROJECT_NAME = mako2v4l
PROJECT_DIR = ../..
EXAMPLES_DIR = $(PROJECT_DIR)/../..
VIMBASDK_DIR = $(EXAMPLES_DIR)/../..
MAKE_INCLUDE_DIR = $(CURDIR)/$(EXAMPLES_DIR)/Build/Make
include $(MAKE_INCLUDE_DIR)/Common.mk
CONFIG_DIR = $(ARCH)_$(WORDSIZE)bit
BIN_FILE = $(PROJECT_NAME)
BIN_DIR = binary/$(CONFIG_DIR)
OBJ_DIR = object/$(CONFIG_DIR)
BIN_PATH = $(BIN_DIR)/$(BIN_FILE)
all: $(BIN_PATH)
include $(MAKE_INCLUDE_DIR)/VimbaCPP.mk
include $(MAKE_INCLUDE_DIR)/VimbaImageTransform.mk
SOURCE_DIR = $(PROJECT_DIR)/Source
INCLUDE_DIRS = -I$(SOURCE_DIR) \
-I$(EXAMPLES_DIR) \
-I$(OBJ_DIR) \
-I$(VIMBASDK_DIR)/VimbaCPP/Include \
-I$(VIMBASDK_DIR)/VimbaImageTransform/Include
LIBS = $(VIMBACPP_LIBS) \
$(VIMBAIMAGETRANSFORM_LIBS) \
-lv4l2
DEFINES =
CFLAGS = $(COMMON_CFLAGS) \
$(VIMBACPP_CFLAGS) \
$(VIMBAIMAGETRANSFORM_CFLAGS)
OBJ_FILES = $(OBJ_DIR)/ApiController.o \
$(OBJ_DIR)/FrameObserver.o \
$(OBJ_DIR)/main.o
DEPENDENCIES = VimbaCPP \
VimbaImageTransform
$(OBJ_DIR)/%.o: $(SOURCE_DIR)/%.cpp $(OBJ_DIR)
$(CXX) -c $(INCLUDE_DIRS) $(DEFINES) $(CFLAGS) -o $@ $<
$(BIN_PATH): $(DEPENDENCIES) $(OBJ_FILES) $(BIN_DIR)
$(CXX) $(ARCH_CFLAGS) -o $(BIN_PATH) $(OBJ_FILES) $(LIBS) -Wl,-rpath,'$ORIGIN'
clean:
$(RM) binary -r -f
$(RM) object -r -f
$(OBJ_DIR):
$(MKDIR) -p $(OBJ_DIR)
$(BIN_DIR):
$(MKDIR) -p $(BIN_DIR)

View File

@@ -0,0 +1,173 @@
#include "ApiController.h"
#include "FrameObserver.h"
#include <sstream>
#include <iostream>
#include <memory>
namespace AVT {
namespace VmbAPI {
namespace Examples {
ApiController::ApiController()
: m_system(VimbaSystem::GetInstance()),
m_nWidth(0),
m_nHeight(0),
m_nPixelFormat(0)
{
}
ApiController::~ApiController()
{
if (m_pCamera) {
StopContinuousImageAcquisition();
}
}
VmbErrorType ApiController::StartUp()
{
return m_system.Startup();
}
void ApiController::ShutDown()
{
m_system.Shutdown();
}
VmbErrorType ApiController::StartContinuousImageAcquisition(const std::string &rStrCameraID)
{
VmbErrorType res = m_system.OpenCameraByID(rStrCameraID.c_str(),
VmbAccessModeFull,
m_pCamera);
if (VmbErrorSuccess != res) {
return res;
}
// Adjust packet size for GigE cameras
FeaturePtr pCommandFeature;
if (VmbErrorSuccess == m_pCamera->GetFeatureByName("GVSPAdjustPacketSize", pCommandFeature)) {
res = pCommandFeature->RunCommand();
if (VmbErrorSuccess == res) {
bool isDone = false;
do {
res = pCommandFeature->IsCommandDone(isDone);
if (VmbErrorSuccess != res) {
break;
}
} while (!isDone);
}
}
// Set camera parameters
res = SetValueIntMod2(m_pCamera, "Width", m_nWidth);
if (VmbErrorSuccess == res) {
res = SetValueIntMod2(m_pCamera, "Height", m_nHeight);
}
if (VmbErrorSuccess == res) {
FeaturePtr pFormatFeature;
res = m_pCamera->GetFeatureByName("PixelFormat", pFormatFeature);
if (VmbErrorSuccess == res) {
res = pFormatFeature->GetValue(m_nPixelFormat);
}
}
// Create frame observer
m_pFrameObserver = IFrameObserverPtr(new FrameObserver(m_pCamera));
// Start acquisition
if (VmbErrorSuccess == res) {
res = m_pCamera->StartContinuousImageAcquisition(3, m_pFrameObserver);
}
if (VmbErrorSuccess != res && m_pCamera) {
m_pCamera->Close();
}
return res;
}
VmbErrorType ApiController::StopContinuousImageAcquisition()
{
if (m_pCamera) {
m_pCamera->StopContinuousImageAcquisition();
return m_pCamera->Close();
}
return VmbErrorSuccess;
}
ApiController::CameraPtrVector ApiController::GetCameraList()
{
CameraPtrVector cameras;
m_system.GetCameras(cameras);
return cameras;
}
ApiController::FramePtr ApiController::GetFrame()
{
return FramePtr();
}
VmbErrorType ApiController::QueueFrame(FramePtr pFrame)
{
if (!m_pCamera) {
return VmbErrorDeviceNotOpen;
}
return m_pCamera->QueueFrame(pFrame);
}
void ApiController::ClearFrameQueue()
{
// No operation needed as frames are directly streamed to V4L2
}
int ApiController::GetWidth() const {
return static_cast<int>(m_nWidth);
}
int ApiController::GetHeight() const {
return static_cast<int>(m_nHeight);
}
VmbPixelFormatType ApiController::GetPixelFormat() const {
return static_cast<VmbPixelFormatType>(m_nPixelFormat);
}
double ApiController::GetAverageLatency() const {
if (m_pFrameObserver) {
FrameObserver* observer = static_cast<FrameObserver*>(m_pFrameObserver.get());
if (observer) {
return observer->GetAverageLatency();
}
}
return 0.0;
}
size_t ApiController::GetFrameCount() const {
if (m_pFrameObserver) {
FrameObserver* observer = static_cast<FrameObserver*>(m_pFrameObserver.get());
if (observer) {
return observer->GetFrameCount();
}
}
return 0;
}
VmbErrorType ApiController::SetValueIntMod2(const AVT::VmbAPI::CameraPtr &camera,
const std::string &featureName,
VmbInt64_t &storage)
{
AVT::VmbAPI::FeaturePtr pFeature;
VmbInt64_t min = 0, max = 0, inc = 0;
VmbErrorType res = camera->GetFeatureByName(featureName.c_str(), pFeature);
if (VmbErrorSuccess == res) res = pFeature->GetRange(min, max);
if (VmbErrorSuccess == res) res = pFeature->GetIncrement(inc);
if (VmbErrorSuccess == res) {
max = max - (max % inc);
if (max % 2 != 0) max -= inc;
res = pFeature->SetValue(max);
storage = max;
}
return res;
}
}}} // namespace AVT::VmbAPI::Examples

View File

@@ -0,0 +1,55 @@
#ifndef APICONTROLLER_H
#define APICONTROLLER_H
#include <string>
#include <VimbaCPP/Include/VimbaCPP.h>
#include <chrono>
namespace AVT {
namespace VmbAPI {
namespace Examples {
class FrameObserver;
class ApiController
{
public:
typedef AVT::VmbAPI::CameraPtrVector CameraPtrVector;
typedef AVT::VmbAPI::FramePtr FramePtr;
ApiController();
~ApiController();
VmbErrorType StartUp();
void ShutDown();
VmbErrorType StartContinuousImageAcquisition(const std::string& cameraID);
VmbErrorType StopContinuousImageAcquisition();
CameraPtrVector GetCameraList();
FramePtr GetFrame();
VmbErrorType QueueFrame(FramePtr pFrame);
void ClearFrameQueue();
int GetWidth() const;
int GetHeight() const;
VmbPixelFormatType GetPixelFormat() const;
// 新增性能统计方法
double GetAverageLatency() const;
size_t GetFrameCount() const;
private:
AVT::VmbAPI::VimbaSystem& m_system;
AVT::VmbAPI::CameraPtr m_pCamera;
AVT::VmbAPI::IFrameObserverPtr m_pFrameObserver;
VmbInt64_t m_nPixelFormat;
VmbInt64_t m_nWidth;
VmbInt64_t m_nHeight;
VmbErrorType SetValueIntMod2(const AVT::VmbAPI::CameraPtr &camera,
const std::string &featureName,
VmbInt64_t &storage);
};
}}} // namespace AVT::VmbAPI::Examples
#endif

View File

@@ -0,0 +1,137 @@
#include "FrameObserver.h"
#include <string.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <chrono>
namespace AVT {
namespace VmbAPI {
namespace Examples {
FrameObserver::FrameObserver(const CameraPtr& pCamera)
: IFrameObserver(pCamera), m_videoFd(-1)
{
if (!SetupVideoDevice()) {
fprintf(stderr, "Failed to setup video device\n");
}
m_lastFrameTime = std::chrono::high_resolution_clock::now();
}
FrameObserver::~FrameObserver()
{
CloseVideoDevice();
}
bool FrameObserver::SetupVideoDevice()
{
m_videoFd = open("/dev/video61", O_RDWR | O_NONBLOCK);
if (m_videoFd < 0) {
perror("Failed to open video device");
return false;
}
memset(&m_vfmt, 0, sizeof(m_vfmt));
m_vfmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
m_vfmt.fmt.pix.width = 640;
m_vfmt.fmt.pix.height = 480;
m_vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
m_vfmt.fmt.pix.field = V4L2_FIELD_NONE;
if (ioctl(m_videoFd, VIDIOC_S_FMT, &m_vfmt) < 0) {
perror("Set video format");
close(m_videoFd);
m_videoFd = -1;
return false;
}
return true;
}
void FrameObserver::CloseVideoDevice()
{
if (m_videoFd >= 0) {
close(m_videoFd);
m_videoFd = -1;
}
}
bool FrameObserver::UpdateVideoFormat(VmbUint32_t width, VmbUint32_t height, VmbPixelFormatType pixelFormat)
{
std::lock_guard<std::mutex> lock(m_deviceMutex);
if (m_vfmt.fmt.pix.width == width &&
m_vfmt.fmt.pix.height == height &&
m_vfmt.fmt.pix.pixelformat == (pixelFormat == VmbPixelFormatMono8 ? V4L2_PIX_FMT_GREY : V4L2_PIX_FMT_YUYV)) {
return true;
}
m_vfmt.fmt.pix.width = width;
m_vfmt.fmt.pix.height = height;
switch (pixelFormat) {
case VmbPixelFormatMono8:
m_vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_GREY;
m_vfmt.fmt.pix.sizeimage = width * height;
break;
case VmbPixelFormatBayerRG8:
m_vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_SRGGB8;
m_vfmt.fmt.pix.sizeimage = width * height;
break;
default:
m_vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
m_vfmt.fmt.pix.sizeimage = width * height * 2;
}
if (ioctl(m_videoFd, VIDIOC_S_FMT, &m_vfmt) < 0) {
perror("Update video format");
return false;
}
return true;
}
void FrameObserver::FrameReceived(const FramePtr pFrame)
{
auto startTime = std::chrono::high_resolution_clock::now();
VmbUchar_t* pBuffer = nullptr;
VmbUint32_t nSize = 0;
VmbUint32_t width = 0, height = 0;
VmbPixelFormatType pixelFormat;
if (pFrame->GetImage(pBuffer) != VmbErrorSuccess ||
pFrame->GetImageSize(nSize) != VmbErrorSuccess ||
pFrame->GetWidth(width) != VmbErrorSuccess ||
pFrame->GetHeight(height) != VmbErrorSuccess ||
pFrame->GetPixelFormat(pixelFormat) != VmbErrorSuccess) {
return;
}
if (!UpdateVideoFormat(width, height, pixelFormat)) {
return;
}
ssize_t written = write(m_videoFd, pBuffer, nSize);
if (written != (ssize_t)nSize) {
perror("Write to video device");
}
auto endTime = std::chrono::high_resolution_clock::now();
double latency = std::chrono::duration<double, std::milli>(endTime - startTime).count();
m_totalLatency.store(m_totalLatency.load() + latency);
m_frameCount++;
m_pCamera->QueueFrame(pFrame);
}
double FrameObserver::GetAverageLatency() const {
return m_frameCount > 0 ? m_totalLatency.load() / m_frameCount : 0.0;
}
size_t FrameObserver::GetFrameCount() const {
return m_frameCount;
}
}}} // namespace AVT::VmbAPI::Examples

View File

@@ -0,0 +1,43 @@
#ifndef FRAMEOBSERVER_H
#define FRAMEOBSERVER_H
#include <linux/videodev2.h>
#include <fcntl.h>
#include <unistd.h>
#include <VimbaCPP/Include/VimbaCPP.h>
#include <mutex>
#include <chrono>
#include <atomic>
namespace AVT {
namespace VmbAPI {
namespace Examples {
class FrameObserver : public IFrameObserver
{
public:
FrameObserver(const CameraPtr& pCamera);
virtual ~FrameObserver();
void FrameReceived(const FramePtr pFrame) override;
double GetAverageLatency() const;
size_t GetFrameCount() const;
private:
bool SetupVideoDevice();
void CloseVideoDevice();
bool UpdateVideoFormat(VmbUint32_t width, VmbUint32_t height, VmbPixelFormatType pixelFormat);
int m_videoFd;
struct v4l2_format m_vfmt;
std::mutex m_deviceMutex;
std::atomic<size_t> m_frameCount{0};
std::atomic<double> m_totalLatency{0.0};
std::chrono::high_resolution_clock::time_point m_lastFrameTime;
};
}}} // namespace AVT::VmbAPI::Examples
#endif

View File

@@ -0,0 +1,52 @@
#include "ApiController.h"
#include <iostream>
#include <cstdlib>
#include <chrono>
#include <thread>
int main(int argc, char* argv[])
{
AVT::VmbAPI::Examples::ApiController controller;
if (VmbErrorSuccess != controller.StartUp()) {
std::cerr << "Failed to start Vimba system" << std::endl;
return EXIT_FAILURE;
}
auto cameras = controller.GetCameraList();
if (cameras.empty()) {
std::cerr << "No cameras found" << std::endl;
controller.ShutDown();
return EXIT_FAILURE;
}
std::string cameraId;
cameras[0]->GetID(cameraId);
std::cout << "Using camera: " << cameraId << std::endl;
if (VmbErrorSuccess != controller.StartContinuousImageAcquisition(cameraId)) {
std::cerr << "Failed to start image acquisition" << std::endl;
controller.ShutDown();
return EXIT_FAILURE;
}
std::cout << "Streaming to V4L2 device. Press Enter to stop..." << std::endl;
// 定期打印性能统计
while (true) {
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "Frames: " << controller.GetFrameCount()
<< ", Avg Latency: " << controller.GetAverageLatency() << "ms"
<< std::endl;
// 检查用户输入
if (std::cin.rdbuf()->in_avail() > 0) {
break;
}
}
controller.StopContinuousImageAcquisition();
controller.ShutDown();
return EXIT_SUCCESS;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/AsynchronousGrabQt">
<file>AsynchronousGrab.png</file>
</qresource>
</RCC>

View File

@@ -0,0 +1,111 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AsynchronousGrabClass</class>
<widget class="QMainWindow" name="AsynchronousGrabClass">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1040</width>
<height>780</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>1040</width>
<height>780</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>1040</width>
<height>780</height>
</size>
</property>
<property name="windowTitle">
<string>AsynchronousGrab (Qt version)</string>
</property>
<property name="windowIcon">
<iconset resource="AsynchronousGrab.qrc">
<normaloff>:/AsynchronousGrabQt/AsynchronousGrab.png</normaloff>:/AsynchronousGrabQt/AsynchronousGrab.png</iconset>
</property>
<widget class="QWidget" name="centralWidget">
<widget class="QListWidget" name="m_ListBoxCameras">
<property name="geometry">
<rect>
<x>0</x>
<y>10</y>
<width>261</width>
<height>491</height>
</rect>
</property>
</widget>
<widget class="QListWidget" name="m_ListLog">
<property name="geometry">
<rect>
<x>0</x>
<y>580</y>
<width>1041</width>
<height>191</height>
</rect>
</property>
</widget>
<widget class="QPushButton" name="m_ButtonStartStop">
<property name="geometry">
<rect>
<x>0</x>
<y>540</y>
<width>261</width>
<height>31</height>
</rect>
</property>
<property name="text">
<string>Start Image Acquisition</string>
</property>
</widget>
<widget class="QLabel" name="m_LabelStream">
<property name="geometry">
<rect>
<x>270</x>
<y>10</y>
<width>771</width>
<height>561</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
</widget>
<widget class="QCheckBox" name="m_ColorProcessingCheckBox">
<property name="geometry">
<rect>
<x>140</x>
<y>510</y>
<width>121</width>
<height>20</height>
</rect>
</property>
<property name="text">
<string>ColorProcessing</string>
</property>
</widget>
</widget>
</widget>
<layoutdefault spacing="6" margin="11"/>
<resources>
<include location="AsynchronousGrab.qrc"/>
</resources>
<connections/>
</ui>

View File

@@ -12,40 +12,50 @@ BIN_FILE = $(PROJECT_NAME)
BIN_DIR = binary/$(CONFIG_DIR) BIN_DIR = binary/$(CONFIG_DIR)
OBJ_DIR = object/$(CONFIG_DIR) OBJ_DIR = object/$(CONFIG_DIR)
BIN_PATH = $(BIN_DIR)/$(BIN_FILE) BIN_PATH = $(BIN_DIR)/$(BIN_FILE)
# Qt 工具路径
MOC = /usr/lib/aarch64-linux-gnu/qt5/bin/moc
UIC = /usr/lib/aarch64-linux-gnu/qt5/bin/uic
RCC = /usr/lib/aarch64-linux-gnu/qt5/bin/rcc
# 使用 pkg-config 获取 Qt 编译标志
QT_CFLAGS := $(shell pkg-config --cflags Qt5Core Qt5Gui Qt5Widgets)
QT_LIBS := $(shell pkg-config --libs Qt5Core Qt5Gui Qt5Widgets)
all: $(BIN_PATH) all: $(BIN_PATH)
include $(MAKE_INCLUDE_DIR)/VimbaCPP.mk include $(MAKE_INCLUDE_DIR)/VimbaCPP.mk
include $(MAKE_INCLUDE_DIR)/VimbaImageTransform.mk include $(MAKE_INCLUDE_DIR)/VimbaImageTransform.mk
include $(MAKE_INCLUDE_DIR)/Qt.mk
SOURCE_DIR = $(PROJECT_DIR)/Source SOURCE_DIR = $(PROJECT_DIR)/Source
INCLUDE_DIRS = -I$(SOURCE_DIR) \ INCLUDE_DIRS = -I$(SOURCE_DIR) \
-I$(EXAMPLES_DIR) \ -I$(EXAMPLES_DIR) \
-I$(OBJ_DIR) -I$(OBJ_DIR) \
-I/usr/include/aarch64-linux-gnu/qt5 \
-I/usr/include/aarch64-linux-gnu/qt5/QtCore \
-I/usr/include/aarch64-linux-gnu/qt5/QtGui \
-I/usr/include/aarch64-linux-gnu/qt5/QtWidgets
OPENCV_CFLAGS = $(shell $(PKGCFG) --cflags opencv)
OPENCV_LIBS = $(shell $(PKGCFG) --libs opencv) OPENCV_CFLAGS = $(shell $(PKGCFG) --cflags opencv4)
OPENCV_LIBS = $(shell $(PKGCFG) --libs opencv4)
LIBS = $(VIMBACPP_LIBS) \ LIBS = $(VIMBACPP_LIBS) \
$(VIMBAIMAGETRANSFORM_LIBS) \ $(VIMBAIMAGETRANSFORM_LIBS) \
$(QTCORE_LIBS) \ $(QT_LIBS) \
$(QTGUI_LIBS) \
$(OPENCV_LIBS) $(OPENCV_LIBS)
DEFINES = DEFINES =
CFLAGS = $(COMMON_CFLAGS) \ CFLAGS = $(COMMON_CFLAGS) \
$(VIMBACPP_CFLAGS) \ $(VIMBACPP_CFLAGS) \
$(VIMBAIMAGETRANSFORM_CFLAGS) \ $(VIMBAIMAGETRANSFORM_CFLAGS) \
$(QTCORE_CFLAGS) \ $(QT_CFLAGS) \
$(QTGUI_CFLAGS) \
$(OPENCV_CFLAGS) $(OPENCV_CFLAGS)
OBJ_FILES = $(OBJ_DIR)/ApiController.o \ OBJ_FILES = $(OBJ_DIR)/ApiController.o \
@@ -62,9 +72,7 @@ OBJ_FILES = $(OBJ_DIR)/ApiController.o \
GEN_HEADERS = $(OBJ_DIR)/ui_AsynchronousOpenCVRecorder.h GEN_HEADERS = $(OBJ_DIR)/ui_AsynchronousOpenCVRecorder.h
DEPENDENCIES = VimbaCPP \ DEPENDENCIES = VimbaCPP \
VimbaImageTransform \ VimbaImageTransform
QtCore \
QtGui
$(OBJ_DIR)/moc_%.cpp: $(SOURCE_DIR)/%.h $(OBJ_DIR) $(OBJ_DIR)/moc_%.cpp: $(SOURCE_DIR)/%.h $(OBJ_DIR)
$(MOC) -o $@ $< $(MOC) -o $@ $<

View File

@@ -90,7 +90,18 @@ the use of this software, even if advised of the possibility of such damage.
#include "VmbTransform.h" #include "VmbTransform.h"
#include <VimbaCPP/Include/VimbaCPP.h> #include <VimbaCPP/Include/VimbaCPP.h>
#include <opencv2/videoio.hpp> // 必须包含 videoio 以使用 VideoWriter
#include <opencv2/opencv.hpp>
// 替换原有的 enum 定义
static const int FOURCC_USER_SELECT = -1; // 替代 CV_FOURCC_PROMPT
static const int FOURCC_DEFAULT = cv::VideoWriter::fourcc('I','Y','U','V');
static const int FOURCC_MPEG1 = cv::VideoWriter::fourcc('P','I','M','1');
static const int FOURCC_MJPEG = cv::VideoWriter::fourcc('M','J','P','G');
static const int FOURCC_MPEG42 = cv::VideoWriter::fourcc('M','P','4','2');
static const int FOURCC_MPEG43 = cv::VideoWriter::fourcc('M','P','4','3');
static const int FOURCC_DIVX = cv::VideoWriter::fourcc('D','I','V','X');
static const int FOURCC_X264 = cv::VideoWriter::fourcc('X','2','6','4');
// //
// Base exception // Base exception
// //
@@ -130,17 +141,6 @@ class OpenCVRecorder: public QThread
// Example FOURCC codes that can be used with the OpenCVRecorder // Example FOURCC codes that can be used with the OpenCVRecorder
// //
VmbUint32_t maxQueueElements() const { return 3; } VmbUint32_t maxQueueElements() const { return 3; }
enum
{
FOURCC_USER_SELECT = CV_FOURCC_PROMPT,
FOURCC_DEFAULT = CV_FOURCC_MACRO('I','Y','U','V'),
FOURCC_MPEG1 = CV_FOURCC_MACRO('P','I','M','1'),
FOURCC_MJPEG = CV_FOURCC_MACRO('M','J','P','G'),
FOURCC_MPEG42 = CV_FOURCC_MACRO('M','P','4','2'),
FOURCC_MPEG43 = CV_FOURCC_MACRO('M','P','4','3'),
FOURCC_DIVX = CV_FOURCC_MACRO('D','I','V','X'),
FOURCC_X264 = CV_FOURCC_MACRO('X','2','6','4'),
};
// //
// frame data temporary storage // frame data temporary storage
// //
@@ -305,10 +305,10 @@ class OpenCVRecorder: public QThread
public: public:
OpenCVRecorder(const QString &fileName, VmbFloat_t fps, VmbUint32_t Width, VmbUint32_t Height) OpenCVRecorder(const QString &fileName, VmbFloat_t fps, VmbUint32_t Width, VmbUint32_t Height)
: m_StopThread( false ) : m_StopThread( false )
#ifdef _MSC_VER // codec selection only supported by Windows #ifdef _MSC_VER // Windows 下支持手动选择编码器
, m_VideoWriter(fileName.toStdString(), FOURCC_USER_SELECT, fps, cv::Size(Width,Height),true ) , m_VideoWriter(fileName.toStdString(), FOURCC_USER_SELECT, fps, cv::Size(Width, Height), true)
#else #else
, m_VideoWriter(fileName.toStdString(), FOURCC_X264, fps, cv::Size(Width,Height),true ) , m_VideoWriter(fileName.toStdString(), FOURCC_X264, fps, cv::Size(Width, Height), true)
#endif #endif
, m_ConvertImage( Height, Width, CV_8UC3) , m_ConvertImage( Height, Width, CV_8UC3)
{ {
@@ -398,4 +398,20 @@ public:
} }
}; };
class RTSPStreamer {
public:
RTSPStreamer(const std::string& rtsp_url, int width, int height, int fps);
~RTSPStreamer();
bool pushFrame(const cv::Mat& frame);
bool isConnected() const { return m_connected; }
private:
std::string m_rtspUrl;
AVFormatContext* m_fmtCtx = nullptr;
AVCodecContext* m_codecCtx = nullptr;
AVStream* m_stream = nullptr;
SwsContext* m_swsCtx = nullptr;
std::atomic<bool> m_connected{false};
int m_width, m_height, m_fps;
};
#endif #endif

View File

@@ -0,0 +1,104 @@
RTSPStreamer::RTSPStreamer(const std::string& rtsp_url, int width, int height, int fps)
: m_rtspUrl(rtsp_url), m_width(width), m_height(height), m_fps(fps) {
// 初始化 FFmpeg
avformat_network_init();
// 创建输出上下文 (RTSP)
avformat_alloc_output_context2(&m_fmtCtx, nullptr, "rtsp", rtsp_url.c_str());
if (!m_fmtCtx) {
std::cerr << "Failed to create RTSP output context" << std::endl;
return;
}
// 查找 H.264 编码器
const AVCodec* codec = avcodec_find_encoder(AV_CODEC_ID_H264);
if (!codec) {
std::cerr << "H.264 encoder not found" << std::endl;
return;
}
// 创建编码器上下文
m_codecCtx = avcodec_alloc_context3(codec);
m_codecCtx->width = width;
m_codecCtx->height = height;
m_codecCtx->time_base = {1, fps};
m_codecCtx->pix_fmt = AV_PIX_FMT_YUV420P;
// 打开编码器
if (avcodec_open2(m_codecCtx, codec, nullptr) < 0) {
std::cerr << "Failed to open H.264 encoder" << std::endl;
return;
}
// 创建视频流
m_stream = avformat_new_stream(m_fmtCtx, codec);
avcodec_parameters_from_context(m_stream->codecpar, m_codecCtx);
// 打开 RTSP 输出
if (avio_open(&m_fmtCtx->pb, rtsp_url.c_str(), AVIO_FLAG_WRITE) < 0) {
std::cerr << "Failed to open RTSP output" << std::endl;
return;
}
// 写文件头
if (avformat_write_header(m_fmtCtx, nullptr) < 0) {
std::cerr << "Failed to write RTSP header" << std::endl;
return;
}
m_connected = true;
}
bool RTSPStreamer::pushFrame(const cv::Mat& frame) {
if (!m_connected) return false;
// 转换 BGR 到 YUV420
AVFrame* avFrame = av_frame_alloc();
avFrame->format = AV_PIX_FMT_YUV420P;
avFrame->width = m_width;
avFrame->height = m_height;
av_frame_get_buffer(avFrame, 0);
SwsContext* swsCtx = sws_getContext(
frame.cols, frame.rows, AV_PIX_FMT_BGR24,
m_width, m_height, AV_PIX_FMT_YUV420P,
SWS_BILINEAR, nullptr, nullptr, nullptr);
const uint8_t* srcData[1] = {frame.data};
int srcLinesize[1] = {frame.step};
sws_scale(swsCtx, srcData, srcLinesize, 0, frame.rows,
avFrame->data, avFrame->linesize);
sws_freeContext(swsCtx);
// 编码并发送
AVPacket pkt;
av_init_packet(&pkt);
pkt.data = nullptr;
pkt.size = 0;
if (avcodec_send_frame(m_codecCtx, avFrame) < 0) {
av_frame_free(&avFrame);
return false;
}
while (avcodec_receive_packet(m_codecCtx, &pkt) == 0) {
av_packet_rescale_ts(&pkt, m_codecCtx->time_base, m_stream->time_base);
pkt.stream_index = m_stream->index;
av_interleaved_write_frame(m_fmtCtx, &pkt);
av_packet_unref(&pkt);
}
av_frame_free(&avFrame);
return true;
}
RTSPStreamer::~RTSPStreamer() {
if (m_fmtCtx) {
av_write_trailer(m_fmtCtx);
avio_close(m_fmtCtx->pb);
avformat_free_context(m_fmtCtx);
}
if (m_codecCtx) avcodec_free_context(&m_codecCtx);
avformat_network_deinit();
}