博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
UDP实时图像传输进阶篇——1080P视频传输
阅读量:3932 次
发布时间:2019-05-23

本文共 5403 字,大约阅读时间需要 18 分钟。

在一文中,介绍了如何使用UDP来实现图像的实时传输,并使用C#进行了发送端和接收端的搭建。但是文中的方法是对整张图片进行JPEG压缩,并通过UDP一次性地发送到接收端,由于一个UDP数据包只能发送64k字节的数据,所以该方法的图片传输大小是有限制的,实测只能发送480P视频中的图像。

所以本文将继续采取逐帧发送的形式,以1080P的视频为例,实现更高清晰度( 1080 × 1920 × 3 1080\times 1920\times 3 1080×1920×3)的图像实时传输。

基本流程

本文中的高清晰度图像传输就是在前文方法的基础上,在发送端添加了切片压缩传输以及并行加速的步骤,而接收端则相应地使用多线程进行数据接收,分别接收压缩后的切片数据,再拼接起来进行显示。流程如下

系统框图

实验环境

  • VS2019 / .NET4.7.1 / C#(开发环境)

  • EmguCV 4.1(用于读取、压缩图像,使用方法见)

  • PC(测试环境)

发送端

在发送端我们需要达到的效果如下,左边用来显示原始图像,右上角用来显示各个切片,右下角用来处理接收端的连接请求。

在这里插入图片描述
首先设置一些参数

// 实例化一个VideoCapture,选择从本地文件读取视频private VideoCapture capture = new VideoCapture("../../video/04.mp4");// 设置读取的图片宽度const int WIDTH = 1920;  // 设置读取的图片高度const int HEIGHT = 1080; // 切片数量const int NUM_SLICE = 24;

然后进行图像的显示以及切片。初始化一组显示控件,用来显示切片后的结果:

private void Form1_Load(object sender, EventArgs e){
// 设置图像大小 capture.SetCaptureProperty(Emgu.CV.CvEnum.CapProp.FrameWidth, WIDTH); capture.SetCaptureProperty(Emgu.CV.CvEnum.CapProp.FrameHeight, HEIGHT); // 获取面板控件的大小 int w = panel_imgs.Width; int h = panel_imgs.Height; // 在面板panel_imgs上添加显示控件,用于显示每个切片 for (int i = 0; i < NUM_SLICE; i++) {
ImageBox imgb = new ImageBox(); imgb.Left = 0; imgb.Top = i * h / NUM_SLICE; imgb.Width = w; imgb.Height = h / NUM_SLICE - 1; imgb.SizeMode = PictureBoxSizeMode.StretchImage; imgb.Visible = true; imgbox[i] = imgb; panel_imgs.Controls.Add(imgbox[i]); } // 在下拉文本框cbb_localIP中显示该计算机中的IPv4地址 cbb_localIP.Text = GetLocalIPv4Address().ToString();}

最后就是图像的读取、切片、压缩、发送等处理函数,这处理过程中,使用了Parallel.For并行加速功能,相对于串行的for循环,并行速度提高了一倍左右(不知道为啥我四核八线程的处理器只能降低一半的运行时间)

