利用Opencv提供的imencode和imdecode进行图像视频传输(发送端支持Linux和Windows双系统)

本文探讨了网络图像传输中常见的基于像素访问方式的问题,提出使用OpenCV的imencode和imdecode函数进行图像编码和解码,以减少数据量并提高传输效率。通过对比TCP和UDP协议,论证了UDP在实时图像传输中的优势,并给出了基于UDP协议的网络视频传输代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

关于网络图像传输,网上大多数都是基于像素访问进行传输,传输的大小是图像的分辨率以及他的通道数,一般普通摄像头拍摄到图像大小的分辨率是640480,也就是说单通道灰度图像,一次要传输的数据量大小是640480=307200个字节,如果是彩色3通道那就是604803 = 921600个字节。90万的字节对网络资源的消耗是非常大,采用这种方式进行网络图像传输,容易造成视频的卡顿,所以不建议采用像素的访问进行传输。
Openc提供了imencode和imdecode两个函数,将图像进行二进制编码。https://docs.opencv.org/3.0-beta/modules/imgcodecs/doc/reading_and_writing_images.html这个是opencv官网对于图像读写的API做出了相应的介绍。我们先看一下imencode。

关于imencode与imdecode

C++: bool imencode(const String& ext, InputArray img, vector<uchar>& buf, const vector<int>& params=vector<int>())

参数:
ext-定义输出文件格式的扩展名
img-需要被编码的图像
buf-输出的缓存区,类型是vector
parms-被编码的格式和压缩率,类型是vector
prams目前支持以下参数:
JPEG,它的压缩率范围(cv_imwrite_jpeg_quality)从0到100(越大越好)。默认值是95。100为没有压缩。
对于WEBP来说,它的压缩范围(cv_imwrite_webp_quality)从1到100(越大越好)。默认情况下(不含任何参数)和质量在100以上,则使用无损压缩。
png,可以压缩级别(cv_imwrite_png_compression)从0到9。更高的值意味着更小的尺寸和更长的压缩时间。默认值是3。
PPM、PGM、或PBM,它可以是一个二进制格式的标志(cv_imwrite_pxm_binary),0或1。默认值是1。

然后看一下imdecode

C++: Mat imdecode(InputArray buf, int flags)
C++: Mat imdecode(InputArray buf, int flags, Mat* dst)

buf-输入解压的buf
flags-和imread()的flags是一样的
CV_LOAD_IMOSE_COLOR-如果设置,始终将图像转换为彩色图像
CV_LOAD_IMAGE_GRAYSCALE如果设置,始终将图像转换为灰度图像
dst -解码矩阵的可选输出占位符。不填则是NULL。

关于图像传输
图像传输利用socket套接字进行传输,有两种协议,这个是TCP协议一个是UDP协议。对于两种协议的采用哪种,我个人觉得UDP会更好一些。UDP不需要建立握手环节,在某些工程下减少了一些资源的开销。相反TCP协议需呀建立握手后才可以通信,一旦因为网络不好或者网络断开,就需要重新连接,需要一些时间和精力去处理这些琐事,同时也会消耗系统的资源。在网络不好时UDP存在的现象,无非使视频卡顿,而TCP则需重新连接。所以选择UDP是一个非常好的选择。

以下是UDP协议下利用imencode和imdecode的网络视频传输

接收端:

#define  _CRT_SECURE_NO_WARNINGS
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <iostream>
#include <opencv2/opencv.hpp>  
#include <opencv2/imgproc/imgproc.hpp> 
#include <WinSock2.h>
#pragma comment(lib,"WS2_32.lib")

using namespace cv;
using namespace std;

enum
{
	PORT = 0x1234
};
int main(int argc, char** argv)
{
	WSADATA wsaData;
	WSAStartup(0x01, &wsaData); //创建初始化句柄

	SOCKET m_sockClient;
	if ((m_sockClient = socket(AF_INET, SOCK_DGRAM, 0)) < 0)    //创建socket句柄,采用UDP协议
	{
		printf("create socket error: %s(errno: %d)\n", strerror(errno), errno);
		return -1;
	}
	sockaddr_in m_servaddr;
	memset(&m_servaddr, 0, sizeof(m_servaddr));  //初始化结构体
	m_servaddr.sin_family = AF_INET;           //设置通信方式
	m_servaddr.sin_port = htons(PORT);         //设置端口号

	bind(m_sockClient, (sockaddr*)&m_servaddr, sizeof(m_servaddr));//绑定套接字
	Mat image;
	char buf[65536];
	while (true)
	{
		std::vector<uchar> decode;

		int n = recv(m_sockClient, buf, sizeof(buf), 0);//接受缓存
		int pos = 0;
		while (pos < n)
		{
			decode.push_back(buf[pos++]);//存入vector
		}
		buf[n] = 0;
		image = imdecode(decode, CV_LOAD_IMAGE_COLOR);//图像解码
		imshow("image", image);
		waitKey(30);
	}
	return 0;
}

