[置顶] 基于udp的监视系统示例

前言

想法来源:http://nashruddin.com/Web_Based_Surveillance_System_with_OpenCV_PHP_and_Javascript

这篇文章是基于opencv,php,javascript做了一个基于网络的监视系统。

偶虽然熟悉opencv,稍微懂点javascript,但是不懂php,所以只能另辟新径:

服务器:通过opencv捕获每一帧图片,然后基于udp发送出去。

客户端:使用activex控件嵌入网页,activex中接受服务器发送的实时视频文件。

系统设计

整个系统的思路比较清晰,我直接引用上面那篇博文的系统结构图:

服务器端设计

初始化套接字库,接下来是一系列代办的UDP初始化设置。OpenCV初始化,开启摄像头,由于要网络传输视频,所以把图片缩小1/4。需要说明一点:opencv捕获到的视频帧数据不能直接发送(可能是我愚钝,不知道怎么直接发送),所以捕获每一帧数据以后我先写成jpeg文件,然后再一遍读取文件一边发送。套接字缓存最大8K到9K,所以只能分段发送。。。

服务器端需要安装opencv,我的版本是2.0。

最终代码如下:

#include <WinSock2.h> #include <stdio.h> #include <opencv/cv.h> #include <opencv/highgui.h> #include <fstream> #include <iostream> #pragma comment(lib, "ws2_32.lib") IplImage* g_image = NULL; CvMemStorage* g_storage = NULL; // #define CHECK_FILE #define SEND_VIDEO #ifdef SEND_VIDEO void main() { using namespace std; ////////////////////////////////////////////////////////////////////////// // OpenCV CvCapture * capture; IplImage * frame; double t(0); double ms(0); int key(0); bool flag(false); ////////////////////////////////////////////////////////////////////////// // 加载套接字库 WORD wVersionRequested; WSAData wsaData; int err; wVersionRequested = MAKEWORD(1, 1); err = WSAStartup( wVersionRequested, &wsaData ); if (err != 0) return; if ( LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1 ) { WSACleanup(); return; } // 创建套接字 SOCKET sockSrv = socket(AF_INET, SOCK_DGRAM, 0); SOCKADDR_IN addrSrv; addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY); addrSrv.sin_family = AF_INET; addrSrv.sin_port = htons(6000); bind( sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR) ); SOCKADDR_IN addrClient; int len = sizeof SOCKADDR; ////////////////////////////////////////////////////////////////////////// // 等待接收客户端数据 // #NOTE addrClient此时还未知,secvfrom函数调用时传出addrClient值 // 服务器保存数据报信息的源地址 char recvBuf[100]; char tempBuf[100]; recvfrom(sockSrv, recvBuf, 100, 0, (SOCKADDR*)&addrClient, &len); sprintf(tempBuf, "%s say : %s", inet_ntoa(addrClient.sin_addr), recvBuf); printf("%s/n", tempBuf); ////////////////////////////////////////////////////////////////////////// // 设置发送缓存区 int nSendBuf=64*1024;//设置为64K setsockopt(sockSrv, SOL_SOCKET,SO_SNDBUF,(const char*)&nSendBuf,sizeof(int)); ////////////////////////////////////////////////////////////////////////// /* initialize webcam */ capture = cvCaptureFromCAM(0); cvSetCaptureProperty(capture, CV_CAP_PROP_FRAME_WIDTH, 320 ); cvSetCaptureProperty(capture, CV_CAP_PROP_FRAME_HEIGHT, 240); cvNamedWindow("video", 1); while (key != 'q') { t = (double)cvGetTickCount(); /* display video */ frame = cvQueryFrame(capture); cvShowImage("video", frame); key = cvWaitKey(1); /* get elapsed time */ t = (double)cvGetTickCount() - t; ms += t/((double)cvGetTickFrequency() * 1000.0); /* autosave every 300 ms */ if (ceil(ms) >= 300 && !flag) { // flag = true; cvSaveImage("img.jpg", frame); ms = 0; Sleep(2); ifstream infile; infile.open ("img.jpg", ios::binary ); // get length of file: infile.seekg (0, ios::end); long length = infile.tellg(); infile.seekg (0, ios::beg); char buffer[1024]; // 发送文件大小和名称 char temp[1024]; //首先用来存放文件大小,后面用作发送文件缓冲区 memset(temp,0,sizeof(temp)); ltoa(length, temp, 10); //将长度转化为字符 sendto(sockSrv, temp, strlen(temp)+1, 0, (SOCKADDR*)&addrClient, len); //发送文件的名称和大小 printf("File length = %i/n", length); // 发送文件 int ilen =0; int iLastPos = infile.tellg(); const int BufferSize = 1024; int iTemp(0); // 最后一段需要特殊处理 // 当文件剩余不足1024时,读取到文件尾,curPos为-1 while ( 1 ) { infile.read (buffer,BufferSize); int curPos = infile.tellg(); if (-1==curPos) ilen = length % BufferSize; else ilen = BufferSize; sendto(sockSrv, buffer,ilen, 0, (SOCKADDR*)&addrClient, len); iTemp += ilen; // printf("%d /n", iTemp); if (-1==curPos) break; } // sendto(sockSrv, sendBuf, strlen(sendBuf)+1, 0, (SOCKADDR*)&addrClient, len); infile.close(); } } /* free memory */ cvReleaseCapture(&capture); cvDestroyWindow("video"); // exit socket closesocket(sockSrv); WSACleanup(); }

 

