2.5 云台控制方案
云台是一种硬件设备,用于调整摄像头的位置。通常,摄像头安装在云台之上,通过云台的转动实现摄像头水平、垂直方向的改变,从而实现对监控位置的调整。云台除了能够对摄像头进行转动,还可以控制摄像头的焦聚、光圈和变倍等,这样可以调整摄像头的捕捉范围和清晰度。本节将向您介绍有关云台控制方面的知识。
2.5.1 云台设备安装
云台通常是与摄像头结合在一起的,摄像头提供额外的线路用于云台设备与计算机的连接。该线路通常以Com端口的形式连接计算机,在线路的一端连接有云台控制转换器,如图2.47显示。

图2.47 云台控制转换器
云台控制转换器转换器是以Com端口的形式连接计算机的,图2.48显示了云台控制转换器与计算机的连接。

图2.48 云台控制转换器连接计算机
2.5.2 云台控制分析
云台是通过云台解码器与计算机串口或并口相连的,程序是通过向云台解码器发送指令来实现云台控制的。这里的指令是由云台控制协议确定的。不同的厂家,云台控制协议也不尽相同。以Pelco-D2400为例,其命令格式由7个字节构成。第一个字节为同步字节,始终为FFH,第2个字节为地址码,也就是摄像头的逻辑地址号,范围在00H到FFH之间,是在安装摄像头时手动设置的,该值一定要正确,否则命令不会执行。第3、4个字节表示指令码,即执行哪项操作,例如,向上、下、左、右移动摄像头等。第5、6个字节表示数据码,用于指定摄像头的水平、垂直方向移动速度。第7个字节为校验码,它是由第2、3、4、5、6个字节数据之和与100H取模获得的。
此外,在执行某一命令后,应执行停止命令,否则命令执行的动作会一直执行。例如,将摄像头向上移动。如果不发送停止命令,摄像头就会一直向上移动。在Pelco-D2400协议的停止命令中,第一个字节为FFH,第2个字节为地址码,第3、4、5、6个字节为00F,第7个字节为第2个字节数据与100H的模。
只要知道了控制命令,就可以通过向串口发送这些命令来控制云台了。例如,下面的代码实现了云台的向下移动。
void CCloudMsgDlg::OnDown()
{
//向下移动
unsigned char data[7]= {0xff,0x02,0x00,0x10,0x00,0xff,0x12};
VARIANT vt;
SAFEARRAY* pSafe;
SAFEARRAYBOUND band;
band.cElements =7;
band.lLbound = 0;
pSafe = SafeArrayCreate(VT_UI1,1,&band);
for (long i = 0; i<7; i++)
{
SafeArrayPutElement(pSafe,&i,&data[i]);
}
vt.vt= VT_ARRAY |VT_UI1;
vt.parray = pSafe;
m_Com.SetOutput((COleVariant)vt);
//停止移动
unsigned char stopdata[7]= {0xff,0x02,0x00,0x00,0x00,0x00,0x02};
for (i = 0; i<7; i++)
{
SafeArrayPutElement(pSafe,&i,(void*)&stopdata[i]);
}
vt.vt= VT_ARRAY |VT_UI1;
vt.parray = pSafe;
m_Com.SetOutput((COleVariant)vt);
}
在设计云台控制程序时,为了使程序能够灵活地控制云台,不要向云台解码器发送固定的指令,而应提供接口让用户针对不同的云台控制协议自行设置云台控制码。下面笔者以一个具体实例介绍云台控制程序的设计。效果如图2.49、图2.50所示。

图2.49 云台控制效果图

图2.50 控制码设置效果图
程序设计具体步骤如下:
实例位置:光盘\mr\2\2.5\2.5.2\01
(1)设计一个INI文件,其中存储云台控制的各种参数。
[端口设置]
端口号 =1
环境设置 =2400,n,8,1
[使用字节数]
字节数 =7
[控制项]
控制数 =13
[1] --上
字节1 =0xff
字节2 =0x2
字节3 =0x0
字节4 =0x8
字节5 =0x0
字节6 =0xff
字节7 =0x10
字节8 =
[2] --下
字节1 = 0xff
字节2 = 0x02
字节3 = 0x00
字节4 = 0x10
字节5 = 0x00
字节6 = 0xff
字节7 = 0x12
字节8 =
[3] --左
字节1 = 0xff
字节2 = 0x02
字节3 = 0x00
字节4 = 0x04
字节5 = 0xff
字节6 = 0x00
字节7 = 0x06
字节8 =
[4] --右
字节1 = 0xff
字节2 = 0x02
字节3 = 0x00
字节4 = 0x02
字节5 = 0xff
字节6 = 0x00
字节7 = 0x04
字节8 =
[5] --减小聚焦
字节1 = 0xff
字节2 = 0x02
字节3 = 0x00
字节4 = 0x80
字节5 = 0x00
字节6 = 0x00
字节7 = 0x82
字节8 =
[6] --增加聚焦
字节1 = 0xff
字节2 = 0x02
字节3 = 0x01
字节4 = 0x00
字节5 = 0x00
字节6 = 0x00
字节7 = 0x03
字节8 =
[7] --减小对焦
字节1 = 0xff
字节2 = 0x02
字节3 = 0x00
字节4 = 0x20
字节5 = 0x00
字节6 = 0x00
字节7 = 0x22
字节8 =
[8] --增加对焦
字节1 = 0xff
字节2 = 0x02
字节3 = 0x00
字节4 = 0x40
字节5 = 0x00
字节6 = 0x00
字节7 = 0x42
字节8 =
[9] --减小光圈
字节1 = 0xff
字节2 = 0x02
字节3 = 0x02
字节4 = 0x00
字节5 = 0x00
字节6 = 0x00
字节7 = 0x04
字节8 =
[10] --增加光圈
字节1 = 0xff
字节2 = 0x02
字节3 = 0x04
字节4 = 0x00
字节5 = 0x00
字节6 = 0x00
字节7 = 0x06
字节8 =
[11] --关闭雨刷
字节1 = 0xff
字节2 = 0x02
字节3 = 0x00
字节4 = 0x00
字节5 = 0x00
字节6 = 0x00
字节7 = 0x00
字节8 =
[12] --打开雨刷
字节1 = 0xff
字节2 = 0x02
字节3 = 0x00
字节4 = 0x00
字节5 = 0x00
字节6 = 0x00
字节7 = 0x00
字节8 =
[13] --复位
字节1 = 0xff
字节2 = 0x02
字节3 = 0x00
字节4 = 0x00
字节5 = 0x00
字节6 = 0x00
字节7 = 0x02
字节8 =
(2)创建一个基于对话框的工程,向对话框中添加按钮控件。
(3)在对话框中鼠标右键单击,在弹出的快捷菜单中选择“Insert ActiveX Control”菜单项,打开“Insert ActiveX Control”窗口,如图2.51、图2.52所示。

