您的位置:首页 > 编程语言

基于udp的监视系统示例

2011-06-24 21:50 267 查看

前言

想法来源: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
 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息