停车场管理系统

时间:2022-10-17 03:18:50

停车场管理系统

前段时间因为课程要求,所以做了一个停车场管理系统,主要为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);
只是我的工程还是有问题,程序跑不起来,所以我还是采用手动的方式让车牌识别程序运行。有点无奈啊~~。

晚一些时候再传代码上来吧