图2.51 快捷菜单

图2.52 “Insert ActiveX Control”窗口
(4)在“Insert ActiveX Control”窗口中选择“Microsoft Communications Control”选项,将MSCom控件导入到对话框中,如图2.53所示。

图2.53 云台控制对话框
(5)打开类向导窗口,选择“Member Variables”选项卡,为控件命名,如图2.54所示。

图2.54 成员变量窗口
(6)向对话框类中添加成员变量,记录Com端口信息及云台控制码。
unsigned char (*m_pData) [MAXNUM]; //存储云台控制码
int m_Len ; //云台协议使用的字节数
int m_ActoinCount; //云台控制动作数
int m_Port; //Com端口
CString m_Setting; //环境
(7)向对话框中添加控制云台的各个方法。
//向上移动
void CCloudMsgDlg::OnUp()
{
// unsigned char data[8]= {0xff,0x02,0x00,0x08,0x00,0xff,0x10};
VARIANT vt;
SAFEARRAY* pSafe;
SAFEARRAYBOUND band;
band.cElements =m_Len;
band.lLbound = 0;
pSafe = SafeArrayCreate(VT_UI1,1,&band);
for (long i = 0; i<m_Len; i++)
{
SafeArrayPutElement(pSafe,&i,(void*)&m_pData[0][i]);
}
vt.vt= VT_ARRAY |VT_UI1;
vt.parray = pSafe;
m_Com.SetOutput((COleVariant)vt);
}
//停止移动
void CCloudMsgDlg::OnReset()
{
VARIANT vt;
SAFEARRAY* pSafe;
SAFEARRAYBOUND band;
band.cElements =m_Len;
band.lLbound = 0;
pSafe = SafeArrayCreate(VT_UI1,1,&band);
//unsigned char stopdata[7]= {0xff,0x02,0x00,0x00,0x00,0x00,0x02};
for (long i = 0; i<m_Len; i++)
{
SafeArrayPutElement(pSafe,&i,(void*)&m_pData[12][i]);
}
vt.vt= VT_ARRAY |VT_UI1;
vt.parray = pSafe;
m_Com.SetOutput((COleVariant)vt);
}
//向下移动
void CCloudMsgDlg::OnDown()
{
// unsigned char data[7]= {0xff,0x02,0x00,0x10,0x00,0xff,0x12};
VARIANT vt;
SAFEARRAY* pSafe;
SAFEARRAYBOUND band;
band.cElements =m_Len;
band.lLbound = 0;
pSafe = SafeArrayCreate(VT_UI1,1,&band);
for (long i = 0; i<m_Len; i++)
{
SafeArrayPutElement(pSafe,&i,(void*)&m_pData[1][i]);
}
vt.vt= VT_ARRAY |VT_UI1;
vt.parray = pSafe;
m_Com.SetOutput((COleVariant)vt);
}
//向左移动
void CCloudMsgDlg::OnLeft()
{
//unsigned char data[7]= {0xff,0x02,0x00,0x04,0xff,0x00,0x06};
VARIANT vt;
SAFEARRAY* pSafe;
SAFEARRAYBOUND band;
band.cElements =m_Len;
band.lLbound = 0;
pSafe = SafeArrayCreate(VT_UI1,1,&band);
for (long i = 0; i<m_Len; i++)
{
SafeArrayPutElement(pSafe,&i,(void*)&m_pData[2][i]);
}
vt.vt= VT_ARRAY |VT_UI1;
vt.parray = pSafe;
m_Com.SetOutput((COleVariant)vt);
}
//向右移动
void CCloudMsgDlg::OnRight()
{
//unsigned char data[7]= {0xff,0x02,0x00,0x02,0xff,0x00,0x04};
VARIANT vt;
SAFEARRAY* pSafe;
SAFEARRAYBOUND band;
band.cElements =m_Len;
band.lLbound = 0;
pSafe = SafeArrayCreate(VT_UI1,1,&band);
for (long i = 0; i<m_Len; i++)
{
SafeArrayPutElement(pSafe,&i,(void*)&m_pData[3][i]);
}
vt.vt= VT_ARRAY |VT_UI1;
vt.parray = pSafe;
m_Com.SetOutput((COleVariant)vt);
}
//增加聚焦
void CCloudMsgDlg::OnInFoci()
{
// unsigned char data[7]= {0xff,0x02,0x01,0x00,0x00,0x00,0x03};
VARIANT vt;
SAFEARRAY* pSafe;
SAFEARRAYBOUND band;
band.cElements =m_Len;
band.lLbound = 0;
pSafe = SafeArrayCreate(VT_UI1,1,&band);
for (long i = 0; i<m_Len; i++)
{
SafeArrayPutElement(pSafe,&i,(void*)&m_pData[5][i]);
}
vt.vt= VT_ARRAY |VT_UI1;
vt.parray = pSafe;
m_Com.SetOutput((COleVariant)vt);
}
//减小聚焦
void CCloudMsgDlg::OnReFoci()
{
//unsigned char data[7]= {0xff,0x02,0x00,0x80,0x00,0x00,0x82};
VARIANT vt;
SAFEARRAY* pSafe;
SAFEARRAYBOUND band;
band.cElements =m_Len;
band.lLbound = 0;
pSafe = SafeArrayCreate(VT_UI1,1,&band);
for (long i = 0; i<m_Len; i++)
{
SafeArrayPutElement(pSafe,&i,(void*)&m_pData[4][i]);
}
vt.vt= VT_ARRAY |VT_UI1;
vt.parray = pSafe;
m_Com.SetOutput((COleVariant)vt);
}
//倍长增
void CCloudMsgDlg::OnInLen()
{
//unsigned char data[7]= {0xff,0x02,0x00,0x40,0x00,0x00,0x42};
VARIANT vt;
SAFEARRAY* pSafe;
SAFEARRAYBOUND band;
band.cElements =m_Len;
band.lLbound = 0;
pSafe = SafeArrayCreate(VT_UI1,1,&band);
for (long i = 0; i<m_Len; i++)
{
SafeArrayPutElement(pSafe,&i,(void*)&m_pData[7][i]);
}
vt.vt= VT_ARRAY |VT_UI1;
vt.parray = pSafe;
m_Com.SetOutput((COleVariant)vt);
}
//倍长减
void CCloudMsgDlg::OnReLen()
{
//unsigned char data[7]= {0xff,0x02,0x00,0x20,0x00,0x00,0x22};
VARIANT vt;
SAFEARRAY* pSafe;
SAFEARRAYBOUND band;
band.cElements =m_Len;
band.lLbound = 0;
pSafe = SafeArrayCreate(VT_UI1,1,&band);
for (long i = 0; i<m_Len; i++)
{
SafeArrayPutElement(pSafe,&i,(void*)&m_pData[6][i]);
}
vt.vt= VT_ARRAY |VT_UI1;
vt.parray = pSafe;
m_Com.SetOutput((COleVariant)vt);
}
//光圈减
void CCloudMsgDlg::OnReAperture()
{
//unsigned char data[7]= {0xff,0x02,0x02,0x00,0x00,0x00,0x04};
VARIANT vt;
SAFEARRAY* pSafe;
SAFEARRAYBOUND band;
band.cElements =m_Len;
band.lLbound = 0;
pSafe = SafeArrayCreate(VT_UI1,1,&band);
for (long i = 0; i<m_Len; i++)
{
SafeArrayPutElement(pSafe,&i,(void*)&m_pData[8][i]);
}
vt.vt= VT_ARRAY |VT_UI1;
vt.parray = pSafe;
m_Com.SetOutput((COleVariant)vt);
}
//光圈增
void CCloudMsgDlg::OnInAperture()
{
// unsigned char data[8]= {0xff,0x02,0x04,0x00,0x00,0x00,0x06};
VARIANT vt;
SAFEARRAY* pSafe;
SAFEARRAYBOUND band;
band.cElements =m_Len;
band.lLbound = 0;
pSafe = SafeArrayCreate(VT_UI1,1,&band);
for (long i = 0; i<m_Len; i++)
{
SafeArrayPutElement(pSafe,&i,(void*)&m_pData[9][i]);
}
vt.vt= VT_ARRAY |VT_UI1;
vt.parray = pSafe;
m_Com.SetOutput((COleVariant)vt);
}
//雨刷减
void CCloudMsgDlg::OnReBrush()
{
//unsigned char data[7]= {0xff,0x00,0x00,0x00,0x00,0x00,0x00};
VARIANT vt;
SAFEARRAY* pSafe;
SAFEARRAYBOUND band;
band.cElements =m_Len;
band.lLbound = 0;
pSafe = SafeArrayCreate(VT_UI1,1,&band);
for (long i = 0; i<m_Len; i++)
{
SafeArrayPutElement(pSafe,&i,(void*)&m_pData[10][i]);
}
vt.vt= VT_ARRAY |VT_UI1;
vt.parray = pSafe;
m_Com.SetOutput((COleVariant)vt);
}
//雨刷增
void CCloudMsgDlg::OnInBrush()
{
// unsigned char data[7]= {0xff,0x00,0x00,0x00,0x00,0x00,0x00};
VARIANT vt;
SAFEARRAY* pSafe;
SAFEARRAYBOUND band;
band.cElements =m_Len;
band.lLbound = 0;
pSafe = SafeArrayCreate(VT_UI1,1,&band);
for (long i = 0; i<m_Len; i++)
{
SafeArrayPutElement(pSafe,&i,(void*)&m_pData[11][i]);
}
vt.vt= VT_ARRAY |VT_UI1;
vt.parray = pSafe;
m_Com.SetOutput((COleVariant)vt);
}
(8)从CButton类派生一个子类CStageButton,目的是在按下按钮时不停地控制云台,在释放按钮时停止对云台的控制。CStageButton类声明代码如下:
//按钮控制类型,上 下 左 右 自动,焦聚减,焦聚增,倍长减,倍长增,光圈减,光圈增,雨刷减,雨刷增
enum ControlType {ctUp,ctDown,ctLeft,ctRight,ctAuto,ctReFoci,
ctInFoci,ctReLen,ctInLen,ctReAperture,ctInAperture,ctReBrush,ctInBrush};
class CStageButton : public CButton
{
// Construction
public:
CStageButton();
// Attributes
public:
ControlType m_Type;
// Operations
public:
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CStageButton)
//}}AFX_VIRTUAL
// Implementation
public:
virtual ~CStageButton();
// Generated message map functions
protected:
//{{AFX_MSG(CStageButton)
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
CStageButton类实现代码如下:
//在鼠标按钮时根据按钮的类型标识,调用不同的方法
void CStageButton::OnLButtonDown(UINT nFlags, CPoint point)
{
switch (m_Type)
{
case ctUp:
{
((CCloudMsgDlg*)GetParent())->OnUp();
break;
}
case ctDown:
{
((CCloudMsgDlg*)GetParent())->OnDown();
break;
}
case ctLeft:
{
((CCloudMsgDlg*)GetParent())->OnLeft();
break;
}
case ctRight:
{
((CCloudMsgDlg*)GetParent())->OnRight();
break;
}
case ctInFoci:
{
((CCloudMsgDlg*)GetParent())->OnInFoci();
break;
}
case ctReFoci:
{
((CCloudMsgDlg*)GetParent())->OnReFoci();
break;
}
case ctInLen:
{
((CCloudMsgDlg*)GetParent())->OnInLen();
break;
}
case ctReLen:
{
((CCloudMsgDlg*)GetParent())->OnReLen();
break;
}
case ctInAperture:
{
((CCloudMsgDlg*)GetParent())->OnInAperture();
break;
}
case ctReAperture:
{
((CCloudMsgDlg*)GetParent())->OnReAperture();
break;
}
case ctInBrush:
{
((CCloudMsgDlg*)GetParent())->OnInBrush();
break;
}
case ctReBrush:
{
((CCloudMsgDlg*)GetParent())->OnReBrush();
break;
}
default:
{
//......
break;
}
}
CButton::OnLButtonDown(nFlags, point);
}
//在释放鼠标按钮时停止对云台的控制
void CStageButton::OnLButtonUp(UINT nFlags, CPoint point)
{
((CCloudMsgDlg*)GetParent())->OnReset();
CButton::OnLButtonUp(nFlags, point);
}
(9)在对话框初始化时从INI文件中读取云台参数信息,并设置Com端口。
BOOL CCloudMsgDlg::OnInitDialog()
{
CDialog::OnInitDialog();
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL)
{
CString strAboutMenu;
strAboutMenu.LoadString(IDS_ABOUTBOX);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
}
SetIcon(m_hIcon, TRUE);
SetIcon(m_hIcon, FALSE);
m_ButtonUp.m_Type = ctUp;
m_ButtonDown.m_Type = ctDown;
m_ButtonLeft.m_Type = ctLeft;
m_ButtonRight.m_Type = ctRight;
m_InFoci.m_Type = ctInFoci;
m_ReFoci.m_Type = ctReFoci;
m_InLen.m_Type = ctInLen;
m_ReLen.m_Type = ctReLen;
m_InAperture.m_Type = ctInAperture;
m_ReAperture.m_Type = ctReAperture;
m_InBrush.m_Type = ctInBrush;
m_ReBrush.m_Type = ctReBrush;
m_Len = GetPrivateProfileInt("使用字节数","字节数",8,"./stage.ini");
m_ActoinCount = GetPrivateProfileInt("控制项","控制数",13,"./stage.ini");
m_pData = new unsigned char[m_ActoinCount][MAXNUM];
m_Port = GetPrivateProfileInt("端口设置","端口号",1,"./stage.ini");
GetPrivateProfileString("端口设置","环境设置","9600,n,8,1"
,m_Setting.GetBuffer(0),MAX_PATH,"./stage.ini");
int data;
char buff[20] = {0};
char var[20] = {0};
for (int i = 0 ; i<m_ActoinCount ; i++)
for (int j = 0 ; j <m_Len; j++)
{
char section[20] = "字节";
itoa(i+1,var,10);
itoa(j+1,buff,10);
strcat(section,buff);
data = GetPrivateProfileInt(var,section,0,"./stage.ini");
m_pData[i][j] = data;
}
//设置端口信息,并打开端口
m_Com.SetSettings(m_Setting);
m_Com.SetOutBufferSize(512);
m_Com.SetCommPort(m_Port);
m_Com.SetSThreshold(0);
m_Com.SetPortOpen(TRUE);
return TRUE;
}
(10)在工作区的“Class View”选项卡中鼠标右键单击根节点,在弹出的快捷菜单中选择“New Form”菜单项,打开“NewForm”窗口,如图2.55、图2.56所示。