客户端设计

1,首先新建一个activex的工程。activex工程中ctrl类相当于单视图中的view类。在新建的工程ctrl类下有这么一段代码:

void CocxClientCtrl::OnDraw( CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid) { if (!pdc) return; // TODO: 用您自己的绘图代码替换下面的代码。 pdc->FillRect(rcBounds, CBrush::FromHandle((HBRUSH)GetStockObject(WHITE_BRUSH))); pdc->Ellipse(rcBounds); } 

运行一下可以看到一个椭圆。。。这里就是绘图的主函数。就在这个函数中显示自定义的对话框

2,在资源中新建对话框,因为将来要嵌入网页,修改对话框的属性:

Style = child

System Menu = false

Title Bar = false

将确定,取消按钮重命名为:连接服务器,断开连接。在对话框中增加一个Static Text控件显示视频,该控件为IDC_IMAGE

插入新类:ClientDialog。对话框的ID = IDD_CLIENT,需要一个成员控件:IDC_IMAGE。

然后再ClientDialog类中增加udp相关的操作。

最终ClientDialog代码如下:

// ClientDialog.cpp : 实现文件 // #include "stdafx.h" #include "activexApp.h" #include "ClientDialog.h" #include "Picture.h" #include <fstream> #include <iostream> // CClientDialog 对话框 #pragma comment(lib,"Ws2_32") CClientDialog *pDlg=NULL; UINT ListenThread(void *p) { //准备接受请求 while(1) { if(!pDlg->bAppend) { AfxEndThread(0); return 0; } ASSERT(pDlg!=NULL); pDlg->RevFile(pDlg->m_hSocketClient); } return 0; } IMPLEMENT_DYNAMIC(CClientDialog, CDialog) CClientDialog::CClientDialog(CWnd* pParent /*=NULL*/) : CDialog(CClientDialog::IDD, pParent) { m_iPort = 6000; m_hSocketClient=0; pBuffer=NULL; m_iSize=0; bAppend=true; m_isDraw = false; } CClientDialog::~CClientDialog() { if(pBuffer!=NULL) { delete []pBuffer; pBuffer=NULL; } } void CClientDialog::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); DDX_Control(pDX, IDC_IMAGE, m_cImage); } BEGIN_MESSAGE_MAP(CClientDialog, CDialog) ON_WM_PAINT() ON_WM_LBUTTONDOWN() ON_WM_LBUTTONUP() ON_WM_MOUSEMOVE() END_MESSAGE_MAP() // CClientDialog 消息处理程序 BOOL CClientDialog::OnInitDialog() { CDialog::OnInitDialog(); WSADATA wsaData; WORD version = MAKEWORD(1,1); int ret=WSAStartup(version,&wsaData); if(ret!=0) { MessageBox("Init Error"); return FALSE; } pDlg = this; return TRUE; // return TRUE unless you set the focus to a control // 异常: OCX 属性页应返回 FALSE } void CClientDialog::OnPaint() { CPaintDC dc(this); // device context for painting CDialog::OnPaint(); // 显示矩形框 if (m_isDraw) { CClientDC dc(this); CPen pen(PS_SOLID, 2, RGB(234,23,53)); CPen *pOldPen=dc.SelectObject(&pen); CBrush *pBrush=CBrush::FromHandle((HBRUSH)GetStockObject(NULL_BRUSH)); CBrush *pOldBrush=dc.SelectObject(pBrush); dc.Rectangle(CRect(m_begPnt, m_endPnt)); dc.SelectObject(pOldPen); dc.SelectObject(pOldBrush); } } void CClientDialog::OnOK() { UpdateData(); bAppend=true; InitSock(); //m_cStop.EnableWindow(true); //m_cOk.EnableWindow(false); } void CClientDialog::OnCancel() { // TODO: Add extra cleanup here bAppend=false; if(m_hSocketClient) { closesocket(m_hSocketClient); } WSACleanup(); CDialog::OnCancel(); } void CClientDialog::InitSock()// { if(m_hSocketClient)//如果已经创建,先关闭 { closesocket(m_hSocketClient); m_hSocketClient=NULL; } else { // SOCK_STREAM 提供面向连接的 // m_hSocketClient=socket(AF_INET,SOCK_STREAM,0); m_hSocketClient = socket(AF_INET,SOCK_DGRAM,0); ASSERT(m_hSocketClient);// } // 创建套接字 //SOCKADDR_IN addrSrv; //addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); //addrSrv.sin_family = AF_INET; //addrSrv.sin_port = htons(6000); //int len = sizeof(SOCKADDR); m_addrSrv.sin_family=AF_INET; //表示在INT上通信 m_addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); m_addrSrv.sin_port = htons(m_iPort); int len = sizeof(SOCKADDR); ////////////////////////////////////////////////////////////////////////// char getContact[] = {"Hi"}; sendto(m_hSocketClient, getContact, strlen(getContact)+1, 0, (SOCKADDR*)&m_addrSrv, len); ////////////////////////////////////////////////////////////////////////// int nRecvBuf=64*1024;//设置为64K setsockopt(m_hSocketClient, SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int)); // while(1) // RevFile(m_hSocketClient); // 开一个线程监听! AfxBeginThread(ListenThread, this); } void CClientDialog::ShowPIC(char* buf,int iSize) { if(iSize==0||buf==NULL) return; ASSERT(buf!=NULL); CDC *pDC=m_cImage.GetDC(); ASSERT(pDC!=NULL); CPicture pic; pic.Load(buf,iSize); CRect rect; m_cImage.GetClientRect(&rect); rect.top+=15; rect.left+=5; rect.right-=5; rect.bottom-=5; pic.Render(pDC,rect); } void CClientDialog::RevFile(const SOCKET& s) { char buffer[1024]; char temp[1024]; memset(buffer,0,sizeof(buffer)); memset(temp,0,sizeof(temp)); int len = sizeof(SOCKADDR); int rcv = recvfrom(s, buffer, 1024, 0, (SOCKADDR*)&m_addrSrv, &len); long lFileSize = atol(buffer); //文件大小; m_iSize = lFileSize; TRACE("File length = %i/n", lFileSize); int i(0); if(pBuffer!=NULL) { delete [] pBuffer; pBuffer=NULL; } pBuffer=new char[lFileSize+1]; pBuffer[0]='/0'; char* pBuf = pBuffer; long iTemp = 0; memset(buffer,0,sizeof(buffer)); while (1) { rcv = recvfrom(m_hSocketClient, buffer, 1024, 0, (SOCKADDR*)&m_addrSrv, &len); if (rcv <= 0) { break; } for (int i=0; i<rcv; i++) { pBuf[i+iTemp] = buffer[i]; } iTemp += rcv; // TRACE("Received = %3d/n", iTemp); if (rcv < 1024 && iTemp==lFileSize) { iTemp = 0; // 通过内存流显示图片 ShowPIC( pBuf, lFileSize); // std::ofstream outFile("c://test.jpg"); // outFile.write(pBuf, lFileSize); // outFile.close(); break; } } // m_cState.SetWindowText("文件接收成功!"); /*shutdown(s, SD_BOTH); closesocket(s);*/ } bool CClientDialog::isInImageRegion(const CPoint& point) { RECT imageRect; GetDlgItem(IDC_IMAGE)->GetWindowRect(&imageRect); this->ScreenToClient(&imageRect); //TRACE("=====================/n"); //TRACE("%3d %3d %3d %3d/n", imageRect.left, imageRect.top, imageRect.right, imageRect.bottom); //TRACE("%3d %3d/n", point.x, point.y); //TRACE("=====================/n"); return point.x > imageRect.left && point.x < imageRect.right && point.y > imageRect.top && point.y < imageRect.bottom; } void CClientDialog::OnLButtonDown(UINT nFlags, CPoint point) { if ( isInImageRegion(point) ) { m_begPnt.x = point.x; m_begPnt.y = point.y; m_isDraw = true; } CDialog::OnLButtonDown(nFlags, point); } void CClientDialog::OnLButtonUp(UINT nFlags, CPoint point) { m_isDraw = false; CDialog::OnLButtonUp(nFlags, point); } void CClientDialog::OnMouseMove(UINT nFlags, CPoint point) { if (!isInImageRegion(point)) return; if (m_isDraw) { m_endPnt.x = point.x; m_endPnt.y = point.y; Invalidate(FALSE); // draw... } CDialog::OnMouseMove(nFlags, point); }  

到现在,只是定义了ClientDialog类,并没有定义该类的实例,也就是说当运行控件以后看到的仍然是一个偌大的椭圆。所以需要修改ctrl类:

在ctr类中定义成员指针ClientDialog* m_app; ctrl的构造函数中初始化列表中置m_app为NULL,类视图下右击ctrl类,属性,重载OnCreate函数。在OnCreate函数创建对话框,OnDraw中显示,析构函数中释放:

ctrl类的关键代码:

CocxClientCtrl::CocxClientCtrl() : m_app(NULL) { InitializeIIDs(&IID_DocxClient, &IID_DocxClientEvents); } CocxClientCtrl::~CocxClientCtrl() { if (m_app) delete m_app; m_app = 0; } void CocxClientCtrl::OnDraw( CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid) { if (!pdc) return; m_app->MoveWindow(rcBounds); m_app->ShowWindow(SW_SHOW); } void CocxClientCtrl::DoPropExchange(CPropExchange* pPX) { ExchangeVersion(pPX, MAKELONG(_wVerMinor, _wVerMajor)); COleControl::DoPropExchange(pPX); } void CocxClientCtrl::OnResetState() { COleControl::OnResetState(); } // CocxClientCtrl::AboutBox - 向用户显示“关于”框 void CocxClientCtrl::AboutBox() { CDialog dlgAbout(IDD_ABOUTBOX_OCXCLIENT); dlgAbout.DoModal(); } // CocxClientCtrl 消息处理程序 int CocxClientCtrl::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (COleControl::OnCreate(lpCreateStruct) == -1) return -1; m_app = new CClientDialog; m_app->Create(IDD_CLIENT, this); return 0; }  

调试activex设置

工程属性 / 调试 / 命令:C:/Program Files/Internet Explorer/iexplore.exe

工程属性 / 调试 / 命令参数 : E:/RYF resource/UDPChat/a.html

该html内容为:

<HTML> <HEAD> <TITLE>Test</TITLE> </HEAD> <BODY> <OBJECT ID="MyActiveX" WIDTH=600 HEIGHT=500 CLASSID="CLSID:BEB347E2-D2EC-4C39-8593-0B76D36DEB09"> <PARAM NAME="_Version" VALUE="65536"> <PARAM NAME="_ExtentX" VALUE="4657"> <PARAM NAME="_ExtentY" VALUE="4075"> <PARAM NAME="_StockProps" VALUE="0"> </OBJECT> </BODY> </HTML>

注意html中需要一个CLSID,该ID为activex工程下,系统自动生成的idl文件中的:

// CocxClientCtrl 的类信息 [ uuid(BEB347E2-D2EC-4C39-8593-0B76D36DEB09), helpstring("ocxClient Control"), control ] coclass ocxClient { [default] dispinterface _DocxClient; [default, source] dispinterface _DocxClientEvents; }; 

这里的这个ID。。。参见我之前一篇blog:http://blog.csdn.net/dizuo/archive/2011/04/18/6331279.aspx

配置好以后,用IE调试就可以看到对话框出现。。。

 

说明一点:基于UDP连接,所以客户端点击“连接服务器”按钮以后,客户端先发送一个消息给服务器,跟服务器建立链接,然后服务器取得客户端IP地址,开始发送视频数据。代码中服务器是每隔300ms发送一次。。。一秒3帧左右。。。

最终效果:

gif在线制作:http://gif.55.la/

 

代码可以到我资源下载: http://download.csdn.net/source/3393178

 

你可能感兴趣的:([置顶] 基于udp的监视系统示例)