摘要 在流媒体技术的发展中,视频技术的发展越来越迅速,大量应用于安防与教育事业,比如在远程视频教育、在线远程视频会议、医疗、各种场所的视频监控以及危险区域的探测等方面。Linux操作系统在发展的过程具备了很好的网络性能。Linux系统下提供了非常丰富的开源软件和视频支持,支持各种硬件平台,因此 Linux平台成为了视频采集的最佳平台。Linux系统上的软件开发具有高可靠性、低成本、高度自动化、高可配置性、及低耗费等一些优点,同时能够很好的完成多任务和实时性的设计需求。 我国资深嵌入式系统专家——沈绪榜院士曾预言:“未来十年将会产生针头大小,具有超过一亿次运算能力的嵌入式智能芯片”。这些将为我们的开发提供了相当大的创造空间。中国应当注意发展这一智力密集型产业”。 综上所述,嵌入式Linux操作系统在广泛和深入的应用于各个领域,应用的技术也越来越复杂。针对基于网络传输的视频监控系统的实际需求与应用,结合视频的图像采集技术与编码技术,嵌入式Linux操作系统和网络编程技术等多方面的新兴技术,设计的一套Linux 网络视频监控系统,用来进行视频数据的采集、视频数据的压缩编码与解码以及视频的网络传输,该系统基于Linux 操作系统进行开发的,从OV5640捕捉视频数据,QT实现客户端和服务器端,即可查看远程视频影像。关键词 : OV5640、网络传输、QT实现、远程视频影像
一、目的及意义 视频监控系统的设计主要说明视频监控系统的详细设计与实现。介绍了系统中各个模块的设计与模块功能的实现以及应用的相关技术。我将会对每一个模块的设计以及实现方法给出简单的文字说明以及设计的流程图。 课程设计的主要目的是学习熟悉基于TCP/IP网络的远程视频监控系统的基本知识和相关的多媒体开发技术。随着5G通讯时代的到来,我们更可以将视频监控客户端发展到移动终端,这将使我们更加方便。 纵观视频监控的研究进展,从闭路电视系统构建的模拟系统、经历了数字信号控制的模拟视频监控系统、过渡到数字硬盘录像设备为核心的视频监控系统和当代的数字网络视频监控系统这几大重要阶段。 数字网络视频监控系统与其它监控系统的优点:a、布控区域广阔 数字网络视频监控系统是可以超越地域的限制的,可以延伸整个需要布控区域,这是因为它将网络视频信号采集终端直接连入网络,并且能够解除信号的衰减和缆线长度的局限性,而且网络是不受距离长短的影响。b、系统具有几乎无限的无缝扩展能力 需要增加设备时只要将IP地址进行扩充操作就行了,因为所有的IP地址都在系统中进行了标识。c、可组成非常复杂的监控网络 监控系统是以基于网络视频信号采集终端为核心,在组网方式上与基于PC平台的监控和传统的模拟监控方式有了质的飞跃,视频信号采集终端输出已完成了从模拟到数字的转换并压缩,在网络上传输时采用统一的协议,能够实现跨路由器、跨网关等的远程视频传输。d、性能稳定可靠,无需专人管理 视频监控系统中采集视频信号的终端应用了嵌入式Linux技术,主要采用嵌入式多任务实时操作系统,又能够将网络功能和视频压缩功能集中到一个极小体积的设备内,同时可以直接连入广域网络或者局域网络,即插即看,系统的稳定性、可靠性、实时性得到了极大的提升,能够运用于无人值守的环境中。e、我们可以从视频监控中心同时观看多个USB 网络摄像头视频,但是对网络带宽是有一定的要求。
二、视频监控系统的组成 1、总体设计 视频监控系统大致可分为三大模块:视频数据信号的采集部分、网络传输部分以及远程客户端视频的渲染部分。 总体框图如图所示:
2、功能分析 服务器(server)端负责视频数据的采集,采集可以通过v4l2编程实现,有详细的API开发文档,以及v4l2编程的相关资料可以参考。 客户端(client)主要负责的是将接收到的视频数据通过QT编程将其渲染出来。主要用到了QT中的信号(signal)和槽(slot)机制、以及绘图事件,QT是利用C++编程,而且它封装了一套自己的库可以实现跨平台,这也就解决了前面提到的客户端的跨平台特性。 还有连接Server 与Client的就是采用Socket网络服务器的搭建,这主要采用的是Linux环境网络编程。该网络的搭建主要是利用基于TCP/IP协议的HTTP协议,将服务器( server)端采集到的视频数据封装成http 数据帧的形式,然后利用TCP 协议将视频数据帧发送到客户端(client)。由于是实时传输,所以还用到多线程,使得每个客户端(client)相互独立,各个线程都将执行一个死循环,源源不断的给客户端发送视频数据。
3、何采集视频数据逻辑概要 这一节主要介绍了服务器(server)端的如何采集视频数据。 UDP 或者 TCP 传输,使用 Qt 封装好 TCP/IP 协议 Socket 抽象层接口来通信。那么我们也可以 使用它们来发送图像数据啊。这样,服务器和客户端我们完全可以使用 Qt 来写了。实际上 RTSP 协议和 RTMP 协议都使用 TCP/IP 协议,在传输层使用了 UDP 或 TCP。 项目流程如下: (1) 服务器采集摄像头的数据。 (2) 处理视频数据转交给 Socket,由 TCP/UDP 传输。 (3) 客户端接收视频数据。
三、视频监控系统的组成 1.系统测试过程 视频监控系统的测试将分别对服务端和客户端进行,我们可以下执行步骤。 1)连接好OV5640摄像头,在 Linux终端运行服务端程序,获取摄像头设备的数据,如果摄像头有不匹配或者连接问题,则会报错。 2)如上一步正常则运行客户端程序,也就是采用QT设计的图形化界面,其实在没有开启服务端程序时,也可以开启客户端,但是不能获取到视频数据。 3)在客户端输入服务端的IP地址(如本地摄像头IP为127.0.0.1),设置想要查看的窗口,按照自己的要求进行监控,通过服务端可以设计客户端相关信息在图形化界面上显示出来,以便用户查看。 4)通过客户端查看各个OV5640拍摄到的视频。 系统调试及运行的效果图下所示。 Ov5640连接正常,通过串口进行程序的烧录 客户端: 服务端: 测试完毕,客户端和服务端功能均正常
四、总结 视频监控系统课题的所设计和实现中,充分利用了Linux操作系统作为嵌入式操作系统的诸多优势,嵌入式Linux系统下的视频网络监控系统是计算机软硬件、电工电子装置以及网络通信等多方面的有机结合而形成的,它主要以网络化、交互性、智能化为特征,结构较为复杂。如果采用OSI七层模型的内容和形式,把相应的控制模块硬件、视频数据采集、视频数据的编码、视频数据的解码和视频监控软件以及开发环境的搭建等进行有机结合,可以形成一个统一完整的系统框架。在 Linux操作系统中调用v4l2编程中的API接口和底层设备驱动程序来完成视频捕获操作。v4L2是Linux操作系统中的内核驱动,主要是关于视频的开发。它为Linux操作系统中的各种视频设备提供了统一的API编程接口,应用程序可以通过这些接口函数进行操纵各种不同的设备。结合了QT技术构建了具有图形化界面支持的嵌入式系统开发平台,这种系统结构模式是目前在 PC上进行视频监控系统开发的热点。 对未来视频监控的展望:未来的视频监控系统应该更加趋向智能化、人性化、同时能够自行的对异常情况分析判断。另外在不久的未来,视频监控系统还可具有安防互动功能,与各种传感器,控制器链接,实现自动目标跟踪等功能。而且随着5G通信技术的发展和成熟,视频监控系统的成本也应该进一步降低,并且能够将其移植到移动终端,以适应更广泛领域的需要。 由于我还是个初学者,虽然最终能够实现了一些功能,但是对中间相关的一些模块开发,比如驱动、QTSocket原理等等还不是很熟悉,还有待进一步学习和掌握。
附录:源程序代码 服务端: video_server.pro:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 QT += core gui network greaterThan(QT_MAJOR_VERSION, 4): QT += widgets CONFIG += c++11 DEFINES += QT_DEPRECATED_WARNINGS TARGET = 视频监控服务端v0.1-by-JASand炳川 TEMPLATE = app SOURCES += \ capture_thread.cpp \ main.cpp \ mainwindow.cpp HEADERS += \ capture_thread.h \ mainwindow.h qnx: target.path = /tmp/$${TARGET} /bin else : unix:!android: target.path = /opt/$${TARGET} /bin!isEmpty(target.path): INSTALLS += target
Capture_thread.h:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 #ifndef CAPTURE_THREAD_H #define CAPTURE_THREAD_H #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> #include <unistd.h> #include <string.h> #include <pthread.h> #ifdef linux #include <linux/fb.h> #include <sys/ioctl.h> #include <sys/mman.h> #include <linux/videodev2.h> #include <linux/input.h> #endif #include <QThread> #include <QDebug> #include <QPushButton> #include <QImage> #include <QByteArray> #include <QBuffer> #include <QTime> #include <QUdpSocket> #define VIDEO_DEV "/dev/video1" #define FB_DEV "/dev/fb0" #define VIDEO_BUFFER_COUNT 3 struct buffer_info { void *start; unsigned int length; }; class CaptureThread : public QThread{ Q_OBJECT signals: void imageReady (QImage) ; void sendImage (QImage) ; private : bool startFlag = false ; bool startBroadcast = false ; bool startLocalDisplay = false ; void run () override ; public : CaptureThread (QObject *parent = nullptr ) { Q_UNUSED (parent); } public slots: void setThreadStart (bool start) { startFlag = start; if (start) { if (!this ->isRunning ()) this ->start (); } else { this ->quit (); } } void setBroadcast (bool start) { startBroadcast = start; } void setLocalDisplay (bool start) { startLocalDisplay = start; } }; #endif capture_thread.cpp: #include "capture_thread.h" void CaptureThread::run () { #ifdef linux #ifndef __arm__ return ; #endif int video_fd = -1 ; struct v4l2_format fmt; struct v4l2_requestbuffers req_bufs; static struct v4l2_buffer buf; int n_buf; struct buffer_info bufs_info[VIDEO_BUFFER_COUNT]; enum v4l2_buf_type type; video_fd = open (VIDEO_DEV, O_RDWR); if (0 > video_fd) { printf ("ERROR: failed to open video device %s\n" , VIDEO_DEV); return ; } fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; fmt.fmt.pix.width = 640 ; fmt.fmt.pix.height = 480 ; fmt.fmt.pix.colorspace = V4L2_COLORSPACE_SRGB; fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB565; if (0 > ioctl (video_fd, VIDIOC_S_FMT, &fmt)) { printf ("ERROR: failed to VIDIOC_S_FMT\n" ); close (video_fd); return ; } req_bufs.count = VIDEO_BUFFER_COUNT; req_bufs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; req_bufs.memory = V4L2_MEMORY_MMAP; if (0 > ioctl (video_fd, VIDIOC_REQBUFS, &req_bufs)) { printf ("ERROR: failed to VIDIOC_REQBUFS\n" ); return ; } buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; for (n_buf = 0 ; n_buf < VIDEO_BUFFER_COUNT; n_buf++) { buf.index = n_buf; if (0 > ioctl (video_fd, VIDIOC_QUERYBUF, &buf)) { printf ("ERROR: failed to VIDIOC_QUERYBUF\n" ); return ; } bufs_info[n_buf].length = buf.length; bufs_info[n_buf].start = mmap (NULL , buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, video_fd, buf.m.offset); if (MAP_FAILED == bufs_info[n_buf].start) { printf ("ERROR: failed to mmap video buffer, size 0x%x\n" , buf.length); return ; } } for (n_buf = 0 ; n_buf < VIDEO_BUFFER_COUNT; n_buf++) { buf.index = n_buf; if (0 > ioctl (video_fd, VIDIOC_QBUF, &buf)) { printf ("ERROR: failed to VIDIOC_QBUF\n" ); return ; } } type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (0 > ioctl (video_fd, VIDIOC_STREAMON, &type)) { printf ("ERROR: failed to VIDIOC_STREAMON\n" ); return ; } while (startFlag) { for (n_buf = 0 ; n_buf < VIDEO_BUFFER_COUNT; n_buf++) { buf.index = n_buf; if (0 > ioctl (video_fd, VIDIOC_DQBUF, &buf)) { printf ("ERROR: failed to VIDIOC_DQBUF\n" ); return ; } QImage qImage ((unsigned char *)bufs_info[n_buf].start, fmt.fmt.pix.width, fmt.fmt.pix.height, QImage::Format_RGB16) ; if (startLocalDisplay) emit imageReady (qImage) ; if (startBroadcast) { QUdpSocket udpSocket; QByteArray byte; QBuffer buff (&byte) ; qImage.save (&buff, "JPEG" , -1 ); QByteArray base64Byte = byte.toBase64 (); udpSocket.writeDatagram (base64Byte.data (), base64Byte.size (), QHostAddress::Broadcast, 8888 ); } if (0 > ioctl (video_fd, VIDIOC_QBUF, &buf)) { printf ("ERROR: failed to VIDIOC_QBUF\n" ); return ; } } } msleep (800 ); for (int i = 0 ; i < VIDEO_BUFFER_COUNT; i++) { munmap (bufs_info[i].start, buf.length); } close (video_fd); #endif }
Mainwindow.h:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 #ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include <QLabel> #include <QImage> #include <QPushButton> #include <QHBoxLayout> #include <QCheckBox> #include "capture_thread.h" class MainWindow : public QMainWindow{ Q_OBJECT public : MainWindow (QWidget *parent = nullptr ); ~MainWindow (); private : QLabel *videoLabel; CaptureThread *captureThread; QPushButton *startCaptureButton; QPushButton *startCaptureButton2; QCheckBox *checkBox1; QCheckBox *checkBox2; void resizeEvent (QResizeEvent *event) override ; private slots: void showImage (QImage) ; void startCaptureButtonClicked (bool ) ; void startCaptureButton2Clicked (bool ) ; }; #endif Mainwindow.cpp: #include "mainwindow.h" MainWindow::MainWindow (QWidget *parent) : QMainWindow (parent) { this ->setGeometry (0 , 0 , 800 , 480 ); videoLabel = new QLabel (this ); videoLabel->setText ("未获取到图像数据或未开启服务端显示" ); videoLabel->setStyleSheet ("QWidget {color: white;}" ); videoLabel->setAlignment (Qt::AlignCenter); videoLabel->resize (640 , 480 ); checkBox1 = new QCheckBox (this ); checkBox2 = new QCheckBox (this ); checkBox1->resize (150 , 50 ); checkBox2->resize (150 , 50 ); checkBox1->setText ("服务端显示" ); checkBox2->setText ("开启广播" ); checkBox1->setStyleSheet ("QCheckBox {color: red;}" "QCheckBox:indicator {width: 40; height: 40;}" ); checkBox2->setStyleSheet ("QCheckBox {color: red;}" "QCheckBox:indicator {width: 40; height: 40;}" ); startCaptureButton = new QPushButton (this ); startCaptureButton->setCheckable (true ); startCaptureButton->setText ("开始采集摄像头数据" ); startCaptureButton2 = new QPushButton (this ); startCaptureButton2->setCheckable (true ); startCaptureButton2->setText ("防伪按钮" ); QColor color = QColor (Qt::black); QPalette p; p.setColor (QPalette::Window, color); this ->setPalette (p); startCaptureButton->setStyleSheet ("QPushButton {background-color: white; border-radius: 30}" "QPushButton:pressed {background-color: red;}" ); captureThread = new CaptureThread (this ); connect (startCaptureButton, SIGNAL (clicked (bool )), captureThread, SLOT (setThreadStart (bool ))); connect (startCaptureButton, SIGNAL (clicked (bool )), this , SLOT (startCaptureButtonClicked (bool ))); connect (startCaptureButton2, SIGNAL (clicked (bool )), this , SLOT (startCaptureButton2Clicked (bool ))); connect (captureThread, SIGNAL (imageReady (QImage)), this , SLOT (showImage (QImage))); connect (checkBox1, SIGNAL (clicked (bool )), captureThread, SLOT (setLocalDisplay (bool ))); connect (checkBox2, SIGNAL (clicked (bool )), captureThread, SLOT (setBroadcast (bool ))); } MainWindow::~MainWindow () { } void MainWindow::showImage (QImage image) { videoLabel->setPixmap (QPixmap::fromImage (image)); } void MainWindow::resizeEvent (QResizeEvent *event) { Q_UNUSED (event) startCaptureButton->move ((this ->width () - 200 ) / 2 , this ->height () - 80 ); startCaptureButton->resize (200 , 60 ); startCaptureButton2->move (((this ->width () - 200 ) / 2 ) -300 , this ->height ()- 40 ); startCaptureButton2->resize (100 , 40 ); videoLabel->move ((this ->width () - 640 ) / 2 , (this ->height () - 480 ) / 2 ); checkBox1->move (this ->width () - 130 , this ->height () / 2 + 145 ); checkBox2->move (this ->width () - 130 , this ->height () / 2 + 190 ); } void MainWindow::startCaptureButtonClicked (bool start) { if (start) startCaptureButton->setText ("停止采集摄像头数据" ); else startCaptureButton->setText ("开始采集摄像头数据" ); } void MainWindow::startCaptureButton2Clicked (bool start) { if (start) startCaptureButton2->setText ("by JAS & LBC" ); else startCaptureButton2->setText ("防伪按钮" ); }
Main.cpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 #include "mainwindow.h" #include <QApplication> int main (int argc, char *argv[]) { QApplication a (argc, argv) ; MainWindow w; w.show (); return a.exec (); }
客户端: Video_Client.pro:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 QT += core gui network greaterThan(QT_MAJOR_VERSION, 4): QT += widgets CONFIG += c++11 DEFINES += QT_DEPRECATED_WARNINGS TARGET = 视频监控客户端v0.1-byJAS-炳川 TEMPLATE = app SOURCES += \ main.cpp \ mainwindow.cpp HEADERS += \ mainwindow.h qnx: target.path = /tmp/$${TARGET} /bin else : unix:!android: target.path = /opt/$${TARGET} /bin!isEmpty(target.path): INSTALLS += target
Mainwindow.h:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 #ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include <QUdpSocket> #include <QLabel> class MainWindow : public QMainWindow{ Q_OBJECT public : MainWindow (QWidget *parent = nullptr ); ~MainWindow (); private : QUdpSocket *udpSocket; QLabel *videoLabel; void resizeEvent (QResizeEvent *event) override ; private slots: void videoUpdate () ; }; #endif Mainwindow.cpp: #include "mainwindow.h" #include <QDebug> MainWindow::MainWindow (QWidget *parent) : QMainWindow (parent) { QColor color = QColor (Qt::black); QPalette p; p.setColor (QPalette::Window, color); this ->setPalette (p); udpSocket = new QUdpSocket (this ); udpSocket->bind (QHostAddress::Any, 8888 ); videoLabel = new QLabel (this ); videoLabel->resize (640 , 480 ); videoLabel->setText ("未获取到图像数据" ); videoLabel->setStyleSheet ("QWidget {color: white;}" ); videoLabel->setAlignment (Qt::AlignCenter); connect (udpSocket, SIGNAL (readyRead ()), this ,SLOT (videoUpdate ())); this ->setGeometry (0 , 0 , 800 , 480 ); } MainWindow::~MainWindow () { } void MainWindow::videoUpdate () { QByteArray datagram; datagram.resize (udpSocket->pendingDatagramSize ()); udpSocket->readDatagram (datagram.data (), datagram.size ()); QByteArray decryptedByte; decryptedByte = QByteArray::fromBase64 (datagram.data ()); QImage image; image.loadFromData (decryptedByte); videoLabel->setPixmap (QPixmap::fromImage (image)); } void MainWindow::resizeEvent (QResizeEvent *event) { Q_UNUSED (event) videoLabel->move ((this ->width () - 640 ) / 2 , (this ->height () - 480 ) / 2 ); }
Main.cpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 #include "mainwindow.h" #include <QApplication> int main (int argc, char *argv[]) { QApplication a (argc, argv) ; MainWindow w; w.show (); return a.exec (); }