图2.55 新建对话框快捷菜单

图2.56 “NewForm”窗口
(11)在“Name”编辑框中输入对话框的类名称,单击“OK”按钮创建对话框。
(12)在新建立的对话框中添加按钮、组合框、编辑框、静态文本等控件,如图2.57所示。

图2.57 控制码设置对话框
(13)按“Ctrl+W”组合键打开类向导窗口,选择“Member Variables”选项卡,为对话框中的控件命名,如图2.58所示。

图2.58 控制码设置对话框控件命名窗口
(14)向对话框中添加如下成员变量。
CCloudMsgDlg* m_pMain; //主窗口指针
CEdit* m_pEdit[8]; //控件数组
(15)在对话框初始化时为控件和成员变量赋值。
BOOL CControlForm::OnInitDialog()
{
CDialog::OnInitDialog();
m_pEdit[0] = &m_Byte1;
m_pEdit[1] = &m_Byte2;
m_pEdit[2] = &m_Byte3;
m_pEdit[3] = &m_Byte4;
m_pEdit[4] = &m_Byte5;
m_pEdit[5] = &m_Byte6;
m_pEdit[6] = &m_Byte7;
m_pEdit[7] = &m_Byte8;
m_pMain = (CCloudMsgDlg*)AfxGetApp()->GetMainWnd();
m_Port.SetCurSel(m_pMain->m_Port-1);
m_Setting.SetWindowText(m_pMain->m_Setting);
char data[20];
itoa(m_pMain->m_ActoinCount,data,10);
m_ActionNum.SetWindowText(data);
char buff[20];
itoa(m_pMain->m_Len,buff,10);
m_ByteNum.SetWindowText(buff);
return TRUE;
}
(16)处理“设置端口”按钮的单击事件,将信息保存到INI文件中,并更新Com端口信息。
void CControlForm::OnPortset()
{
//设置端口信息
CString port;
m_Port.GetWindowText(port);
if (!port.IsEmpty())
{
m_pMain->m_Port = m_Port.GetCurSel()+1;
port.Format("%i",m_pMain->m_Port);
WritePrivateProfileString("端口设置","端口号",port,"./stage.ini");
m_pMain->m_Com.SetPortOpen(FALSE);
m_pMain->m_Com.SetCommPort(m_pMain->m_Port);
m_pMain->m_Com.SetPortOpen(TRUE);
}
CString setting;
m_Setting.GetWindowText(setting);
if (!setting.IsEmpty())
{
m_pMain->m_Setting = setting;
WritePrivateProfileString("端口设置","环境设置",setting,"./stage.ini");
m_pMain->m_Com.SetPortOpen(FALSE);
m_pMain->m_Com.SetSettings(setting);
m_pMain->m_Com.SetPortOpen(TRUE);
}
}
(17)处理“控制动作”组合框选项改变时的事件,显示相应的云台控制码。
void CControlForm::OnSelchangeActions()
{
int index = m_Actions.GetCurSel();
if (index != -1)
{
for (int j = 0 ; j<m_pMain->m_Len; j++)
for (int i = 0 ; i< m_pMain->m_ActoinCount; i++)
{
char buff[20] = {0};
char prex[20]= "0x";
itoa(m_pMain->m_pData[index][j],buff,16);
strcat(prex,buff);
m_pEdit[j]->SetWindowText(prex);
}
}
}
(18)处理“设置”按钮的单击事件,将云台控制码保存到INI文件中。
void CControlForm::OnCmdset()
{
//设置控制码
int index = m_Actions.GetCurSel();
if (index != -1)
{
for (int j = 0 ; j<m_pMain->m_Len; j++)
{
char data[20];
m_pEdit[j]->GetWindowText(data,20);
char* stop;
int x = strtol(data,&stop,16);
m_pMain->m_pData[index][j] = strtol(data,&stop,16);
char sec[20] = {0};
char key[20] = "字节";
char num[10] = {0};
itoa(j+1,num,10);
strcat(key,num);
itoa(index+1,sec,10);
WritePrivateProfileString(sec,key,data,"./stage.ini");
}
}
//设置动作数和协议使用的字节数
CString num;
m_ByteNum.GetWindowText(num);
if (!num.IsEmpty())
{
WritePrivateProfileString("使用字节数","字节数",num,"./stage.ini");
}
CString actions;
m_ActionNum.GetWindowText(actions);
if (! actions.IsEmpty())
{
WritePrivateProfileString("控制项","控制数",actions,"./stage.ini");
}
CDialog::OnCancel();
}
2.5.3 定时广角监控方案
在开发监控系统时,可能要求摄像头捕捉的范围更广泛,这样就不能使摄像头始终捕捉一个方向,而是时时进行全方位的捕捉。为此,在设计程序时应当考虑人为控制和系统自动控制两个方面。当有人值班时,他可以控制摄像头捕捉的方位,当无人值班时,系统要完成各个方位的自动捕捉。
实际上,系统实现的关键是对云台的自动控制。当无人值班时,可以让摄像头由左向右、由上到下等顺序按一定速度转动。这里需要注意的是转动的速度要适宜,即不要太快,也不要太慢。如果太快的话,捕捉的画面质量较差,起不到监控的作用了。如果太慢的话,当有人在摄像头监控范围的某一个区域进行非法活动时,摄像头可能不能及时捕捉,使得监控形同虚设了。
为了在系统自动捕捉时用户能够进行界面操作。笔者通过创建一个线程来执行捕捉的动作,当用户需要停止系统自动捕捉,只要终止线程就可以了。效果如图2.59所示。

