停车场管理系统
前段时间因为课程要求,所以做了一个停车场管理系统,主要为mfc+oracle+opencv,简单来说,也就是可视化界面+数据库连接+车牌识别。
在继续阅读下去的同时,我先声明,这里我的车牌识别由于时间的问题,是调用了别人的接口,所以想研究车牌识别的同学可以跳转下这边详细的车牌识别.好了废话不多说,进入教程。
0.摘要
本项目采用C++编程实现停车场管理系统,其主要功能包括:车辆入库、出库、车辆计费、信息查询、权限管理、摄像头调用与拍照等。课题可分为三个个模块:界面显示、车牌识别、数据库操作。界面使用MFC编程绘制,通过账号密码登录,可显示管理员-游客等不同权限的功能界面,其中管理员可对车辆信息处理,选择车辆入库时,程序调用摄像头拍照进行车牌识别,自动记录车牌信息与入库时间;当对车辆信息查询时,程序连接Oracle数据库并进行数据读取,返回信息至List控件中显示;摄像头的调用与拍照通过opencv视觉库实现,车牌识别系统基于EasyPR工程实现,通过形态学操作、梯度计算、阈值分割等实现车牌定位,使用NN训练实现字符识别等,数据库的访问与信息存储通过微软提供的ADO接口对oracle进行数据操作。
查询显示的车辆数据存于oracle中的,车辆入库与出库时的车牌号码是通过车牌识别得来的。
MFC工程建立在VS2012中,下面来看看工程全貌和流程图。
- DBOperation是数据库操作文件
- CarInDlg是车辆入库界面
- CarOutDlg是车辆出库界面
- CvvImage是调用摄像头时用到的文件
- openCameraDlg是打开摄像头界面
- park_ManageDlg是登录主界面
- ManageDlg是管理员界面
-
vistiorDlg是游客界面。
看起来有点小复杂,其实跟着慢慢来也还可以。
1.C++连接数据库
毕竟我们是数据库课设,所以首先讲解下如何连接oracle。
工具如下:
- oracleXE(轻量级学习版oracle)安装教程
- sql developer(百度搜搜直接下)
oracle用来建表存储数据,sql developer以可视化的界面查看数据库内的数据。
C++连接oracle有几种方式:有OCCI、ADO,我们这里采用最简单且已经封装好微软提供的的ADO方式。具体代码参考了this
1.1导入ADO的库
使用ADO的话必须要导入它的库,这语句放到头文件.h中
//导入ADO库
#import "c:\program files\common files\system\ado\msado15.dll" no_namespace rename("EOF", "adoEOF")
1.2将DBOperation.h与DBOperation.c加入MFC工程中
DBOperiation.h
#pragma once
#import "c:\program files\common files\system\ado\msado15.dll" no_namespace rename("EOF", "adoEOF")
class CDBOperation
{
public:
//初始化数据库操作需要的对象
CDBOperation(void);
~CDBOperation(void);
//连接至数据库
bool ConnToDB(char *ConnectionString, char *UserID, char *Password);
//数据库操作函数
//查询操作 删除以及添加
_RecordsetPtr ExecuteWithResSQL(const char *);
//bool ExecuteNoResSQL(const char *);//delete and add
private:
void PrintErrorInfo(_com_error &);
private:
//初始化数据库连接、命令、记录集
_ConnectionPtr CreateConnPtr();
_CommandPtr CreateCommPtr();
_RecordsetPtr CreateRecsetPtr();
private:
//数据库连接需要的连接、命令操作对象
_ConnectionPtr m_pConnection;
_CommandPtr m_pCommand;
};
DBOperation.c
#include "stdafx.h"
#include "DBOperation.h"
CDBOperation::CDBOperation(void)
{
CoInitialize(NULL);
m_pConnection = CreateConnPtr();
m_pCommand = CreateCommPtr();
}
CDBOperation::~CDBOperation(void)
{
//m_pCommand->Close();
m_pConnection->Close();
}
bool CDBOperation::ConnToDB(char *ConnectionString, char *UserID, char *Password)
{
if (NULL == m_pConnection)
{
printf("Failed to create connection\n");
return false;
}
try
{
HRESULT hr = m_pConnection->Open(ConnectionString, UserID, Password, NULL);
if (TRUE == FAILED(hr))
{
return false;
}
m_pCommand->ActiveConnection = m_pConnection;
return true;
}
catch(_com_error &e)
{
PrintErrorInfo(e);
return false;
}
}
_RecordsetPtr CDBOperation::ExecuteWithResSQL(const char *sql)
{
//已经在连接至数据库的时候进行判断了
//if (NULL == m_pCommand || 0 == m_pConnection->State)
//{
// printf("Failed to create command OR the state of connection is zero\n");
// return NULL;
//}
//char *query = new char;
//strcpy(query, sql);
try
{
m_pCommand->CommandText = _bstr_t(sql);
_RecordsetPtr pRst = m_pCommand->Execute(NULL, NULL, adCmdText);
return pRst;
//_variant_t ra;
//_RecordsetPtr pRst = m_pConnection->Execute((_bstr_t)query, &ra, adCmdText);
}
catch(_com_error &e)
{
PrintErrorInfo(e);
return NULL;
}
}
//bool CDBOperation::ExecuteNoResSQL(const char *sql)
//{
// //if (NULL == m_pCommand || 0 == m_pConnection->State)
// //{
// // printf();
// //}
// try
// {
// char *query = NULL;
// strcpy(query, sql);
// m_pCommand->CommandText = (_bstr_t)query;
//
// }
//}
void CDBOperation::PrintErrorInfo(_com_error &e)
{
printf("Error infomation are as follows\n");
printf("ErrorNo: %d\nError Message:%s\nError Source:%s\nError Description:%s\n", e.Error(), e.ErrorMessage(), (LPCTSTR)e.Source(), (LPCTSTR)e.Description());
}
_ConnectionPtr CDBOperation::CreateConnPtr()
{
HRESULT hr;
_ConnectionPtr connPtr;
hr = connPtr.CreateInstance(__uuidof(Connection));
if (FAILED(hr) == TRUE)
{
return NULL;
}
return connPtr;
}
_CommandPtr CDBOperation::CreateCommPtr()
{
HRESULT hr;
_CommandPtr commPtr;
hr = commPtr.CreateInstance(__uuidof(Command));
if (FAILED(hr) == TRUE)
{
return NULL;
}
return commPtr;
}
_RecordsetPtr CDBOperation::CreateRecsetPtr()
{
HRESULT hr;
_RecordsetPtr recsetPtr;
hr = recsetPtr.CreateInstance(__uuidof( Command));
if (FAILED(hr) ==TRUE)
{
return NULL;
}
return recsetPtr;
}
1.3连接oracle
CDBOperation dbOper;
bool bConn = dbOper.ConnToDB("Provider=MSDAORA;Persist Security Info=True;Data Source=xe", "system", "123456");
if (false == bConn)
{
MessageBox("连接数据库出现错误");
return;
}
如果你是oracle完整版的,记得将Provider=MSDAORA改成Provider=OraOLEDB。
代码中的xe是数据库的SID,system是用户名,123456是密码,请记得修改。
1.4进行增删改查
//执行插入操作
sprintf_s(sql, "insert into CARINFO(CARLICENCE, CARTYPE, TIMECARIN,TIMECAROUT,CARFEE) values('%s', %s, sysdate, null, 0)",carInlien,carIntype); //这里的dbOper是连接数据库时得的变量,不清楚可以看下连接数据库的代码 pRst = dbOper.ExecuteWithResSQL(sql); //这里的CARINFO是oracle中的一张表,实际上增删改查都是通过将sql语句转换为字符串(这里使用sprintf_s转化,不懂可以查下这个函数),随后再通过ExecuteWithResSQL执行。 //执行更新操作 sprintf_s(sql, "update CARINFO set CARFEE = (trunc(abs((TIMECARIN - TIMECAROUT)*24))*(select FEE from FEESTAND where CARINFO.CARTYPE = FEESTAND.CARTYPE)) where CARLICENCE = '%s'",carOutLience); pRst = dbOper.ExecuteWithResSQL(sql); //执行查找操作 sprintf_s(sql, "select * from FEESTAND"); pRst = dbOper.ExecuteWithResSQL(sql); while (!pRst->adoEOF) { carInfoList.InsertItem(i, (LPSTR)(LPCSTR)_bstr_t(pRst->GetCollect(_variant_t("CARTYPE")))); carInfoList.SetItemText(i, 1,(LPSTR)(LPCSTR)_bstr_t(pRst->GetCollect(_variant_t("FEE")))); i++; pRst->MoveNext(); } //查询返回的是一个指针,通过不断遍历它,(LPSTR)(LPCSTR)_bstr_t(pRst->GetCollect(_variant_t("CARTYPE")))可以获得每条数据属性值为"CARTYPE"的值 //删除同理,写出sql语言,转化为字符串,进行执行
2.可视化界面的编写
2.1子窗口的显示
MFC中的对话框分为了模态对话框与非模态对话框,具体区别可以百度搜搜看,下面讲解一个子界面的创建至显示过程。
- 1.在资源中添加一个对话框 右击Dialog | Insert dialog
- 2.在对话框上添加自己需要的按钮等控件
- 3.双击控件会出现一个对话框,为添加的对话框命名一个类名,例如Dlg2 自动生成.cpp和.h文件
- 4.在父窗口的.cpp文件中包含上面生成的那个.h文件
- 5.在父窗口的的按钮的响应函数中添加如下代码
Dlg2 dlg2;
dlg2.DoModal();
那么点击父窗口按钮时,就会弹出子对话框.
如果需要创建非模态对话框,则需调用Create函数与ShowWindow函数
TestDlg *test = new TestDlg;
test -> Create(ID,母窗口or this)
test -> ShowWindow(SW_SHOW)
2.2编辑控件中数据的获取
对应编辑框控件添加变量后,使用如下代码即可:
UpdataDate(True)
//读出编辑框中的值改为该控件变量的值
UpdataDate(False)
//将该控件变量的值写入编辑框
2.3List控件的数据显示
这位博主写的特别清楚,可以看下这里,这边就不多提了。
3.车牌识别
原本想自己写一个车牌识别的,但由于时间的问题,最后只能去调用EasyPR的接口,MFC工程是使用VS2012写的,但EasyPR只支持VS2013且需要配置opencv一些的库,于是下了个2013绿色版,下载了EasyPR懒人版(无须配置opencv,可在EasyPR github中下载到,具体链接)。由于界面与识别代码因版本的原因只能作为两个程序,于是采用文件的方式进行数据传递。车牌识别代码每次识别完后将结果存入某个文件,姑且称为’cardInfo.txt’,每次车辆入库或出库子窗口初始化时就去读取’cardInfo.txt’,并将车牌号显示于编辑框中。
其实是可以在MFC工程调用车牌识别的程序的,使用即可 WinExec("D:\\EasyPR1\\demo.exe", SW_SHOW);
只是我的工程还是有问题,程序跑不起来,所以我还是采用手动的方式让车牌识别程序运行。有点无奈啊~~。
晚一些时候再传代码上来吧