发送端:

#ifdef _WIN32	
#define  _CRT_SECURE_NO_WARNINGS
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <WinSock2.h>
#pragma comment(lib,"WS2_32.lib")
#else
#include <unistd.h>//Linux系统下网络通讯的头文件集合
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <malloc.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <stdarg.h>
#include <fcntl.h>
#include <fcntl.h>
#endif
#include <iostream>
#include <opencv2/opencv.hpp>  
#include <opencv2/imgproc/imgproc.hpp> 
using namespace cv;
using namespace std;

enum
{
	PORT = 0X4321
};
int main(int argc, char** argv)
{
#ifdef _WIN32
	WSADATA wsaData;
	WSAStartup(0x01, &wsaData); //创建初始化句柄
#endif
	int m_sockClient;
	if ((m_sockClient = socket(AF_INET, SOCK_DGRAM, 0)) < 0)    //创建socket句柄,采用UDP协议
	{
		printf("create socket error: %s(errno: %d)\n", strerror(errno), errno);
		return -1;
	}
	sockaddr_in m_servaddr;
	memset(&m_servaddr, 0, sizeof(m_servaddr));  //初始化结构体
	m_servaddr.sin_family = AF_INET;           //设置通信方式
	m_servaddr.sin_port = htons(PORT);         //设置端口号

	bind(m_sockClient, (sockaddr*)&m_servaddr, sizeof(m_servaddr));//绑定端口号
	VideoCapture capture(0);//打开摄像头
	Mat image;
	while (true)
	{
		capture >> image;//读入图片
		if (image.empty())    //如果照片为空则退出
		{
			printf("empty image\n\n");
			return -1;
		}
		std::vector<uchar> data_encode;
		std::vector<int> quality;
		quality.push_back(CV_IMWRITE_JPEG_QUALITY);
		quality.push_back(50);//进行50%的压缩
		imencode(".jpg", image, data_encode,quality);//将图像编码
		char encodeImg[65535];

		int nSize = data_encode.size();
		for (int i = 0; i < nSize; i++)
		{
			encodeImg[i] = data_encode[i];
		}
		m_servaddr.sin_addr.s_addr = inet_addr("192.168.1.102");
		m_servaddr.sin_port = htons(0x1234);//设置需要发送的IP和端口号
		sendto(m_sockClient, encodeImg, nSize, 0, (const sockaddr*)& m_servaddr, sizeof(m_servaddr));
		memset(&encodeImg, 0, sizeof(encodeImg));  //初始化结构体
	}
	return 0;
}

相关源码可以在此处下载:http://download.youkuaiyun.com/download/qq_37406130/10161435