图2.59 定时广角监控方案
程序设计具体步骤如下:
实例位置:光盘\mr\2\2.5\2.5.3\01
(1)创建一个基于对话框的工程,向对话框中添加标签、按钮、日期等控件,如图2.60所示。

图2.60 对话框资源
(2)向对话框类中添加如下成员变量。
//成员变量
unsigned char (*m_pData) [100]; //端口数据
int m_Len ; //云台协议使用的字节数
int m_ActoinCount; //云台控制动作数
int m_Port; //端口号
CString m_Setting; //端口信息
BOOL m_Tail; //是否开始监控
HANDLE m_hThread; //线程句柄
(3)在对话框初始化时从INI文件中读取基础信息。
BOOL CTimeDIYDlg::OnInitDialog()
{
CDialog::OnInitDialog();
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL)
{
CString strAboutMenu;
strAboutMenu.LoadString(IDS_ABOUTBOX);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
}
SetIcon(m_hIcon, TRUE);
SetIcon(m_hIcon, FALSE);
CTime time = CTime::GetCurrentTime();
m_Time.SetTime(&time);
m_Len = GetPrivateProfileInt("使用字节数","字节数",8,"./stage.ini");
m_ActoinCount = GetPrivateProfileInt("控制项","控制数",13,"./stage.ini");
m_pData = new unsigned char[m_ActoinCount][100];
m_Port = GetPrivateProfileInt("端口设置","端口号",1,"./stage.ini");
GetPrivateProfileString("端口设置","环境设置
","9600,n,8,1",m_Setting.GetBuffer(0),MAX_PATH,"./stage.ini");
int data;
char buff[20] = {0};
char var[20] = {0};
for (int i = 0 ; i<m_ActoinCount ; i++)
for (int j = 0 ; j <m_Len; j++)
{
char section[20] = "字节";
itoa(i+1,var,10);
itoa(j+1,buff,10);
strcat(section,buff);
data = GetPrivateProfileInt(var,section,0,"./stage.ini");
m_pData[i][j] = data;
}
//设置端口信息,并打开端口
m_Com.SetSettings(m_Setting);
m_Com.SetOutBufferSize(512);
m_Com.SetCommPort(m_Port);
m_Com.SetSThreshold(0);
m_Com.SetPortOpen(TRUE);
if (VCAInitSdk(m_hWnd))
{
VCARegVidCapCallBack(0,CapCallBack);
VCAOpenDevice(0,m_Panel.m_hWnd);
VCAStartVideoPreview(0);
}
m_Tail = FALSE;
SetTimer(1,300,NULL);
return TRUE;
}
(4)处理对话框的WM_WINDOWPOSCHANGED消息,在对话框位置改变时更新视频预览窗口。
void CTimeDIYDlg::OnWindowPosChanged(WINDOWPOS FAR* lpwndpos)
{
CDialog::OnWindowPosChanged(lpwndpos);
VCAUpdateOverlayWnd(m_Panel.m_hWnd);
VCAUpdateVideoPreview(0,m_Panel.m_hWnd);
}
(5)编写线程函数,实现系统的自动监控。
DWORD WINAPI ThreadProc(LPVOID lpParameter )
{
CTimeDIYDlg* pDlg = (CTimeDIYDlg*)lpParameter;
while (true)
{
//进行云台控制
VARIANT vt;
SAFEARRAY* pSafe;
SAFEARRAYBOUND band;
band.cElements =pDlg->m_Len;
band.lLbound = 0;
pSafe = SafeArrayCreate(VT_UI1,1,&band);
//向上
for (long i = 0; i<pDlg->m_Len; i++)
{
SafeArrayPutElement(pSafe,&i,(void*)&pDlg->m_pData[0][i]);
}
vt.vt= VT_ARRAY |VT_UI1;
vt.parray = pSafe;
pDlg->m_Com.SetOutput((COleVariant)vt);
Sleep(15000);
//向左
vt.vt= VT_ARRAY |VT_UI1;
vt.parray = pSafe;
for ( i = 0; i<pDlg->m_Len; i++)
{
SafeArrayPutElement(pSafe,&i,(void*)&pDlg->m_pData[2][i]);
}
pDlg->m_Com.SetOutput((COleVariant)vt);
vt.vt= VT_ARRAY |VT_UI1;
vt.parray = pSafe;
for ( i = 0; i<pDlg->m_Len; i++)
{
SafeArrayPutElement(pSafe,&i,(void*)&pDlg->m_pData[1][i]);
}
Sleep(15000);
//向下
pDlg->m_Com.SetOutput((COleVariant)vt);
//向右
Sleep(15000);
vt.vt= VT_ARRAY |VT_UI1;
vt.parray = pSafe;
for ( i = 0; i<pDlg->m_Len; i++)
{
SafeArrayPutElement(pSafe,&i,(void*)&pDlg->m_pData[3][i]);
}
pDlg->m_Com.SetOutput((COleVariant)vt);
Sleep(15000);
}
return 0;
}
(6)处理“开始监控”按钮的单击事件,让系统自动监控。
void CTimeDIYDlg::OnStartTail()
{
m_Tail = TRUE;
//开始监控
VCAStartVideoCapture(0,CAP_MPEG4_STREAM,MPEG4_AVIFILE_ONLY ,"C:\\WW.avi");
DWORD threadID;
m_hThread = ::CreateThread(NULL,0,ThreadProc,(LPVOID)this,0,&threadID);
}
(7)处理“停止监控”按钮的单击事件,停止系统自动监控。
void CTimeDIYDlg::OnStopTail()
{
if (m_Tail==TRUE)
{
//停止运动
VARIANT vt;
SAFEARRAY* pSafe;
SAFEARRAYBOUND band;
band.cElements =m_Len;
band.lLbound = 0;
pSafe = SafeArrayCreate(VT_UI1,1,&band);
for (long i = 0; i<m_Len; i++)
{
SafeArrayPutElement(pSafe,&i,(void*)&m_pData[12][i]);
}
vt.vt= VT_ARRAY |VT_UI1;
vt.parray = pSafe;
m_Com.SetOutput((COleVariant)vt);
::TerminateThread(m_hThread,0);
//停止监控
VCAStopVideoCapture(0);
m_Tail = FALSE;
}
}
(8)处理对话框的WM_TIMER消息,当指定的时间到达时,实现提供的自动监控。
void CTimeDIYDlg::OnTimer(UINT nIDEvent)
{
CTime time= CTime::GetCurrentTime();
CTime strtime;
m_Time.GetTime(strtime);
if (time==strtime)
{
OnStartTail() ;
KillTimer(1);
}
CDialog::OnTimer(nIDEvent);
}
(9)在对话框关闭时停止摄像头的转动。
void CTimeDIYDlg::OnCancel()
{
delete [] m_pData;
VCACloseDevice(0);
VCAUnInitSdk();
OnStopTail();
2.5.4 远程云台控制方案
在开发较大型的监控系统时,由于监控设备的物理位置比较分散,因此通常需要通过网络来对监控设备进行控制。在程序中实现云台远程控制比较容易,可以采用客户/服务器的模式来实现,客户端将指令发送给服务器,服务器在接收到指令后执行相应的动作。效果如图2.61、图2.62所示。

图2.61 服务器端效果图

图2.62 客户端效果图
下面笔者结合实例介绍远程云台控制的实现。服务器端程序设计步骤如下:
实例位置:光盘\mr\2\2.5\2.5.4\01
(1)创建一个基于对话框的应用程序,在对话框中添加标签、按钮、编辑框等控件,并导入ActiveX控件——MSComm,如图2.63所示。

图2.63 服务器端对话框资源
(2)在“stdafx.h”头文件中引用一些头文件,目的是使用套接字相关函数。
#include "winsock2.h"
#include <afxsock.h>
#pragma comment (lib,"ws2_32.lib") //连接ws2_32.lib库
(3)在应用程序初始化时初始套接字。
//初始化套接字
WSADATA data;
AfxSocketInit(&data);
(4)在对话框初始化时创建面向无连接套接字,并且将其绑定到本机的指定端口上。
BOOL CCloudMsgDlg::OnInitDialog()
{
CDialog::OnInitDialog();
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL)
{
CString strAboutMenu;
strAboutMenu.LoadString(IDS_ABOUTBOX);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
}
SetIcon(m_hIcon, TRUE);
SetIcon(m_hIcon, FALSE);
CString IP;
GetPrivateProfileString("端口设置","端口号","",IP.GetBuffer(0),
MAX_PATH,"./stage.ini");
m_Port.SetWindowText(IP);
CString setting;
GetPrivateProfileString("端口设置","环境设置","",setting.GetBuffer(0),
MAX_PATH,"./stage.ini");
m_Setting.SetWindowText(setting);
//创建UDP套接字
m_Client = socket(AF_INET,SOCK_DGRAM,0);
//获取本机名称
char name[MAX_PATH];
memset(name,0,MAX_PATH);
gethostname(name,MAX_PATH);
hostent* pTent = gethostbyname(name);
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr = (* (in_addr*)pTent->h_addr_list[0]) ;
long port = 600;
addr.sin_port = htons(port);
if (bind(m_Client,(sockaddr*)&addr,sizeof(addr))==SOCKET_ERROR)
{
MessageBox("地址绑定错误");
}
WSAAsyncSelect(m_Client,m_hWnd,CM_READINFO,FD_READ|FD_WRITE);
//设置端口信息,并打开端口
m_Com.SetSettings("2400,n,8,1");
m_Com.SetOutBufferSize(512);
m_Com.SetCommPort(1);
m_Com.SetSThreshold(0);
m_Com.SetPortOpen(TRUE);
return TRUE;
}
(5)处理“设置”按钮的单击事件,设置Com端口信息。
void CCloudMsgDlg::OnPortset()
{
CFileDialog fDlg(TRUE,NULL,NULL);
fDlg.DoModal();
CString IP;
m_Port.GetWindowText(IP);
WritePrivateProfileString("端口设置","端口号",IP.GetBuffer(0),"./stage.ini");
CString setting;
m_Setting.GetWindowText(setting);
WritePrivateProfileString("端口设置","环境设置
",setting.GetBuffer(0),"./stage.ini");
m_Com.SetPortOpen(FALSE);
m_Com.SetSettings(setting);
m_Com.SetOutBufferSize(512);
m_Com.SetCommPort(atoi(IP));
m_Com.SetSThreshold(0);
m_Com.SetPortOpen(TRUE);
}
(6)向对话框中添加一个消息映射宏,在其消息处理函数中读取客户端发来的指令,并将其发送到Com端口中。
ON_MESSAGE(CM_READINFO,OnReadInfo)
//消息处理函数
void CCloudMsgDlg::OnReadInfo(WPARAM wp, LPARAM lp)
{
unsigned char data[MAX_PATH];
memset(data,0,MAX_PATH);
sockaddr_in addr;
int len = sizeof(addr);
int ret = recvfrom(m_Client,(char*)data,MAX_PATH,0,(SOCKADDR*)&addr,&len);
if (ret >0)
{
//从数据报中获取命令码的字节数
int packagelen = data[1];
VARIANT vt;
SAFEARRAY* pSafe;
SAFEARRAYBOUND band;
band.cElements =packagelen;
band.lLbound = 0;
pSafe = SafeArrayCreate(VT_UI1,1,&band);
//读取控制码
for (long i = 0; i<packagelen; i++)
{
SafeArrayPutElement(pSafe,&i,(void*)&data[i+2]);
}
vt.vt= VT_ARRAY |VT_UI1;
vt.parray = pSafe;
m_Com.SetOutput((COleVariant)vt);
//读取停止码
for (i = 0; i<packagelen; i++)
{
SafeArrayPutElement(pSafe,&i,(void*)&data[packagelen+2+i]);
}
vt.vt= VT_ARRAY |VT_UI1;
vt.parray = pSafe;
Sleep(500);
m_Com.SetOutput((COleVariant)vt);
}
}
(7)在对话框关闭时关闭套接字。
void CCloudMsgDlg::OnCancel()
{
closesocket(m_Client);
CDialog::OnCancel();
}
客户端程序设计步骤如下:
实例位置:光盘\mr\2\2.5\2.5.4\02
(1)创建一个基于对话框的工程,在对话框中添加按钮、群组框等控件,如图2.64所示。

图2.64 客户框对话框资源
(2)向对话框类中添加成员变量,记录云台参数及服务器信息。
//成员变量
unsigned char (*m_pData) [100]; //存储云台控制码
int m_Len ; //云台协议使用的字节数
int m_ActoinCount; //云台控制动作数
int m_Port; //端口
CString m_Setting; //环境设置
SOCKET m_Client; //客户端套接字
int m_SrvPort; //服务器端口
CString m_Server; //服务器名称或IP
(3)在对话框初始化时创建套接字,绑定套接字地址,从INI文件中读取云台参数信息。
BOOL CCloudMsgDlg::OnInitDialog()
{
CDialog::OnInitDialog();
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL)
{
CString strAboutMenu;
strAboutMenu.LoadString(IDS_ABOUTBOX);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
}
SetIcon(m_hIcon, TRUE);
SetIcon(m_hIcon, FALSE);
//创建UDP套接字
m_Client = socket(AF_INET,SOCK_DGRAM,0);
//获取本机名称
char name[MAX_PATH];
memset(name,0,MAX_PATH);
gethostname(name,MAX_PATH);
hostent* pTent = gethostbyname(name);
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr = (* (in_addr*)pTent->h_addr_list[0]) ;
addr.sin_port = htonl(60025);
if (bind(m_Client,(sockaddr*)&addr,sizeof(addr))==SOCKET_ERROR)
{
MessageBox("地址绑定错误");
}
m_ButtonUp.m_Type = ctUp;
m_ButtonDown.m_Type = ctDown;
m_ButtonLeft.m_Type = ctLeft;
m_ButtonRight.m_Type = ctRight;
m_InFoci.m_Type = ctInFoci;
m_ReFoci.m_Type = ctReFoci;
m_InLen.m_Type = ctInLen;
m_ReLen.m_Type = ctReLen;
m_InAperture.m_Type = ctInAperture;
m_ReAperture.m_Type = ctReAperture;
m_InBrush.m_Type = ctInBrush;
m_ReBrush.m_Type = ctReBrush;
m_Len = GetPrivateProfileInt("使用字节数","字节数",8,"./stage.ini");
m_ActoinCount = GetPrivateProfileInt("控制项","控制数",13,"./stage.ini");
m_pData = new unsigned char[m_ActoinCount][100];
m_Port = GetPrivateProfileInt("端口设置","端口号",1,"./stage.ini");
GetPrivateProfileString("端口设置","环境设置","9600,n,8,1",
m_Setting.GetBuffer(0),MAX_PATH,"./stage.ini");
int data;
char buff[20] = {0};
char var[20] = {0};
for (int i = 0 ; i<m_ActoinCount ; i++)
for (int j = 0 ; j <m_Len; j++)
{
char section[20] = "字节";
itoa(i+1,var,10);
itoa(j+1,buff,10);
strcat(section,buff);
data = GetPrivateProfileInt(var,section,0,"./stage.ini");
m_pData[i][j] = data;
}
return TRUE;
}
(4)向对话框中添加SendCmd方法,根据参数的不同,向服务器端发送不同的指令。
void CCloudMsgDlg::SendCmd(int CtrCmd,int StopCmd /*=12*/)
{
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.S_un.S_addr = inet_addr(m_Server);
addr.sin_port = htons(m_SrvPort);
//定义数据报的格式
//--报的总长度(1个字节)--云台控制码字节数(1个字节)--控制码数据--停止码数据
unsigned char * pPackage = new unsigned char [m_Len*2+2];
memset(pPackage,0,m_Len+2+3);
//填充数据报
pPackage[0] = m_Len*2+2;
pPackage[1] = m_Len ;
int index = 2;
//填充控制码数据
for (int i = 0 ; i<m_Len; i++,index++)
{
pPackage[index] = m_pData[CtrCmd][i];
}
//填充停止码数据
for ( i = 0 ; i<m_Len; i++,index++)
{
pPackage[index] = m_pData[StopCmd][i];
}
//发送数据报
sendto(m_Client,(char*)pPackage,(m_Len*2+2)*2,0,(sockaddr*)&addr,sizeof(addr));
delete [] pPackage; //equal to delete pPackage
}






