停车场管理系统

#停车场管理系统

前段时间因为课程要求,所以做了一个停车场管理系统,主要为mfc+oracle+opencv,简单来说,也就是可视化界面+数据库连接+车牌识别。
在继续阅读下去的同时,我先声明,这里我的车牌识别由于时间的问题,是调用了别人的接口,所以想研究车牌识别的同学可以跳转下这边详细的车牌识别.好了废话不多说,进入教程。

##0.摘要
本项目采用C++编程实现停车场管理系统,其主要功能包括:车辆入库、出库、车辆计费、信息查询、权限管理、摄像头调用与拍照等。课题可分为三个个模块:界面显示、车牌识别、数据库操作。界面使用MFC编程绘制,通过账号密码登录,可显示管理员-游客等不同权限的功能界面,其中管理员可对车辆信息处理,选择车辆入库时,程序调用摄像头拍照进行车牌识别,自动记录车牌信息与入库时间;当对车辆信息查询时,程序连接Oracle数据库并进行数据读取,返回信息至List控件中显示;摄像头的调用与拍照通过opencv视觉库实现,车牌识别系统基于EasyPR工程实现,通过形态学操作、梯度计算、阈值分割等实现车牌定位,使用NN训练实现字符识别等,数据库的访问与信息存储通过微软提供的ADO接口对oracle进行数据操作。

查询显示的车辆数据存于oracle中的,车辆入库与出库时的车牌号码是通过车牌识别得来的。

MFC工程建立在VS2012中,下面来看看工程全貌和流程图。
停车场管理系统_第1张图片

  • DBOperation是数据库操作文件
  • CarInDlg是车辆入库界面
  • CarOutDlg是车辆出库界面
  • CvvImage是调用摄像头时用到的文件
  • openCameraDlg是打开摄像头界面
  • park_ManageDlg是登录主界面
  • ManageDlg是管理员界面
  • vistiorDlg是游客界面。

停车场管理系统_第2张图片

看起来有点小复杂,其实跟着慢慢来也还可以。

##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编辑控件中数据的获取
停车场管理系统_第3张图片
对应编辑框控件添加变量后,使用如下代码即可:

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);
只是我的工程还是有问题,程序跑不起来,所以我还是采用手动的方式让车牌识别程序运行。有点无奈啊~~。

晚一些时候再传代码上来吧
https://github.com/fengqian-wei/park-manage-system
https://download.csdn.net/download/qq_34771697/10372014

补上数据库设计:
E-R图:概念设计
停车场管理系统_第4张图片
逻辑设计:由E-R图可得出以下关系模式
1.停放车辆信息表(车牌号、车辆类型、入库时间、出库时间、停车费)
主键为:车牌号、入库时间 外键为:车辆类型
2.停车管理员表(姓名、职工号、性别、年龄) 主键为:职工号
3.收费标准表(车辆类型、每小时收取费用) 主键为:车辆类型)

以下为每个表的具体设计:
停车场管理系统_第5张图片停车场管理系统_第6张图片停车场管理系统_第7张图片
请注意以下问题:
1、该工程用的数据库为oracle32位,开发时VS用的是2012。
2、跑前请确保已安装OPENCV库,并在VS内配置好依赖库,具体opencv配置可参考如下:
https://blog.csdn.net/poem_qianmo/article/details/19809337
3、满足以上条件基本上可以将工程跑起来,运行过程中可能会涉及连接数据库出错,请检查数据库服务是否开启,可尝试用sql developer连接测试。

你可能感兴趣的:(停车场管理系统)