private void ProcessFram() // 图像读取、切片、发送{
DateTime startDT = System.DateTime.Now; while (true) {
// 计算两次循环间的间隔,并显示在左上角 DateTime stopDT = System.DateTime.Now; TimeSpan ts = stopDT.Subtract(startDT); this.Text = "图片处理耗时:" + ts.TotalMilliseconds + "ms"; startDT = System.DateTime.Now; // 读取一张图片 Mat currentImage = capture.QueryFrame(); // 显示摄像头/视频流的图像 imageBox0.Image = currentImage; int N = HEIGHT / NUM_SLICE; // 对图像进行切片,并将切片压缩后发送到接收端 Parallel.For(0, NUM_SLICE, i => // Parallel并行加速 {
// 从原图中切割,输入参数:原始图片 行范围row 列范围col img[i] = new Mat(currentImage, new Range(i * N, (i + 1) * N - 1), new Range(0, WIDTH)); // 显示 imgbox[i].Image = img[i]; // 转换格式 Image
img_trans = img[i].ToImage
(); // JPEG压缩 byte[] bytes = img_trans.ToJpegData(95); // UDP配置 UdpClient udpClient = new UdpClient(); //IPAddress ipaddress = IPAddress.Parse("192.168.0.105"); IPAddress ipaddress = remoteIP; IPEndPoint endpoint = new IPEndPoint(ipaddress, 8000 + 10 * i); // UDP发送 udpClient.Send(bytes, bytes.Length, endpoint); udpClient.Close(); } ); }}

在初始化函数中添加以下程序就可以执行包含切片、压缩、发送等操作的线程

Thread transFrames = new Thread(ProcessFram);transFrames.Start();

接收端

接收端比较简单,实现效果如下,因为在接收端没有对图片进行更进一步的处理,所以本文只在接收端添加了若干个显示控件,用来显示每个切片,但是从观感上每个切片依次连接,形成了一张完整的图片。

在这里插入图片描述
首先进行参数设置

// 切片数量,与发送端保持一致 const int NUM_SLICE = 24;  // 为每一个切片创建一个显示控件 PictureBox[] imgbox = new PictureBox[NUM_SLICE]; // 为每一个切片创建一个UDP套接字 Socket[] udpServer = new Socket[NUM_SLICE];

在初始化过程中添加显示控件,与发送端类似

int w = panel_imgs.Width;int h = panel_imgs.Height;// 在面板panel_imgs上添加显示接收到的图片的控件for (int i = 0; i < NUM_SLICE; i++){
// 设置PictureBox的位置、大小等参数 PictureBox imgb = new PictureBox(); imgb.Left = 0; imgb.Top = i * h / NUM_SLICE; imgb.Width = w; imgb.Height = h / NUM_SLICE + 1; imgb.SizeMode = PictureBoxSizeMode.StretchImage; imgb.Visible = true; // 添加到面板panel_imgs上 imgbox[i] = imgb; panel_imgs.Controls.Add(imgbox[i]);}

接下来需要为每个切片创建一个接收线程

for (int i = 0; i < NUM_SLICE; i++){
new Thread(new ParameterizedThreadStart(ImgReceive)) {
IsBackground = true }.Start(8000 + i * 10); // 输入参数为端口号,依次增加}

最后就是接收线程的入口函数ImgReceive的内容

private void ImgReceive(object arg){
// 网络端口号 int port = (int)arg; int index = port % 8000 / 10; // 创建套接字 udpServer[index] = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); // 绑定IP和端口 udpServer[index].Bind(new IPEndPoint(IPAddress.Parse(cbb_remoteIP.Text), port)); // 开启数据接收线程 while (true) {
EndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 0); // 设置一个64k的字节数组作为缓存 byte[] data = new byte[65536]; int length = udpServer[index].ReceiveFrom(data, ref remoteEndPoint);//此方法把数据来源ip、port放到第二个参数中 MemoryStream ms = new MemoryStream(data, 0, length); // 将图像显示到对应的PictureBox控件上 Image img = Image.FromStream(ms); imgbox[index].Image = img; }}

测试结果

测试时发送端和接收端都在同一台PC上运行,运行流程与结果如这张GIF所示,左边是发送端,右边是接收端

在这里插入图片描述

运行流程中的TCP创建与连接的程序本文没有给出,可以参考上一篇文章。

如果有时间的话,就继续添加利用时间戳计算延迟的程序。另外,发送端的并行处理效率有待提高,后续可以使用C++进行openMP加速看看效果。
完整项目在这里(资源里没有视频文件,使用时将视频放到\ImgTransmitPlus\ImgTransmitPlus\video中,再更改发送端代码中的视频文件名即可)

----------- 2020.8.28更新 -----------

TCP对传输的数据大小没有限制,且能保证传输的可靠性,详见

转载地址:http://glvgn.baihongyu.com/

你可能感兴趣的文章
uva 357 - Let Me Count The Ways(动态规划-注意dp初始化的问题)
查看>>
uva 562 - Dividing coins(注意判断条件,可以转换成01背包做)
查看>>
uva 10404 - Bachet's Game(DP)
查看>>
最优二叉搜索树
查看>>
hdu 1008 Elevator
查看>>
hdu 1005 Number Sequence(数学题目,好好看)
查看>>
zoj 2106 Tick and Tick(比较好的数学题目,代码特麻烦,注意精度)
查看>>
zoj 2107 Quoit Design(最近点对问题,好好思考,分治)
查看>>
zoj 2111 Starship Troopers(树形DP)
查看>>
vector 容器的使用方法
查看>>
hdu 1520 Anniversary party(基本树形DP)
查看>>
fzu Problem 2138 久违的月赛之一
查看>>
poj 1947 Rebuilding Roads(树形DP)
查看>>
zoj 3626 Treasure Hunt I(树形DP+分组背包)
查看>>
poj 1655 Balancing Act(树形DP,删点)
查看>>
hdu 1754 I Hate It(线段树,单点替换,求区间最值)
查看>>
poj 2828 Buy Tickets(线段树中单点更新较难的题目)
查看>>
codeforces 395 B1. iwiwi(待续)
查看>>
hdu 4283 You Are the One(区间DP)题目转换难,状态难,。。。
查看>>
codeforces 397B. On Corruption and Numbers
查看>>