<think>我们使用纯C++(不含Qt)进行传感器数据采集串口通信,使用PySide6构建GUI,并通过进程间通信(IPC)将图像数据从C++程序传递到Python程序进行ONNX推理。 步骤: 1. C++程序:监听串口,当光电传感器触发时,通过摄像头拍照(使用OpenCV)。 2. C++程序:将采集到的图像通过进程间通信(如ZeroMQ)发送给Python程序。 3. Python程序:使用ONNX Runtime加载YOLO模型进行推理,将结果返回给GUI。 4. PySide6 GUI:显示原始图像检测结果,并提供控制界面。 注意:由于要求C++不含Qt,所以C++部分使用标准库第三方库(如OpenCV、ZeroMQ)实现。 具体实现: 一、C++数据采集与通信模块 - 串口通信:使用Boost.Asio或Windows/Linux原生串口API。 - 图像采集:使用OpenCV的VideoCapture。 - IPC:使用ZeroMQ发送图像数据。 二、Python推理模块 - 使用ZeroMQ接收图像。 - 使用ONNX Runtime进行推理。 - 将结果发送到PySide6 GUI(通过信号槽)。 三、PySide6 GUI模块 - 主窗口显示图像结果。 - 启动一个线程用于接收来自推理模块的结果。 代码示例: 1. C++ 串口监听与图像采集(伪代码): ```cpp #include <opencv2/opencv.hpp> #include <zmq.hpp> // 串口类(以Linux为例) class SerialPort { int fd; public: void open(const std::string& port, int baud_rate) { /* 打开串口 */ } char read_trigger() { /* 读取触发信号,例如返回一个字符 */ } }; int main() { SerialPort serial; serial.open("/dev/ttyUSB0", 9600); zmq::context_t context(1); zmq::socket_t sender(context, ZMQ_PUSH); sender.connect("tcp://localhost:5555"); // 连接到Python的接收器 cv::VideoCapture cap(0); if (!cap.isOpened()) return -1; while (true) { if (serial.read_trigger() == 'T') { // 假设触发信号是字符'T' cv::Mat frame; cap >> frame; // 将图像转换为字节流 std::vector<uchar> buffer; cv::imencode(".jpg", frame, buffer); // 发送图像数据 zmq::message_t message(buffer.size()); memcpy(message.data(), buffer.data(), buffer.size()); sender.send(message, zmq::send_flags::none); } } return 0; } ``` 2. Python 推理服务(使用PySide6ONNX Runtime): ```python import sys import cv2 import numpy as np import zmq from PySide6.QtWidgets import QApplication, QLabel, QVBoxLayout, QWidget from PySide6.QtCore import QObject, Signal, Slot, QThread import onnxruntime as ort # 创建ZMQ接收器 context = zmq.Context() socket = context.socket(zmq.PULL) socket.bind("tcp://*:5555") # 加载ONNX模型 ort_session = ort.InferenceSession("yolov5.onnx") class InferenceWorker(QObject): image_processed = Signal(np.ndarray) # 发送处理后的图像(带检测结果) def run(self): while True: # 接收图像 message = socket.recv() img_data = np.frombuffer(message, dtype=np.uint8) img = cv2.imdecode(img_data, cv2.IMREAD_COLOR) # 预处理 input_tensor = self.preprocess(img) # 推理 outputs = ort_session.run(None, {'images': input_tensor}) # 后处理(假设使用YOLOv5的输出处理) result_img = self.postprocess(outputs, img) # 发送结果 self.image_processed.emit(result_img) def preprocess(self, img): # 根据模型要求进行预处理(调整大小、归一化等) # 例如:resize to 640x640, normalize, convert to float32, etc. pass def postprocess(self, outputs, img): # 解析输出,绘制边界框等 # 返回绘制了检测结果的图像 pass class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle("视觉检测系统") self.image_label = QLabel() layout = QVBoxLayout() layout.addWidget(self.image_label) self.setLayout(layout) @Slot(np.ndarray) def update_image(self, img): # 将OpenCV图像转换为Qt图像并显示 h, w, c = img.shape qimg = QImage(img.data, w, h, w*c, QImage.Format_BGR888) self.image_label.setPixmap(QPixmap.fromImage(qimg)) if __name__ == "__main__": app = QApplication(sys.argv) window = MainWindow() window.show() worker = InferenceWorker() thread = QThread() worker.moveToThread(thread) worker.image_processed.connect(window.update_image) thread.started.connect(worker.run) thread.start() sys.exit(app.exec()) ``` 注意事项: - 跨语言通信:使用ZeroMQ传输图像数据,注意图像编码(如JPEG)以减少数据量。 - 线程管理:在Python中,推理工作放在单独的线程中,避免阻塞GUI主线程。 - 性能:如果处理速度跟不上触发频率,可能需要加入队列或丢弃策略。 优化: - 使用多线程处理推理任务(例如使用线程池处理多个图像)。 - 使用共享内存(如POSIX共享内存或Windows共享内存)代替ZeroMQ传输图像,减少拷贝开销。 §§相关问题§§ 1. 在C++程序中如何实现串口数据的稳定读取(特别是在高频率触发情况下)? 2. 使用ONNX Runtime进行推理时,如何对YOLO模型的输出进行后处理以得到检测框类别? 3. 在PySide6中如何实现高帧率图像的流畅显示而不卡顿? 4. 如何设计一个跨进程的图像传输协议,除了图像数据外还能传递时间戳传感器信息? 5. 当检测到多个物体时,如何将检测结果(类别、位置、置信度)实时显示在GUI界面上而不仅仅是图像?</think>### 使用 PySide6 + 纯 C++(不含 Qt)开发视觉检测系统(光电传感器触发 + ONNX 推理) #### 系统架构设计 ```mermaid graph TB A[光电传感器] -->|串口数据| B[纯C++采集程序] B -->|共享内存| C[PySide6主程序] C -->|调用| D[ONNX推理模块] D -->|检测结果| E[GUI显示/输出] ``` #### 核心实现步骤 1. **C++ 传感器与采集模块(无Qt)** - 使用 **Boost.Asio** 处理串口通信 - **OpenCV** 实现图像采集 - **共享内存** 实现跨进程通信 ```cpp #include <boost/asio.hpp> #include <opencv2/opencv.hpp> #include <boost/interprocess/shared_memory_object.hpp> void sensor_thread() { // 1. 串口初始化 boost::asio::io_service io; boost::asio::serial_port port(io, "/dev/ttyUSB0"); port.set_option(boost::asio::serial_port_base::baud_rate(9600)); // 2. 等待触发信号 char trigger; boost::asio::read(port, boost::asio::buffer(&trigger, 1)); // 3. 采集图像 cv::VideoCapture cap(0); cv::Mat frame; cap >> frame; // 4. 写入共享内存 boost::interprocess::shared_memory_object shm( boost::interprocess::open_or_create, "camera_frame", boost::interprocess::read_write ); shm.truncate(frame.total() * frame.elemSize()); boost::interprocess::mapped_region region(shm, boost::interprocess::read_write); std::memcpy(region.get_address(), frame.data, region.get_size()); } ``` 2. **PySide6 主程序** - 创建共享内存读取器 - 调用 ONNX 推理模块 ```python from PySide6.QtCore import QSharedMemory, QBuffer, QIODevice import cv2 import numpy as np class ImageReceiver: def get_frame(self): # 访问C++创建的共享内存 shm = QSharedMemory("camera_frame") if not shm.attach(QSharedMemory.ReadOnly): return None # 转换为OpenCV图像 buffer = QBuffer() buffer.setData(shm.data()) buffer.open(QIODevice.ReadOnly) img_data = np.frombuffer(buffer.data(), dtype=np.uint8) return cv2.imdecode(img_data, cv2.IMREAD_COLOR) ``` 3. **ONNX 推理模块** ```python import onnxruntime as ort class YOLOInferencer: def __init__(self, model_path): self.session = ort.InferenceSession(model_path) self.input_name = self.session.get_inputs()[0].name def detect(self, image): # 预处理 input_tensor = cv2.resize(image, (640, 640)) input_tensor = input_tensor.transpose(2, 0, 1) input_tensor = np.expand_dims(input_tensor, axis=0).astype(np.float32) # 推理 outputs = self.session.run(None, {self.input_name: input_tensor}) # 后处理 (示例:YOLOv5输出解析) detections = self.parse_yolov5_output(outputs[0]) return detections ``` 4. **系统集成** ```python class MainWindow(QMainWindow): def __init__(self): super().__init__() self.receiver = ImageReceiver() self.detector = YOLOInferencer("yolov5s.onnx") self.timer = QTimer() self.timer.timeout.connect(self.process_frame) self.timer.start(50) # 20fps def process_frame(self): frame = self.receiver.get_frame() if frame is not None: results = self.detector.detect(frame) self.display_results(frame, results) ``` #### 关键技术点 1. **跨进程通信方案** - **共享内存**:使用 `boost::interprocess`(C++) `QSharedMemory`(Python)实现高速图像传输 - **互斥锁**:通过命名互斥锁防止读写冲突 ```cpp // C++写入前加锁 boost::interprocess::named_mutex mutex( boost::interprocess::open_or_create, "frame_mutex" ); mutex.lock(); // 写入共享内存... mutex.unlock(); ``` 2. **传感器同步优化** - 双缓冲机制:C++维护两个共享内存区域交替写入 - 触发计数器:在共享内存中记录触发序号防止漏帧 3. **部署方案** ```mermaid graph LR A[C++采集程序] -->|Windows服务/Linux守护进程| B[运行] C[PySide6应用] -->|打包为exe/app| D[部署] ``` #### 性能优化建议 1. **零拷贝传输**:使用内存映射文件代替数据复制 2. **推理加速**:启用TensorRT优化ONNX模型 3. **异步处理**:Python使用线程池处理推理任务 4. **硬件加速**:OpenCV启用VAAPI/Vulkan硬件解码
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值