分类

课内:
不限
类型:
不限 游戏 项目 竞赛 个人研究 其他
评分:
不限 10 9 8 7 6 5 4 3 2 1
年份:
不限 2018 2019

资源列表

  • 基于JSP的校园论坛BBS网站的设计与实现

    1 概述开发校园论坛系统的目的是提供一个供我校学生交流的平台,为我校学生提供交流经验、探讨问题的社区。因此, 校园论坛系统最基本的功能首先是发表主题,其次是其他人员根据主题发表自己的看法。此外,为了记录主题的发表者和主题的回复者信息,系统还需要提供用户注册和登录的功能。只有注册的用户登录后才能够发表和回复主题,浏览者(游客)只能浏览主题信息。根据用户的需求及以上的分析,校园论坛需要具备前台功能和后台功能。
    1.1 系统概述
    网站名称
    校园论坛
    网站功能实现
    为用户提供一个注册、发帖、回复、浏览等交流操作功能。
    用户
    在校学生
    子系统关系图


    1.2 系统目标为了方便校内同学的交流,我们决定要做这么一个校园论坛,而对于论坛这样的数据流量特别大的网络管理系统,必须要满足使用方便、操作灵活等设计需求。所以本系统在设计时应满足以下几个目标:

    临时用户进入,可浏览帖子但不可发帖一个版面能显示所有的帖子具有登录模块,有用户的个人信息用户随时都可以查看自己发表的帖子有用户的消息的时间及时提醒,主要用于提示哪个用户 回复了自己的主题管理员权限可删除任意帖子,具有最大权限的管理功能对用户输入的数据,系统进行严格的数据检验,尽可能 排除人为的错误系统最大限度地实现了易维护性和易操作性系统运行稳定安全可靠
    1.3 文档概述需求分析报告采用面向对象的方法,在文档中主要采用了用例、流程图等表示方法来描述需求。
    1.4 需求概述1.4.1 用户需求对于一个用户,使用论坛进行交流时,首先要注册一个 账户,然后登录后才能进行对帖子的回复,如果不登录,就 只能查看帖子而不能进行回复和发表帖子。用户使用论坛系 统的需求是发表某一个主题相关的帖子,用户在发表帖子后, 如果有人进行回复,就要在首页提醒用户有新消息。用户可以删除自己发表的帖子和评论。对于论坛管理人员来说,需要完成对用户发表的帖子的管理,包括:设置精华帖、置顶 帖子、删除帖子等操作。
    开发校园论坛系统的目的是提供一个供我校学生交流的平台,为我校学生提供交流经验、探讨问题的社区。因此, 校园论坛系统最基本的功能首先是发表主题,其次是其他人员根据主题发表自己的看法。此外,为了记录主题的发表者和主题的回复者信息,系统还需要提供用户注册和登录的功能。只有注册的用户登录后才能够发表和回复主题,浏览者(游客)只能浏览主题信息。根据用户的需求及以上的分析, 校园论坛需要具备前台功能和后台功能。

    系统前台功能:显示用户发表的帖子,查看帖子的内容、发表对帖子的回复、发表对回复的回复、显示用户的头像、用户信息的显示、用户新信息的提醒。系统后台功能:进入后台、帖子管理、用户管理、添加删除用户、系统设置、退出系统、返回首页。
    1.4.2 系统开发环境需求1.4.2.1 开发环境我们一致认为在开发此论坛的时候需要配置以下软件环境:
    服务器端:

    操作系统:Windows 7及以上Web服务器:Tomcat 7.0 集成开发环境:Eclipse 数据库:MySQL
    客户端:

    无特别要求
    1.4.2.2 使用技术
    前端:HTML、CSS、JS、BootStrap后端:Java数据库:MySQL
    1.4.2.3 用户的特点
    本网站的最终用户的特点:所有上网用户在无需培训的情况下,按照网站页面提示即可使用网站的相关服务和功能后台管理和维护人员的教育水平和技术专长:本软件的后台管理和维护人员均为我小组开发成员
    1.5 功能需求经过系统性的研究,我们认为整个论坛大概分为 3 个功能模块,分别为:论坛模块、管理员模块和用户模块。
    1.5.1 前台功能需求在论坛前台中,我们作了如下设计:分未登录前和登录后,登录前,用户进入校园论坛的主页面后,可以查看帖子内容、用户注册、用户登录,登录后,用户可以修改个人信息、查看个新消息、修改头像、查看帖子内容、回复帖子。
    1.5.2 后台功能需求管理用户进入后台后,可以进行帖子管理,包括查看帖子、删除帖子、返回论坛首页和退出系统。
    1.5.3 系统前台流程图 在系统前台流程中,我们做出了如下设置。首先,我们开始点开界面,是我们的论坛主页,不登录可以以临时用户身份浏览,登陆则可以发帖和评论,没有账号的可以注册。

    1.5.4 系统后台流程图在系统的后台流程中,我们做出了如下设置。首先,我们进入登录界面,使用管理员账号和密码进行登录,在管理员界面,我们可以进行用户信息管理,可以查看、删除用户帖子

    1.6 系统用例图
    1.7 系统时序图论坛管理员处理帖子的时序图

    用户发帖评论时序图

    1.8 系统组件图
    1.9 系统E-R图
    1.10 系统配置图
    2 操作指引2.1 项目简介校园论坛所具有的功能包括:用户注册、用户登录、用户信息修改、浏览帖子、发表帖子、收藏帖子、搜索帖子、回复帖子、用户信息管理(查询、增加、删除、修改)。
    从整体上可以分为数据层、数据访问层和业务逻辑层。数据层是系统最底层,它用于存储系统的所有数据。数据访问层建立在数据库之上,应用程序通过该层访问数据库。数据访问层一般封装数据库的选择、添加、更新和删除操作,同时还为业务逻辑层服务,所以数据访问层的设计的好坏关系到整个系统的成败。业务逻辑层包括用户登录、用户注册、 发表帖子等业务逻辑,它一般由Web页面实现。
    系统操作结构

    页面操作结构

    2.2 操作介绍在登录注册界面可以通过登录和注册按钮进行登录和注册操作


    登录完就会进入主界面,在主界面上方有“个人信息”,“我的帖子”、“用户管理”等按钮可以进行相应的操作。界面中间是其他用户发的帖子,可以点击进行浏览和恢复等操作。界面的最下方是发帖模块,只用登录用户才可以进行发 帖操作,游客只有浏览帖子的权限。

    点击个人信息按钮进入个人信息界面可以修改个人的详细信息。

    点击我的帖子进入我的帖子界面可以对自己发的帖子进行删除和查看操作。

    在首页点击其他用户的帖子可以进入帖子的完整内容进行浏览,还可以在最下方的回复模块进行回复。

    如果你是以管理员的身份登录,你还可以进入用户管理模块,进行删除帖子的操作。

    3 业务说明3.1 业务简介本软件系统旨在通过网络论坛,让在校大学生快速地进行交流更为便捷。使得大学生的交流环境和校方教育及管理人员获得广大学生声音更加方便也更为人性化。校园论坛是面向各大高校师生的一个信息交流平台,建立一个基于师生沟通为目的,功能简洁,方便实用和管理的论坛系统显得越来越必要。为达成这一目标,并将大学学习期间所学的数据库设计与实现、网页设计、面向对象程序设计、Web 应用开发等知识综合运用,融会贯通,特开发此项目。
    3.2 业务场景


    触发事件
    用户注册




    执行者
    用户


    工作内容
    1.进行用户的信息注册






    触发事件
    用户登录




    执行者
    用户


    工作内容
    1.用户使用已注册的账号和密码进行登录






    触发事件
    查看已发布的帖子




    执行者
    用户/游客


    工作内容
    1. 对已发布的帖子进行查看






    触发事件
    发帖




    执行者
    用户


    工作内容
    1.用户进行帖子发布






    触发事件
    回帖




    执行者
    用户


    工作内容
    1.用户对已发布的帖子内容进行回复






    触发事件
    论坛出现违规帖子




    执行者
    网站管理员


    工作内容
    1.对违规帖子进行查看,评估 2.对存在违规现象的帖子进行删除,当情况严重时还需要对违规用户进行禁言或封号处理



    4 数据库数据流图,简称 DFD,是 SA 方法中用于表示系统逻辑模型的一种工具,它以图形的方式描绘数据在系统中流动和处理的过程,由于它只反映系统必须完成的逻辑功能,所以它 是一种功能模型。
    4.1 顶层数据流图
    4.2 0 层数据流图
    4.3 具体数据流图4.3.1 登录系统
    4.3.2 注册系统
    4.3.3 发表主题
    4.3.4 回复主题
    4.3.5 论坛管理
    4.4 数据字典4.4.1 数据流




    4.4.2 数据项




    4.5 E-R图E-R 图即实体-联系图(Entity Relationship Diagram),是指提供了表示实体型、属性和联系的方法,用来描述现实世界的概念模型 。 E-R方法是 “实体-联系方法 ”(Entity-Relationship Approach)的简称,它是描述现实世界概念结构模型的有效方法。

    4.6 数据库设计4.6.1 数据库分析数据库的设计,在程序的开发中起着至关重要的作用,它往往决定了在后面的开发中进行怎样的程序编码。一个合理、有限的数据库设计可降低程序的复杂性,使程序开发的过程更为容易。
    本系统是一个中型的用户交流的网站,考虑到用户访问量以及网络连接,本系统采用MySQL 作为数据库。
    MySQL 是一种关联数据库管理系统,关联数据库将数据保存在不同的表中,而不是将所有数据放在一个大仓库内,这样就增加了速度并提高了灵活性。MySQL 的 SQL 语言是用于访问数据库的最常用标准化语言。
    4.6.2 数据库的逻辑设计根据系统需求,我们就可以创建系统所需要的数据库表了。本系统包含 3 个表,下面是这些表的结构。
    user_info 表结构如表所示:



    字段名
    数据类型
    字段长度
    是否主键
    是否为空
    备注




    user_id
    int
    15


    用户 id


    user_name
    varchar
    50


    用户名


    user_password
    varchar
    50


    密码


    user_sex
    varchar
    2


    性别


    user_face
    varchar
    255


    头像


    user_phone
    varchar
    255


    联系电话


    user_email
    varchar
    200


    电子邮箱


    user_from
    varchar
    200


    来自何处


    isAdmin
    int
    2


    是否为管理员



    forum_info 表结构如表所示:



    字段名
    数据类型
    字段长度
    是否主键
    是否为空
    备注




    Fid
    int
    10


    发帖 id


    Title
    varchar
    255


    帖子标题


    content
    varchar
    255


    帖子内容


    create_time
    datetime



    发帖时间


    user_id
    int
    11


    用户 id



    reply_info 表结构如表所示:



    字段名
    数据类型
    字段长度
    是否主键
    是否为空
    备注




    reply_id
    int
    10


    回帖 id


    reply_content
    varchar
    255


    回帖内容


    reply_time
    datetime



    回帖时间


    user_id
    int
    11


    用户 id


    fid
    int
    11


    发帖 id



    4.6.3 SQL 语句设计(建表语句 )用户信息表(user_info)
    CREATE TABLE `user_info` ( `user_id` int(15) NOT NULL, `user_name` varchar(50) NOT NULL, `user_password` varchar(50) NOT NULL, `user_sex` varchar(2) NOT NULL, `user_face` varchar(255) NOT NULL, `user_phone` varchar(255) NOT NULL, `user_email` varchar(200) NOT NULL, `user_from` varchar(200) NOT NULL, `isAdmin` int(2) DEFAULT NULL, PRIMARY KEY (`user_id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8
    主题信息表(forum_info)
    CREATE TABLE `forum_info` ( `fid` int(10) NOT NULL AUTO_INCREMENT, `title` varchar(255) NOT NULL, `content` varchar(255) NOT NULL, `create_time` datetime NOT NULL, `user_id` int(11) NOT NULL, PRIMARY KEY (`fid`)) ENGINE=InnoDB AUTO_INCREMENT=55 DEFAULT CHARSET=utf8
    回帖信息表(reply_info)
    CREATE TABLE `reply_info` ( `reply_id` int(10) NOT NULL AUTO_INCREMENT, `reply_content` varchar(255) NOT NULL, `reply_time` datetime NOT NULL, `user_id` int(11) NOT NULL, `fid` int(11) NOT NULL, PRIMARY KEY (`reply_id`)) ENGINE=InnoDB AUTO_INCREMENT=52 DEFAULT CHARSET=utf8
    4.7 MD5 算法加密实现代码
    public class MD5 { public static String generateCode(String str) throws Exception{ MessageDigest md5=MessageDigest.getInstance("MD5"); byte[] srcBytes=str.getBytes(); md5.update(srcBytes); byte[] resultBytes=md5.digest(); String result=new String(resultBytes); return result; }}
    实现结果

    5 系统测试
    测试环境
    操作系统:windows10
    浏览器:Google Chrome

    5.1 测试内容


    用户注册
    输入学号、密码及个人信息,并且通过验证码认证后,完成注册





    用户登录
    输入学号及密码完成登录



    浏览帖子
    不论是否登录,都可以正常浏览已有的帖子



    发帖
    只有登录后,方可发帖



    回复
    只有登录后,方可回复



    个人信息
    查看个人信息(头像、学号、姓名、性别 联系电话、电子邮箱、来自哪里)



    修改个人信息
    对个人信息(头像、学号、姓名、性别、联系电话、电子邮箱、来自哪里)进行修改



    退出登录
    退出已登录状态




    5.2 功能截图校园论坛主页

    在校园论坛主页,可作为游客身份浏览帖子,但只有注册、 登录之后,方可回复跟帖。主页提供直观的注册、登录按钮。
    用户注册

    输入学号、用户名、密码,以及其它个人信息之后,即可完成注册。并且为了网站安全,用户需要通过验证码验证。
    用户登录
    输入学号、密码即可完成登录。

    若勾选“记住密码”,会自动填充学号及密码,方便用户快捷登录校园论坛。
    查看帖子

    登录之后,进入“查看帖子”页面,可浏览已发布的帖子。右上角提供“个人信息”、“ 我的帖子”、“退出论坛”三个按钮。
    发帖

    “查看帖子”页面底部,输入标题及内容,点击”发表”,即可发布自己的帖子。
    阅读、回复帖子

    点击帖子的标题,即可阅读帖子详情,可以回复跟帖。
    个人信息

    点击右上角的“个人信息”按钮,即可查看个人信息,包 括头像、学号、姓名、性别、联系电话、电子邮箱,及来自哪里。
    修改个人信息

    “个人信息”页面,点击“修改资料”按钮,即可进入“修改个人信息”页面。可对个人信息进行修改。
    退出登录

    点击右上角的“退出论坛”按钮,即退出登录状态,回到论坛主页。
    4 评论 68 下载 2018-10-05 22:38:31 下载需要16点积分
  • VC++实现的基于人眼状态的疲劳驾驶识别系统

    一、文档说明
    文档主要对项目的程序进行说明和描述程序的思想。
    程序的功能
    程序的思想
    程序的源码
    注意之处(程序中比较难理解,比较特殊的地方)
    待改进之处(能使得效果更好的地方)

    二、程序内容1. main()函数程序的功能首先,利用Adaboost算法检测人脸,紧接着根据人脸的先验知识分割出大致的人眼区域。然后,对人眼大致区域的图像进行图像增强处理(中值滤波、非线性点运算),接着利用Ostu算法计算最佳分割阈值,对图像进行二值化处理。
    然后定位人眼的具体位置,具体有以下几个步骤。首先利用直方图积分投影,根据设定的阈值判断并消除眉毛区域。然后分割出左眼和右眼的图像,分别对左右眼的图像计算直方图和直方图积分投影,从而分别确定左右眼的中心位置。
    最后,根据定位出的左右眼的中心位置,人为设定人眼矩形框的大小,根据矩形框内的像素特征判断眼睛的睁开闭合状态。有三个特征,眼睛长宽比R,黑色像素占总像素的比例α,以虹膜中心点为中心的1/2中间区域的黑色像素比例β。根据模糊综合评价的思想,将这三个指标划分为相同的4个级别(见下表),然后根据百分比组合成一个函数。最终根据函数值与阈值比较,确定眼睛的睁开、闭合状态。




    闭合
    可能闭合
    可能睁开
    睁开
    标准
    权重




    Value
    0
    2
    6
    8




    R
    (0, 0.8] (3, 无穷]
    (0.8, 1.2]
    (1.2, 1.5] (2.5, 3]
    (1.5, 2.5]
    2.0
    0.2


    α
    (0, 0.4]
    (0.4, 0.5]
    (0.5, 0.6]
    (0.6, 1]
    0.65
    0.4


    β
    (0, 0.3]
    (0.3, 0.45]
    (0.45, 0.6]
    (0.6, 1]
    0.55
    0.4



    为了判定驾驶员是否处于疲劳驾驶状态,需要对很多帧视频进行上述处理,根据PERCLOS原理和制定的判断规则,判断最终状态。
    程序的源码/*************************************************************************功能:检测人脸,检测人眼,识别人眼闭合状态,判断是否处于疲劳驾驶状态改进: 1. detectFace()中用了直方图均衡化,到时看有没有必要 2. 二值化的效果不太理想,到时用实际的驾驶图片测试再看看怎么改进。 二值化之前一定要做图像增强:非线性点运算或直方图均衡化。 在OSTU找到的最优阈值基础上减了一个常数,但减太多了,导致整张图片很灰暗的情况下二值化效果很差。 3. detectFace子函数中有一个budge:返回的objects在子函数外被释放了!**************************************************************************/#include <highgui.h>#include <cv.h>#include <cxcore.h>#include "histogram.h"#include "memory.h"#include "time.h"#include "ostuThreshold.h"#include "detectFace.h"#include "histProject.h"#include "linetrans.h"#include "nonlinetrans.h"#include "getEyePos.h"#include "recoEyeState.h"#include "recoFatigueState.h"#define DETECTTIME 30 // 一次检测过程的时间长度,用检测次数衡量#define FATIGUETHRESHOLD 180 // 判断是否疲劳驾驶的阈值extern CvSeq* objectsTemp = NULL; // 传递objects的值回来main()int main(){/*************** 主程序用到的参数 **************************/ IplImage * srcImg = NULL; // 存放从摄像头读取的每一帧彩色源图像 IplImage * img = NULL; // 存放从摄像头读取的每一帧灰度源图像 CvCapture * capture; // 指向CvCapture结构的指针 CvMemStorage* storage = cvCreateMemStorage(0); // 存放矩形框序列的内存空间 CvSeq* objects = NULL; // 存放检测到人脸的平均矩形框 double scale_factor = 1.2; // 搜索窗口的比例系数 int min_neighbors = 3; // 构成检测目标的相邻矩形的最小个数 int flags = 0; // 操作方式 CvSize min_size = cvSize(40, 40); // 检测窗口的最小尺寸 int i, globalK; // 绘制人脸框选用的颜色 int hist[256]; // 存放直方图的数组 int pixelSum; int threshold; // 存储二值化最优阈值 clock_t start, stop; // 计时参数 IplImage* faceImg = NULL; // 存储检测出的人脸图像 int temp = 0; // 临时用到的变量 int temp1 = 0; // 临时用到的变量 int count = 0; // 计数用的变量 int flag = 0; // 标记变量 int * tempPtr = NULL; // 临时指针 CvRect* largestFaceRect; // 存储检测到的最大的人脸矩形框 int * horiProject = NULL; // 水平方向的投影结果(数组指针) int * vertProject = NULL; // 垂直方向的投影结果(数组指针) int * subhoriProject = NULL; // 水平方向的投影结果(数组指针) int * subvertProject = NULL; // 垂直方向的投影结果(数组指针) int WIDTH; // 图像的宽度 int HEIGHT; // 图像的高度 int rEyeCol = 0; // 右眼所在的列数 int lEyeCol = 0; // 左眼所在的列数 int lEyeRow = 0; // 左眼所在的行数 int rEyeRow = 0; // 右眼所在的行数 int eyeBrowThreshold; // 区分眉毛与眼睛之间的阈值 uchar* rowPtr = NULL; // 指向图片每行的指针 uchar* rowPtrTemp = NULL; // 指向图片每行的指针, 中间变量 IplImage* eyeImg = NULL; // 存储眼睛的图像 CvRect eyeRect; // 存储裁剪后的人眼的矩形区域 CvRect eyeRectTemp; // 临时矩形区域 IplImage* lEyeImg = NULL; // 存储左眼的图像 IplImage* rEyeImg = NULL; // 存储右眼的图像 IplImage* lEyeImgNoEyebrow = NULL; // 存储去除眉毛之后的左眼图像 IplImage* rEyeImgNoEyebrow = NULL; // 存储去除眉毛之后的右眼图像 IplImage* lEyeballImg = NULL; // 存储最终分割的左眼框的图像 IplImage* rEyeballImg = NULL; // 存储最终分割的右眼框的图像 IplImage* lMinEyeballImg = NULL; // 存储最终分割的最小的左眼框的图像 IplImage* rMinEyeballImg = NULL; // 存储最终分割的最小的右眼框的图像 int lMinEyeballBlackPixel; // 存储最终分割的最小的左眼框的白色像素个数 int rMinEyeballBlackPixel; // 存储最终分割的最小的右眼框的白色像素个数 double lMinEyeballBlackPixelRate; // 存储最终分割的最小的左眼框的黑色像素占的比例 double rMinEyeballBlackPixelRate; // 存储最终分割的最小的右眼框的黑色像素占的比例 double lMinEyeballRectShape; // 存储最小左眼眶的矩形长宽比值 double rMinEyeballRectShape; // 存储最小右眼眶的矩形长宽比值 double lMinEyeballBeta; // 存储最小左眼眶的中间1/2区域的黑像素比值 double rMinEyeballBeta; // 存储最小右边眼眶的中间1/2区域的黑像素比值 int lEyeState; // 左眼睁(0)、闭(1)状态 int rEyeState; // 右眼睁(0)、闭(1)状态 int eyeState; // 眼睛综合睁(0)、闭(1)状态 int eyeCloseNum = 0; // 统计一次检测过程中闭眼的总数 int eyeCloseDuration = 0; // 统计一次检测过程中连续检测到闭眼状态的次数 int maxEyeCloseDuration = 0; // 一次检测过程中连续检测到闭眼状态的次数的最大值 int failFaceNum = 0; // 统计一次检测过程中未检测到人脸的总数 int failFaceDuration = 0; // 统计一次检测过程中连续未检测到人脸的次数 int maxFailFaceDuration = 0; // 一次检测过程中连续未检测到人脸的次数的最大值 int fatigueState = 1; // 驾驶员的驾驶状态:疲劳驾驶(1),正常驾驶(0) /****************** 创建显示窗口 *******************/ cvNamedWindow("img", CV_WINDOW_AUTOSIZE); // 显示灰度源图像 cvNamedWindow("分割后的人脸", 1); // 显示分割出大致眼眶区域的人脸 cvNamedWindow("大致的左眼区域", 1); // 显示大致的左眼区域 cvNamedWindow("大致的右眼区域", 1); // 显示大致的右眼区域 cvNamedWindow("l_binary"); // 显示大致右眼区域的二值化图像 cvNamedWindow("r_binary"); // 显示大致左眼区域的二值化图像 cvNamedWindow("lEyeImgNoEyebrow", 1); // 显示去除眉毛区域的左眼图像 cvNamedWindow("rEyeImgNoEyebrow", 1); // 显示去除眉毛区域的右眼图像 cvNamedWindow("lEyeCenter", 1); // 显示标出虹膜中心的左眼图像 cvNamedWindow("rEyeCenter", 1); // 显示标出虹膜中心的右眼图像 cvNamedWindow("lEyeballImg", 1); // 根据lEyeImgNoEyebrow大小的1/2区域重新划分的左眼图像 cvNamedWindow("rEyeballImg", 1); // 根据rEyeImgNoEyebrow大小的1/2区域重新划分的右眼图像 cvNamedWindow("lkai", 1); // 左眼进行开运算之后的图像 cvNamedWindow("rkai", 1); // 右眼进行开运算之后的图像 cvNamedWindow("lMinEyeballImg", 1); // 缩小至边界区域的左眼虹膜图像 cvNamedWindow("rMinEyeballImg", 1); // 缩小至边界区域的右眼眼虹膜图像 capture = cvCreateCameraCapture(0); if( capture == NULL ) return -1; for( globalK = 1; globalK <= DETECTTIME; globalK ++ ){ start = clock(); srcImg = cvQueryFrame(capture); img = cvCreateImage(cvGetSize(srcImg), IPL_DEPTH_8U, 1); cvCvtColor(srcImg, img, CV_BGR2GRAY); if( !img ) continue; cvShowImage("img", img); cvWaitKey(20); /******************** 检测人脸 *************************/ cvClearMemStorage(storage); // 将存储块的 top 置到存储块的头部,既清空存储块中的存储内容 detectFace( img, // 灰度图像 objects, // 输出参数:检测到人脸的矩形框 storage, // 存储矩形框的内存区域 scale_factor, // 搜索窗口的比例系数 min_neighbors, // 构成检测目标的相邻矩形的最小个数 flags, // 操作方式 cvSize(20, 20) // 检测窗口的最小尺寸 ); // 提取人脸区域 if ( !objectsTemp->total ){ printf("Failed to detect face!\n"); // 调试代码 failFaceNum ++; // 统计未检测到人脸的次数 failFaceDuration ++; // 统计连续未检测到人脸的次数 // 检测过程中判断全是闭眼和检测不到人脸的情况,没有睁开眼的情况,导致maxEyeCloseDuration = 0; (eyeCloseDuration > maxEyeCloseDuration) ? maxEyeCloseDuration = eyeCloseDuration : maxEyeCloseDuration; eyeCloseDuration = 0; if( globalK == DETECTTIME ){ // 当一次检测过程中,所有的过程都检测不到人脸,则要在此更新 maxFailFaceDuration (failFaceDuration > maxFailFaceDuration) ? maxFailFaceDuration = failFaceDuration : maxFailFaceDuration; printf("\nFATIGUETHRESHOLD: %d\n", FATIGUETHRESHOLD); printf("eyeCloseNum: %d\tmaxEyeCloseDuration: %d\n", eyeCloseNum, maxEyeCloseDuration); printf("failFaceNum: %d\tmaxFailFaceDuration: %d\n", failFaceNum, maxFailFaceDuration); // 进行疲劳状态的判别 fatigueState = recoFatigueState(FATIGUETHRESHOLD, eyeCloseNum, maxEyeCloseDuration, failFaceNum, maxFailFaceDuration); if( fatigueState == 1 ) printf("驾驶员处于疲劳驾驶状态\n\n"); else if( fatigueState == 0 ) printf("驾驶员处于正常驾驶状态\n\n"); // 进入下一次检测过程前,将变量清零 globalK = 0; lEyeState = 1; rEyeState = 1; eyeState = 1; eyeCloseNum = 0; eyeCloseDuration = 0; maxEyeCloseDuration = 0; failFaceNum = 0; failFaceDuration = 0; maxFailFaceDuration = 0; fatigueState = 1; cvWaitKey(0); } continue; } else{ // 统计连续未检测到人脸的次数中的最大数值 (failFaceDuration > maxFailFaceDuration) ? maxFailFaceDuration = failFaceDuration : maxFailFaceDuration; failFaceDuration = 0; // 找到检测到的最大的人脸矩形区域 temp = 0; for(i = 0; i < (objectsTemp ? objectsTemp->total : 0); i ++) { CvRect* rect = (CvRect*) cvGetSeqElem(objectsTemp, i); if ( (rect->height * rect->width) > temp ){ largestFaceRect = rect; temp = rect->height * rect->width; } } // 根据人脸的先验知识分割出大致的人眼区域 temp = largestFaceRect->width / 8; largestFaceRect->x = largestFaceRect->x + temp; largestFaceRect->width = largestFaceRect->width - 3*temp/2; largestFaceRect->height = largestFaceRect->height / 2; largestFaceRect->y = largestFaceRect->y + largestFaceRect->height / 2; largestFaceRect->height = largestFaceRect->height / 2; cvSetImageROI(img, *largestFaceRect); // 设置ROI为检测到的最大的人脸区域 faceImg = cvCreateImage(cvSize(largestFaceRect->width, largestFaceRect->height), IPL_DEPTH_8U, 1); cvCopy(img, faceImg, NULL); cvResetImageROI(img); // 释放ROI cvShowImage("分割后的人脸", faceImg); eyeRectTemp = *largestFaceRect; // 根据人脸的先验知识分割出大致的左眼区域 largestFaceRect->width /= 2; cvSetImageROI(img, *largestFaceRect); // 设置ROI为检测到的最大的人脸区域 lEyeImg = cvCreateImage(cvSize(largestFaceRect->width, largestFaceRect->height), IPL_DEPTH_8U, 1); cvCopy(img, lEyeImg, NULL); cvResetImageROI(img); // 释放ROI cvShowImage("大致的左眼区域", lEyeImg); // 根据人脸的先验知识分割出大致的右眼区域 eyeRectTemp.x += eyeRectTemp.width / 2; eyeRectTemp.width /= 2; cvSetImageROI(img, eyeRectTemp); // 设置ROI为检测到的最大的人脸区域 rEyeImg = cvCreateImage(cvSize(eyeRectTemp.width, eyeRectTemp.height), IPL_DEPTH_8U, 1); cvCopy(img, rEyeImg, NULL); cvResetImageROI(img); // 释放ROI cvShowImage("大致的右眼区域", rEyeImg); /***************** 二值化处理 **********************/ // 图像增强:直方图均衡化在detectFace中实现了一次;可尝试非线性点运算 /*** 二值化左眼大致区域的图像 ***/ //lineTrans(lEyeImg, lEyeImg, 1.5, 0); // 线性点运算 cvSmooth(lEyeImg, lEyeImg, CV_MEDIAN); // 中值滤波 默认窗口大小为3*3 nonlineTrans(lEyeImg, lEyeImg, 0.8); // 非线性点运算 memset(hist, 0, sizeof(hist)); // 初始化直方图的数组为0 histogram(lEyeImg, hist); // 计算图片直方图 // 计算最佳阈值 pixelSum = lEyeImg->width * lEyeImg->height; threshold = ostuThreshold(hist, pixelSum, 45); cvThreshold(lEyeImg, lEyeImg, threshold, 255, CV_THRESH_BINARY);// 对图像二值化 // 显示二值化后的图像 cvShowImage("l_binary",lEyeImg); /*** 二值化右眼大致区域的图像 ***/ //lineTrans(rEyeImg, rEyeImg, 1.5, 0); // 线性点运算 cvSmooth(rEyeImg, rEyeImg, CV_MEDIAN); // 中值滤波 默认窗口大小为3*3 nonlineTrans(rEyeImg, rEyeImg, 0.8); // 非线性点运算 memset(hist, 0, sizeof(hist)); // 初始化直方图的数组为0 histogram(rEyeImg, hist); // 计算图片直方图 // 计算最佳阈值 pixelSum = rEyeImg->width * rEyeImg->height; threshold = ostuThreshold(hist, pixelSum, 45); cvThreshold(rEyeImg, rEyeImg, threshold, 255, CV_THRESH_BINARY);// 对图像二值化 // 显示二值化后的图像 cvShowImage("r_binary",rEyeImg); /********************** 检测人眼 ***********************/ /** 如果有明显的眉毛区域,则分割去除眉毛 **/ // 分割左眼眉毛 HEIGHT = lEyeImg->height; WIDTH = lEyeImg->width; // 分配内存 horiProject = (int*)malloc(HEIGHT * sizeof(int)); vertProject = (int*)malloc(WIDTH * sizeof(int)); if( horiProject == NULL || vertProject == NULL ){ printf("Failed to allocate memory\n"); cvWaitKey(0); return -1; } // 内存置零 for(i = 0; i < HEIGHT; i ++) *(horiProject + i) = 0; for(i = 0; i < WIDTH; i ++) *(vertProject + i) = 0; histProject(lEyeImg, horiProject, vertProject); // 计算直方图投影 lEyeRow = removeEyebrow(horiProject, WIDTH, HEIGHT, 10); // 计算分割眉毛与眼框的位置 // 分割右眼眉毛 HEIGHT = rEyeImg->height; WIDTH = rEyeImg->width; // 分配内存 horiProject = (int*)malloc(HEIGHT * sizeof(int)); vertProject = (int*)malloc(WIDTH * sizeof(int)); if( horiProject == NULL || vertProject == NULL ){ printf("Failed to allocate memory\n"); cvWaitKey(0); return -1; } // 内存置零 for(i = 0; i < HEIGHT; i ++) *(horiProject + i) = 0; for(i = 0; i < WIDTH; i ++) *(vertProject + i) = 0; histProject(rEyeImg, horiProject, vertProject); // 计算直方图投影 rEyeRow = removeEyebrow(horiProject, WIDTH, HEIGHT, 10); // 计算分割眉毛与眼框的位置 // 显示去除眉毛后的人眼大致区域 eyeRect = cvRect(0, lEyeRow, lEyeImg->width, (lEyeImg->height - lEyeRow)); // 去眉毛的眼眶区域在lEyeImg中的矩形框区域 cvSetImageROI(lEyeImg, eyeRect); // 设置ROI为去除眉毛的眼眶,在下面释放ROI lEyeImgNoEyebrow = cvCreateImage(cvSize(eyeRect.width, eyeRect.height), IPL_DEPTH_8U, 1); cvCopy(lEyeImg, lEyeImgNoEyebrow, NULL); cvShowImage("lEyeImgNoEyebrow", lEyeImgNoEyebrow); eyeRectTemp = cvRect(0, rEyeRow, rEyeImg->width, (rEyeImg->height - rEyeRow)); // 去眉毛的眼眶区域在rEyeImg中的矩形框区域 cvSetImageROI(rEyeImg, eyeRectTemp); // 设置ROI为去除眉毛的眼眶,在下面释放ROI rEyeImgNoEyebrow = cvCreateImage(cvSize(eyeRectTemp.width, eyeRectTemp.height), IPL_DEPTH_8U, 1); cvCopy(rEyeImg, rEyeImgNoEyebrow, NULL); cvShowImage("rEyeImgNoEyebrow", rEyeImgNoEyebrow); ///////// 定位眼睛中心点在去除眉毛图像中的行列位置 /////////// HEIGHT = lEyeImgNoEyebrow->height; WIDTH = lEyeImgNoEyebrow->width; // 分配内存 subhoriProject = (int*)malloc(HEIGHT * sizeof(int)); subvertProject = (int*)malloc(WIDTH * sizeof(int)); if( subhoriProject == NULL || subvertProject == NULL ){ printf("Failed to allocate memory\n"); cvWaitKey(0); return -1; } // 内存置零 for(i = 0; i < HEIGHT; i ++) *(subhoriProject + i) = 0; for(i = 0; i < WIDTH; i ++) *(subvertProject + i) = 0; histProject(lEyeImgNoEyebrow, subhoriProject, subvertProject); // 重新对分割出的左眼图像进行积分投影 lEyeRow = getEyePos(subhoriProject, HEIGHT, HEIGHT/5); // 定位左眼所在的行 lEyeCol = getEyePos(subvertProject, WIDTH, WIDTH/5); // 定位左眼所在的列 HEIGHT = rEyeImgNoEyebrow->height; WIDTH = rEyeImgNoEyebrow->width; // 分配内存 subhoriProject = (int*)malloc(HEIGHT * sizeof(int)); subvertProject = (int*)malloc(WIDTH * sizeof(int)); if( subhoriProject == NULL || subvertProject == NULL ){ printf("Failed to allocate memory\n"); cvWaitKey(0); return -1; } // 内存置零 for(i = 0; i < HEIGHT; i ++) *(subhoriProject + i) = 0; for(i = 0; i < WIDTH; i ++) *(subvertProject + i) = 0; histProject(rEyeImgNoEyebrow, subhoriProject, subvertProject); // 重新对分割出的右眼图像进行积分投影 rEyeRow = getEyePos(subhoriProject, HEIGHT, HEIGHT/5); // 定位右眼所在的行 rEyeCol = getEyePos(subvertProject, WIDTH, WIDTH/5); // 定位右眼所在的列 // 标记眼睛的位置 cvCircle(lEyeImgNoEyebrow, cvPoint(lEyeCol, lEyeRow), 3, CV_RGB(0,0,255), 1, 8, 0); cvCircle(rEyeImgNoEyebrow, cvPoint(rEyeCol, rEyeRow), 3, CV_RGB(0,0,255), 1, 8, 0); cvShowImage("lEyeCenter", lEyeImgNoEyebrow); cvShowImage("rEyeCenter", rEyeImgNoEyebrow); /****************** 判断人眼睁闭状态 *************************/ ///////// 分割出以找到的中心为中心的大致眼眶 ///////////// // 左眼眶 HEIGHT = lEyeImgNoEyebrow->height; WIDTH = lEyeImgNoEyebrow->width; // 计算大致眼眶的区域: eyeRect eyeRect = cvRect(0, 0, WIDTH, HEIGHT); calEyeSocketRegion(&eyeRect, WIDTH, HEIGHT, lEyeCol, lEyeRow); cvSetImageROI(lEyeImgNoEyebrow, eyeRect); // 设置ROI为检测到眼眶区域 lEyeballImg = cvCreateImage(cvGetSize(lEyeImgNoEyebrow), IPL_DEPTH_8U, 1); cvCopy(lEyeImgNoEyebrow, lEyeballImg, NULL); cvResetImageROI(lEyeImgNoEyebrow); cvShowImage("lEyeballImg", lEyeballImg); // 右眼眶 HEIGHT = rEyeImgNoEyebrow->height; WIDTH = rEyeImgNoEyebrow->width; // 计算大致眼眶的区域: eyeRectTemp eyeRect = cvRect(0, 0, WIDTH, HEIGHT); calEyeSocketRegion(&eyeRect, WIDTH, HEIGHT, rEyeCol, rEyeRow); cvSetImageROI(rEyeImgNoEyebrow, eyeRect); // 设置ROI为检测到眼眶区域 rEyeballImg = cvCreateImage(cvGetSize(rEyeImgNoEyebrow), IPL_DEPTH_8U, 1); cvCopy(rEyeImgNoEyebrow, rEyeballImg, NULL); cvResetImageROI(rEyeImgNoEyebrow); cvShowImage("rEyeballImg", rEyeballImg); /////////////////////////// 闭运算 /////////////////////////// cvErode(lEyeballImg, lEyeballImg, NULL, 2); //腐蚀图像 cvDilate(lEyeballImg, lEyeballImg, NULL, 2); //膨胀图像 cvShowImage("lkai", lEyeballImg); cvErode(rEyeballImg, rEyeballImg, NULL, 1); //腐蚀图像 cvDilate(rEyeballImg, rEyeballImg, NULL, 1); //膨胀图像 cvShowImage("rkai", rEyeballImg); /////////////////// 计算最小眼睛的矩形区域 //////////////////// ///////////////////////////左眼 HEIGHT = lEyeballImg->height; WIDTH = lEyeballImg->width; // 分配内存 subhoriProject = (int*)malloc(HEIGHT * sizeof(int)); subvertProject = (int*)malloc(WIDTH * sizeof(int)); if( subhoriProject == NULL || subvertProject == NULL ){ printf("Failed to allocate memory\n"); cvWaitKey(0); return -1; } // 内存置零 for(i = 0; i < HEIGHT; i ++) *(subhoriProject + i) = 0; for(i = 0; i < WIDTH; i ++) *(subvertProject + i) = 0; histProject(lEyeballImg, subhoriProject, subvertProject); // 计算左眼最小的矩形区域 eyeRectTemp = cvRect(0, 0 , 1, 1); // 初始化 getEyeMinRect(&eyeRectTemp, subhoriProject, subvertProject, WIDTH, HEIGHT, 5, 3); // 计算最小左眼矩形的长宽比, 判断眼睛状态时用的到 lMinEyeballRectShape = (double)eyeRectTemp.width / (double)eyeRectTemp.height; cvSetImageROI(lEyeballImg, eyeRectTemp); // 设置ROI为检测到最小面积的眼眶 lMinEyeballImg = cvCreateImage(cvGetSize(lEyeballImg), IPL_DEPTH_8U, 1); cvCopy(lEyeballImg, lMinEyeballImg, NULL); cvResetImageROI(lEyeballImg); cvShowImage("lMinEyeballImg", lMinEyeballImg); //////////////////////// 统计左眼黑像素个数 ///////////////////// HEIGHT = lMinEyeballImg->height; WIDTH = lMinEyeballImg->width; // 分配内存 subhoriProject = (int*)malloc(HEIGHT * sizeof(int)); subvertProject = (int*)malloc(WIDTH * sizeof(int)); if( subhoriProject == NULL || subvertProject == NULL ){ printf("Failed to allocate memory\n"); cvWaitKey(0); return -1; } // 内存置零 for(i = 0; i < HEIGHT; i ++) *(subhoriProject + i) = 0; for(i = 0; i < WIDTH; i ++) *(subvertProject + i) = 0; histProject(lMinEyeballImg, subhoriProject, subvertProject); // 统计lEyeballImg中黑色像素的个数 temp = 0; // 白像素个数 for( i = 0; i < WIDTH; i ++ ) temp += *(subvertProject + i); temp /= 255; lMinEyeballBlackPixel = WIDTH * HEIGHT - temp; lMinEyeballBlackPixelRate = (double)lMinEyeballBlackPixel / (double)(WIDTH * HEIGHT); // 统计lMinEyeballImg中的1/2区域内黑像素的比例 lMinEyeballBeta = 0; lMinEyeballBeta = calMiddleAreaBlackPixRate(subvertProject, &eyeRectTemp, WIDTH, HEIGHT, lEyeCol, lMinEyeballBlackPixel); ////////////////////////////////////右眼 HEIGHT = rEyeballImg->height; WIDTH = rEyeballImg->width; // 分配内存 subhoriProject = (int*)malloc(HEIGHT * sizeof(int)); subvertProject = (int*)malloc(WIDTH * sizeof(int)); if( subhoriProject == NULL || subvertProject == NULL ){ printf("Failed to allocate memory\n"); cvWaitKey(0); return -1; } // 内存置零 for(i = 0; i < HEIGHT; i ++) *(subhoriProject + i) = 0; for(i = 0; i < WIDTH; i ++) *(subvertProject + i) = 0; histProject(rEyeballImg, subhoriProject, subvertProject); // 计算右眼最小的矩形区域 eyeRectTemp = cvRect(0, 0 , 1, 1); getEyeMinRect(&eyeRectTemp, subhoriProject, subvertProject, WIDTH, HEIGHT, 5, 3); // 计算最小右眼矩形的长宽比,判断眼睛状态时用的到 rMinEyeballRectShape = (double)eyeRectTemp.width / (double)eyeRectTemp.height; cvSetImageROI(rEyeballImg, eyeRectTemp); // 设置ROI为检测到最小面积的眼眶 rMinEyeballImg = cvCreateImage(cvGetSize(rEyeballImg), IPL_DEPTH_8U, 1); cvCopy(rEyeballImg, rMinEyeballImg, NULL); cvResetImageROI(rEyeballImg); cvShowImage("rMinEyeballImg", rMinEyeballImg); //////////////////////// 统计右眼黑像素个数 ///////////////////// HEIGHT = rMinEyeballImg->height; WIDTH = rMinEyeballImg->width; // 分配内存 subhoriProject = (int*)malloc(HEIGHT * sizeof(int)); subvertProject = (int*)malloc(WIDTH * sizeof(int)); if( subhoriProject == NULL || subvertProject == NULL ){ printf("Failed to allocate memory\n"); cvWaitKey(0); return -1; } // 内存置零 for(i = 0; i < HEIGHT; i ++) *(subhoriProject + i) = 0; for(i = 0; i < WIDTH; i ++) *(subvertProject + i) = 0; histProject(rMinEyeballImg, subhoriProject, subvertProject);// 计算直方图积分投影 // 统计lEyeballImg中黑色像素的个数 temp = 0; for( i = 0; i < WIDTH; i ++ ) temp += *(subvertProject + i); temp /= 255; rMinEyeballBlackPixel = WIDTH * HEIGHT - temp; rMinEyeballBlackPixelRate = (double)rMinEyeballBlackPixel / (double)(WIDTH * HEIGHT); // 统计lMinEyeballImg中的1/2区域内黑像素的比例 rMinEyeballBeta = 0; rMinEyeballBeta = calMiddleAreaBlackPixRate(subvertProject, &eyeRectTemp, WIDTH, HEIGHT, rEyeCol, rMinEyeballBlackPixel); // 判断眼睛睁闭情况 lEyeState = 1; // 左眼状态,默认闭眼 rEyeState = 1; // 右眼状态,默认闭眼 eyeState = 1; // 眼睛综合状态,默认闭眼 if( lMinEyeballBlackPixel > 50) lEyeState = getEyeState(lMinEyeballRectShape, lMinEyeballBlackPixelRate, lMinEyeballBeta); else lEyeState = 1; if( rMinEyeballBlackPixel > 50) rEyeState = getEyeState(rMinEyeballRectShape, rMinEyeballBlackPixelRate, rMinEyeballBeta); else rEyeState = 1; (lEyeState + rEyeState) == 2 ? eyeState = 1 : eyeState=0; // 统计眼睛闭合的次数 if( eyeState == 1 ){ eyeCloseNum ++; // 统计 eyeCloseNum 眼睛闭合次数 eyeCloseDuration ++; if( globalK == DETECTTIME){ // 检测过程中判断全是闭眼情况,没有睁眼和检测不到人脸的情况 (eyeCloseDuration > maxEyeCloseDuration) ? maxEyeCloseDuration = eyeCloseDuration : maxEyeCloseDuration; eyeCloseDuration = 0; } } else{ (eyeCloseDuration > maxEyeCloseDuration) ? maxEyeCloseDuration = eyeCloseDuration : maxEyeCloseDuration; eyeCloseDuration = 0; } } // 承接判断是否检测到人脸的if语句 // 计时:执行一次循环的时间 stop = clock(); //printf("run time: %f\n", (double)(stop - start) / CLOCKS_PER_SEC); printf("eyeState: %d\n", eyeState); // 调整循环变量,进入下一次检测过程 if( globalK == DETECTTIME ){ printf("\nFATIGUETHRESHOLD*****: %d\n", FATIGUETHRESHOLD); printf("eyeCloseNum: %d\tmaxEyeCloseDuration: %d\n", eyeCloseNum, maxEyeCloseDuration); printf("failFaceNum: %d\tmaxFailFaceDuration: %d\n", failFaceNum, maxFailFaceDuration); // 进行疲劳状态的判别 fatigueState = recoFatigueState(FATIGUETHRESHOLD, eyeCloseNum, maxEyeCloseDuration, failFaceNum, maxFailFaceDuration); if( fatigueState == 1 ) printf("驾驶员处于疲劳驾驶状态\n\n"); else if( fatigueState == 0 ) printf("驾驶员处于正常驾驶状态\n\n"); // 进入下一次检测过程前,将变量清零 globalK = 0; lEyeState = 1; rEyeState = 1; eyeState = 1; eyeCloseNum = 0; eyeCloseDuration = 0; maxEyeCloseDuration = 0; failFaceNum = 0; failFaceDuration = 0; maxFailFaceDuration = 0; fatigueState = 1; char c = cvWaitKey(0); if( c == 27 ) break; else continue; } } // 承接检测过程的 for 循环 // 释放内存 cvDestroyWindow("分割后的人脸"); cvDestroyWindow("大致的左眼区域"); cvDestroyWindow("大致的右眼区域"); cvDestroyWindow("l_binary"); cvDestroyWindow("r_binary"); cvDestroyWindow("lEyeImgNoEyebrow"); cvDestroyWindow("rEyeImgNoEyebrow"); cvDestroyWindow("lEyeCenter"); cvDestroyWindow("rEyeCenter"); cvDestroyWindow("lEyeballImg"); cvDestroyWindow("rEyeballImg"); cvDestroyWindow("lkai"); cvDestroyWindow("rkai"); cvDestroyWindow("lMinEyeballImg"); cvDestroyWindow("rMinEyeballImg"); cvReleaseMemStorage(&storage); cvReleaseImage(&eyeImg); free(horiProject); free(vertProject); free(subhoriProject); free(subvertProject); return 0;}
    注意之处
    最佳识别效果的图像大小:500x550,太小了识别效果骤减为了传递人脸检测的序列结果到主函数中,设定了一个外部变量CvSeq *objectTemp主函数涉及到多个自定义的阈值:根据先验知识分割人眼区域,Ostu阈值减去常数CONST,区分眉毛与眼睛的阈值eyeBrowThreshold,判断眼睛具体位置时用到的中间区域,判断眼睛状态的getEyeState()中的阈值
    待改进之处
    程序中多次用到了图像增强的算法,理清楚程序的结构,看能不能优化
    detectFace中有直方图均衡化的代码,看是否需要进行均衡化处理?直方图均衡化对增强比较暗的图像效果很明显
    二值化效果有待改进,尤其是CONST的值的确定!直方图均衡化对增强比较暗的图像效果很明显
    理清楚主函数中内存的使用情况,尤其是指针变量
    自定义的阈值要根据汽车室内的监控图像质量的大小进行最后的调试

    2. detectFace()程序的功能根据Adaboost算法检测出图片中的人脸。
    源码/**************************************************功能:检测图片中的人脸区域输入: IplImage* srcImg, // 灰度图像 CvMemStorage* storage, // 存储矩形框的内存区域 double scale_factor = 1.1, // 搜索窗口的比例系数 int min_neighbors = 3, // 构成检测目标的相邻矩形的最小个数 int flags = 0, // 操作方式 CvSize min_size = cvSize(20, 20) // 检测窗口的最小尺寸输出参数: CvSeq* objects // 检测到人脸的矩形框说明:1. 识别的准确率和速度关键在于cvHaarDetectObject()函数的参数的调整 2. 如果实际用于汽车内检测效果不佳时,可考虑自己搜集汽车室内图片然后训练分类器 3. 实际用于疲劳驾驶检测时,由于人脸位于图片的中央而且占的面积很大,可以将min_size和scale_factor调大一些,加快速度 4. 内含直方图均衡化**************************************************/#include "cv.h"#include "stdlib.h"#include "highgui.h"extern CvSeq* objectsTemp; // 传递objects的值会main()void detectFace( IplImage* srcImg, // 灰度图像 CvSeq* objects, // 输出参数:检测到人脸的矩形框 CvMemStorage* storage, // 存储矩形框的内存区域 double scale_factor = 1.1, // 搜索窗口的比例系数 int min_neighbors = 3, // 构成检测目标的相邻矩形的最小个数 int flags = 0, // 操作方式 CvSize min_size = cvSize(20, 20) // 检测窗口的最小尺寸){ // 程序用到的参数 const char* cascadeName = "haarcascade_frontalface_alt2.xml"; // 级联分类器的xml文件名 // 读取级联分类器xml文件 CvHaarClassifierCascade* cascade = (CvHaarClassifierCascade*)cvLoad(cascadeName, 0, 0, 0); if( !cascade ) { fprintf( stderr, "ERROR: Could not load classifier cascade\n" ); cvWaitKey(0); exit(-1); } // 检测人脸 cvClearMemStorage(storage); objects = cvHaarDetectObjects( srcImg, cascade, storage, scale_factor, min_neighbors, flags, /*CV_HAAR_DO_CANNY_PRUNING*/ min_size ); objectsTemp = objects; // 为了将objects的值传递回main函数 // 释放cascade的内存 cvReleaseHaarClassifierCascade(&cascade);}
    改进之处
    detectFace()中有直方图均衡化的代码,看是否需要进行均衡化处理
    识别的准确率和速度关键在于cvHaarDetectObject()函数的参数的调整
    如果实际用于汽车内检测效果不佳时,可考虑自己搜集汽车室内图片然后训练分类器
    实际用于疲劳驾驶检测时,由于人脸位于图片的中央而且占的面积很大,可以将min_size和scale_factor调大一些,加快速度,但要保证准确率
    可实现并行运算

    3. ostuThreshold()函数程序功能用Ostu最大类间距方差法计算二值化阈值,然后减去自定义常数CONST。
    程序思想由于用ostu计算得出的阈值进行二值化时效果不理想,因此考虑减去一个固定值来补偿。
    源码/******************************************************功能:用Ostu最大类间方差法计算二值化阈值输入: hist:图像的直方图数组 pixelSum:图像的像素总和 CONST: 一个常数;为了适应各种特殊的要求,可实现在找到的最优分割阈值的基础上减去该常数输出: threshold:最优阈值Date: 2014.08.14******************************************************/#pragma once#include <stdio.h>int ostuThreshold(int * hist, int pixelSum, const int CONST){ float pixelPro[256]; int i, j, threshold = 0; //计算每个像素在整幅图像中的比例 for(i = 0; i < 256; i++){ *(pixelPro+i) = (float)(*(hist+i)) / (float)(pixelSum); } //经典ostu算法,得到前景和背景的分割 //遍历灰度级[0,255],计算出方差最大的灰度值,为最佳阈值 float w0, w1, u0tmp, u1tmp, u0, u1, u,deltaTmp, deltaMax = 0; for(i = 0; i < 256; i++){ w0 = w1 = u0tmp = u1tmp = u0 = u1 = u = deltaTmp = 0; for(j = 0; j < 256; j++){ if(j <= i){ //背景部分 //以i为阈值分类,第一类总的概率 w0 += *(pixelPro+j); u0tmp += j * (*(pixelPro+j)); } else //前景部分 { //以i为阈值分类,第二类总的概率 w1 += *(pixelPro+j); u1tmp += j * (*(pixelPro+j)); } } u0 = u0tmp / w0; //第一类的平均灰度 u1 = u1tmp / w1; //第二类的平均灰度 u = u0tmp + u1tmp; //整幅图像的平均灰度 //计算类间方差 deltaTmp = w0 * (u0 - u)*(u0 - u) + w1 * (u1 - u)*(u1 - u); //找出最大类间方差以及对应的阈值 if(deltaTmp > deltaMax){ deltaMax = deltaTmp; threshold = i; } } printf("Ostu Threshold: %d\n", threshold); printf("real Threshold: %d\n", threshold - CONST); //返回最佳阈值; return (threshold - CONST);}
    注意之处
    进行二值化处理之前,先进行了cvSmooth中值滤波处理、nonlineTrans非线性处理
    改进之处
    由于ostu计算得出的阈值不太符合要求,因此可以尝试其他的阈值选取方法
    寻找动态确定CONST常数的方法,以适应更多不同情况。考虑原图很暗,ostu计算出来的阈值本来就很低,结果还被减去CONST导致阈值太低的情况!还有,由于图像太暗,导致二值化后黑色像素过多的情况
    可实现并行运算

    4. histProject()函数程序功能计算直方图在水平方向和垂直方向的积分投影。
    程序思想按行累加实现水平方向的积分投影;按列累加实现垂直方向的积分投影。在一次遍历像素点的过程中实现水平和垂直方向的积分投影。
    源码/**************************************************功能:计算图像直方图在水平方向和垂直方向的投影输入: srcImg:源图像输出: horiProj: 水平方向的投影结果;1 * height数组的指针,输入前记得初始化 vertProj:垂直方向的投影结果;1 * width数组的指针,输入前记得初始化**************************************************/#include "cv.h"void histProject(IplImage * srcImg, int* horiProj, int* vertProj){ // 程序用到的参数 int i, j; uchar* ptr = NULL; // 指向图像当前行首地址的指针 uchar* temp = NULL; int HEIGHT = srcImg->height; int WIDTH = srcImg->width; for(i = 0; i < HEIGHT; i ++){ ptr = (uchar*) (srcImg->imageData + i * srcImg->widthStep); for(j = 0; j < WIDTH; j ++){ temp = ptr + j; // 减少计算量 *(horiProj + i) += *temp; // 计算水平方向的投影 *(vertProj + j) += *temp; // 计算垂直方向的投影 } }}
    注意之处
    传递给histProject的图像必须是灰度图像
    因为涉及到累加运算,所以horiProject和vertProject指针一定要初始化为0

    改进之处
    传递给histProject的图像必须是灰度图像
    可实现并行运算

    5. getEyePos()函数程序功能找出数列中限定区域内的最低点的位置,即找到人眼的位置。
    程序思想先对直方图积分投影结果进行升序排序,然后找出最小值并且判断是否在设定的中间区域内,如果在则输出index索引值,否则对下一个最小值进行相同判断,直到找到第一个符合条件的最小值,然后返回该最小值的索引index。
    源码#include <cv.h>#include <stdlib.h>typedef struct{ int data; int index; }projectArr;// qsort的函数参数int cmpInc( const void *a ,const void *b){ return (*(projectArr *)a).data - (*(projectArr *)b).data;}int getEyePos(int* project, int size, int region){ // 参数 projectArr* projectStruct = NULL; projectArr* projectTemp = NULL; int i, j, pos, sizeTemp, temp; // 分配projectStruct内存空间 projectStruct = (projectArr*)malloc(size * sizeof(projectArr)); projectTemp = (projectArr*)malloc(sizeof(projectArr)); // 初始化内存空间 for(i = 0; i < size; i ++){ (projectStruct + i)->data = *(project + i); (projectStruct + i)->index = i; } // 对project从小到大快速排序 //qsort(projectStruct, size, sizeof(*project), cmpInc); for(i = 0; i <= size - 2; i ++){ for( j = 0; j < size - i - 1; j ++ ){ if( (projectStruct + j)->data > (projectStruct + j + 1)->data ){ *projectTemp = *(projectStruct + j); *(projectStruct + j) = *(projectStruct + j + 1); *(projectStruct + j + 1) = *projectTemp; } } } // 寻找中间区域的最小值及其位置 sizeTemp = size / 2; temp = 0; for( i = 0; i < size; i ++ ){ temp = (projectStruct+i)->index; if( (temp > sizeTemp - region) && (temp < sizeTemp + region) ){ pos = (projectStruct + i)->index; // 防止指针越界访问位置元素出现负数 if( pos < 0) return -1; break; } else{ // 防止整个数列不存在符合条件的元素 if( i == size - 1 ) return -1; } } free(projectTemp); return pos;}
    注意之处
    projectStruct指针的内存释放有问题
    升序排序的方法用的是冒泡排序
    定义了外部变量结构体projectArr

    改进之处
    用快速排序对数列进行排序,可加快速度
    考虑投影值相同但是index不同的情况的处理办法,因为很多时候不能很准确找到中心点就是这个原因
    考虑加入左右眼二值化图像的参数,消除头发或者背景等大片黑块对中心点确定的影响

    6. removeEyebrow()函数程序功能搜索积分投影图的最低点,从而消除眉毛。
    程序思想找到眉毛与眼睛分割的点,然后去除分割点上方的部分,从而消除眉毛。在找分割点时,以3行像素的和为单位进行逐个逐个比较,找到最小的单位。然后以该单位为搜索起点,搜索第一个最高点,然后以该最高点为分割点,即图中箭头位置,去除分割点上方的部分。

    源码/************************************************************功能:搜索积分投影图中的最低点,从而消除眉毛的函数输入: int* horiProject: 数列的指针 int width: 数列的宽度 int height: 数列的高度 int threshold:分割眉毛的阈值,最多输出: 返回找到的最低点行位置,结果为int类型,即眉毛与眼睛的分割线说明: 1. 消除眉毛时可以调整eyeBrowThreshold来调整去除的效果 2. 同时可以调整连续大于阈值的次数count来调整效果。************************************************************/int removeEyebrow(int* horiProject, int width, int height, int threshold){ // 参数 int temp, temp1, count, flag, i; int eyeRow; int eyeBrowThreshold; // 定位人眼位置 eyeBrowThreshold = (width - threshold) * 255; // 为了防止无法区分眼睛和眉毛的情况,可适当降低阈值 // 消除眉毛区域 temp = 100000000; temp1 = 0; count = 0; flag = 0; // 表示当前搜索的位置在第一个最低谷之前 eyeRow = 0; for(i = 0; i < height; i = i + 3){ count ++; temp1 = *(horiProject + i) + *(horiProject + i + 1) + *(horiProject + i + 2); if( (temp1 < temp) & (flag == 0) ){ temp = temp1; eyeRow = i; count = 0; } if (count >= 3 || i >= height - 2){ flag = 1; break; } } // 搜索第一个大于眼睛与眉毛分割阈值的点 count = 0; for( i = eyeRow; i < height; i ++ ){ if( *(horiProject + i) > eyeBrowThreshold){ eyeRow = i; count ++; if( count >= 3 ){ // count: 统计共有多少连续的行的投影值大于阈值; eyeRow = i; break; } } else count = 0;} // 防止没有眉毛错删眼睛的情况,可根据实验结果调整参数! if( eyeRow >= height / 2 ) eyeRow = 0; return eyeRow;}
    注意之处
    消除眉毛时可以调整eyeBrowThreshold来调整去除的效果
    同时可以调整连续大于阈值的次数count来调整效果
    调整单位的像素行数,可以一定程度提高判断的准确率,但是单位太大的话不利于处理比较小的图像

    改进之处
    有时间的话可以考虑重新设置函数的变量,使函数更易于阅读
    根据实际的图像调整参数,使得结果更准确

    7. calEyeSocketRegion()函数程序功能特定功能函数:根据人眼的中心大致计算眼眶的区域。
    程序思想以人眼中心为中心,向外扩展直到扩展后的区域为原图区域的1/2大小。超出边界的情况要特殊处理。
    源码/************************************************************功能:特定功能函数:根据人眼的中心大致计算眼眶的区域输入: CvRect* eyeRect: 眼眶矩形区域的指针 int width: 数列的宽度 int height: 数列的高度 int EyeCol:虹膜中心所在的列位置 int EyeRow:虹膜中心所在的行位置输出: 以指针的方式返回眼眶的大致区域,eyeRect说明:************************************************************/void calEyeSocketRegion(CvRect* eyeRect, int width, int height, int EyeCol, int EyeRow){ // 参数 int temp, temp1; temp = EyeCol - width / 4; temp1 = EyeRow - height / 4; if( (temp < 0) && (temp1 < 0) ){ eyeRect->x = 0; eyeRect->width = width / 2 + temp; eyeRect->y = 0; eyeRect->height = height / 2 + temp1; } else if( (temp < 0) && (temp1 > 0) ){ eyeRect->x = 0; eyeRect->width = width / 2 + temp; eyeRect->y = temp1; eyeRect->height = height / 2; } else if( (temp > 0) && (temp1 < 0) ){ eyeRect->x = temp; eyeRect->width = width / 2; eyeRect->y = 0; eyeRect->height = height / 2 + temp1; } else if( (temp > 0) && (temp1 > 0) ){ eyeRect->x = temp; eyeRect->width = width / 2; eyeRect->y = temp1; eyeRect->height = height / 2; }}
    改进之处
    有时间的话可以考虑重新设置函数的变量,使函数更易于阅读
    根据实际的图像看是否需要调整当前比例

    8. gerEyeMinRect()函数程序功能消除眼睛区域周边的白色区域,计算人眼最小的矩形区域。
    程序思想从上下左右想中心搜索,如果搜索到有黑色像素的行或者列则停止搜索,并记录该处位置,从而得到最小的人眼区域。
    源码/************************************************************功能:特定功能函数:计算人眼最小的矩形区域输入: CvRect* eyeRect: 人眼最小的矩形区域的指针 int* horiProject int* vertProject int width: 数列的宽度 int height: 数列的高度 int horiThreshold:水平方向的阈值 int vertThreshold:垂直方向的阈值输出: 通过指针返回CvRect* eyeRect: 人眼最小的矩形区域的指针************************************************************/void getEyeMinRect(CvRect* eyeRect, int* horiProject, int* vertProject, int width, int height, int horiThreshold=5, int vertThreshold=3){ // 参数 int temp, temp1, i; temp1 = (width - horiThreshold) * 255; for(i = 0; i < height; i ++){ if( *(horiProject + i) < temp1 ){ eyeRect->y = i; break; } } temp = i; // 记录eyeRectTemp.y的位置 printf("eyeRectTemp->y: %d\n", eyeRect->y); if( temp != height ){ // temp != HEIGHT: 防止没有符合*(subhoriProject + i) < temp1条件的位置;如果temp != HEIGHT则一定有满足条件的位置存在 for(i = height-1; i >= 0; i --){ if( *(horiProject + i) < temp1 ){ temp = i; break; } } if( temp == eyeRect->y ) eyeRect->height = 1; else eyeRect->height = temp - eyeRect->y; } else{ eyeRect->height = 1; } printf("eyeRectTemp.height: %d\n", eyeRect->height); temp1 = (height - vertThreshold) * 255; for( i = 0; i < width; i ++ ){ if( *(vertProject + i) < temp1 ){ eyeRect->x = i; break; } } temp = i; // 记录eyeRectTemp.x的位置 printf("eyeRectTemp.x: %d\n", eyeRect->x); if( temp != width ){ for(i = width-1; i >= 0; i --){ if( *(vertProject + i) < temp1 ){ temp = i; break; } } // 防止宽度为0,显示图像时出错! if( temp == eyeRect->x ) eyeRect->width = 1; else eyeRect->width = temp - eyeRect->x; } else{ eyeRect->width = 1; } printf("eyeRectTemp.width: %d\n", eyeRect->width);}
    注意之处
    内涵调试用的输出语句,转化为硬件代码时记得删除调试语句
    改进之处
    有时间的话可以考虑重新设置函数的变量,使函数更易于阅读
    9. lineTrans()函数程序功能对图像进行线性点运算,实现图像增强效果
    程序思想遍历像素点,对每个像素点根据线性方程重新计算像素值。
    源码/********************************************************功能:对图像进行线性点运算,实现图像增强输入: IplImage* srcImg: 源灰度图像 float a:乘系数a float b:常系数b输出: IplImage* dstImg:输出经过线性变换后的图像********************************************************/#include "cv.h"#include "highgui.h"void lineTrans(IplImage* srcImg, IplImage* dstImg, float a, float b){ int i, j; uchar* ptr = NULL; // 指向图像当前行首地址的指针 uchar* pixel = NULL; // 指向像素点的指针 float temp; dstImg = cvCreateImage(cvGetSize(srcImg), IPL_DEPTH_8U, 1); cvCopy(srcImg, dstImg, NULL); int HEIGHT = dstImg->height; int WIDTH = dstImg->width; for(i = 0; i < HEIGHT; i ++){ ptr = (uchar*) (srcImg->imageData + i * srcImg->widthStep); for(j = 0; j < WIDTH; j ++){ pixel = ptr + j; // 线性变换 temp = a * (*pixel) + b; // 判断范围 if ( temp > 255 ) *pixel = 255; else if (temp < 0) *pixel = 0; else *pixel = (uchar)(temp + 0.5);// 四舍五入 } }}
    改进之处
    转到硬件时可以用查表的方式实现相同的效果
    可实现并行运算

    10. nonlineTrans()函数程序功能对图像进行非线性点运算,实现图像增强效果。
    程序思想遍历像素点,对每个像素点根据非线性方程重新计算像素值。
    源码/********************************************************功能:对图像进行线性点运算,实现图像增强输入: IplImage* srcImg: 源灰度图像 float a:乘系数a输出: IplImage* dstImg:输出经过线性变换后的图像********************************************************/#include "cv.h"#include "highgui.h"#include "cv.h"void nonlineTrans(IplImage* srcImg, IplImage* dstImg, float a){ int i, j; uchar* ptr = NULL; // 指向图像当前行首地址的指针 uchar* pixel = NULL; // 指向像素点的指针 float temp; dstImg = cvCreateImage(cvGetSize(srcImg), IPL_DEPTH_8U, 1); cvCopy(srcImg, dstImg, NULL); int HEIGHT = dstImg->height; int WIDTH = dstImg->width; for(i = 0; i < HEIGHT; i ++){ ptr = (uchar*) (srcImg->imageData + i * srcImg->widthStep); for(j = 0; j < WIDTH; j ++){ pixel = ptr + j; // 非线性变换 temp = *pixel + (a * (*pixel) * (255 - *pixel)) / 255; // 判断范围 if ( temp > 255 ) *pixel = 255; else if (temp < 0) *pixel = 0; else *pixel = (uchar)(temp + 0.5);// 四舍五入 } }}
    改进之处
    转到硬件时可以用查表的方式实现相同的效果
    可实现并行运算

    11. recoEyeState()函数程序功能通过模糊综合评价的思想对指标进行分级,然后组合成一个函数,通过计算当前眼睛的函数值与阈值比较,从而判断眼睛的状态。
    程序思想根据最终提取出的人眼图像判断眼睛的睁开、闭合情况,可转化为判断评价问题,即根据现有的人眼数据,判断眼睛的状态。由于3个评价的指标评判眼睛状态的界限不太清晰,因此可通过模糊评价的方法对不同范围的指标划分等级,然后再将三个指标加权组合在一起。
    源码/****************************** 判断眼睛状态 *************************功能:通过模糊综合评价的思想判断眼睛的状态输入: double MinEyeballRectShape:眼睛矩形区域的长宽比 double MinEyeballBlackPixelRate:眼睛矩形区域黑像素点所占的比例 double MinEyeballBeta:眼睛中心1/2区域黑色像素点占总黑像素点的比例输出: 返回人眼睁开闭合的状态0:睁开,1:闭合说明: 1. 三个输入参数的阈值是自己设定的 2. 输出的结果参数的阈值需要调整 3. 为了转硬件方便,加快运算速度,将浮点运算转为了整数运算。*******************************************************************/#include <stdlib.h>int getEyeState(double MinEyeballRectShape, double MinEyeballBlackPixelRate, double MinEyeballBeta){ int eyeState; int funcResult; int shapeFuzzyLv, pixelFuzzyLv, betaFuzzyLv; // 三个参数对应的模糊级别的值 // 判定眼睛矩形区域的长宽比的模糊级别 shapeFuzzyLv = 0; if( (MinEyeballRectShape >= 0) && (MinEyeballRectShape <= 0.8) ) shapeFuzzyLv = 0; else if( MinEyeballRectShape <= 1.2 ) shapeFuzzyLv = 2; else if( MinEyeballRectShape <= 1.5 ) shapeFuzzyLv = 6; else if( MinEyeballRectShape <= 2.5 ) shapeFuzzyLv = 8; else if( MinEyeballRectShape <= 3 ) shapeFuzzyLv = 6; else shapeFuzzyLv = 0; // 判定眼睛矩形区域黑像素点所占比例的模糊级别 pixelFuzzyLv = 0; if( (MinEyeballBlackPixelRate >= 0) && (MinEyeballBlackPixelRate <= 0.4) ) pixelFuzzyLv = 0; else if( MinEyeballBlackPixelRate <= 0.50 ) pixelFuzzyLv = 2; else if( MinEyeballBlackPixelRate <= 0.60 ) pixelFuzzyLv = 6; else if( MinEyeballBlackPixelRate <= 1 ) pixelFuzzyLv = 8; // 判定眼睛中心1/2区域黑色像素点占总黑像素点的比例的模糊级别 betaFuzzyLv = 0; if( (MinEyeballBeta >= 0) && (MinEyeballBeta <= 0.3) ) betaFuzzyLv = 0; else if( MinEyeballBeta <= 0.45 ) betaFuzzyLv = 2; else if( MinEyeballBeta <= 0.6 ) betaFuzzyLv = 6; else if( MinEyeballBeta <= 1 ) betaFuzzyLv = 8; // 模糊评价函数 eyeState = 1; // 默认是闭眼的 funcResult = 2 * shapeFuzzyLv + 4 * pixelFuzzyLv + 4 * betaFuzzyLv; if( funcResult >= 58 ) eyeState = 0; return eyeState;}
    注意之处
    三个输入参数的阈值和模糊评价函数阈值都是自己设定的
    为了转硬件方便,加快运算速度,将浮点运算转为了整数运算,即将百分数扩大了十倍

    改进之处
    使用更客观的方法确定加权系数和等级分数
    可根据实际的图像,调整相应的参数与阈值

    12. recoFatigueState()函数程序功能在一次检测过程完成后,根据闭眼总次数、连续闭眼最大值、未检测到人脸的总次数、连续未检测到人脸的最大值这四个因素,判断是否处于疲劳驾驶状态!
    程序思想利用logistic方程分别构造四个因素对疲劳程度判断的函数方程,然后利用查表的方式计算出每个因素的贡献值,最后根据贡献值总和与阈值的比较得出结论。
    源码/*************************************************功能:特定功能函数——根据眼睛闭合状态和是否检测到人脸 判断驾驶状态:正常?疲劳?输入: int eyeCloseNum:检测过程中眼睛闭状态的总次数 int maxEyeCloseDuration:检测过程中眼睛连续闭合的最大次数 int failFaceNum:检测过程中未检测到人脸的总次数 int maxFailFaceDuration:检测过程中连续未检测到人脸的最大次数**************************************************/#include <stdio.h>int eyeCloseNumTab[] = {2,2,4,6,9,14,20,29,39,50,61,72,80,86,91,94,96,98,98,99,99,100,100,100,100,100,100,100,100,100, 100};int eyeCloseDurationTab[] = {2, 4, 9, 18, 32, 50, 68, 82, 91, 95, 98, 99, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100};int failFaceDurationTab[] = {2, 6, 14, 29, 50, 71, 86, 94, 98, 99, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100};int recoFatigueState(int thresh, int eyeCloseNum, int maxEyeCloseDuration, int failFaceNum, int maxFailFaceDuration){ int eyeCloseValue; // 眼睛闭合次数的贡献值 int eyeCloseDurationValue; // 眼睛连续闭合次数的贡献值 int failFaceValue; // 未检测到人脸的总次数的贡献值 int failFaceDurationValue; // 连续未检测到人脸的贡献值 int compreValue; // 综合贡献值 // 查表计算四个指标的贡献值 eyeCloseValue = eyeCloseNumTab[eyeCloseNum]; eyeCloseDurationValue = eyeCloseDurationTab[maxEyeCloseDuration]; failFaceValue = eyeCloseNumTab[failFaceNum]; failFaceDurationValue = failFaceDurationTab[maxFailFaceDuration]; // 综合贡献值 compreValue = eyeCloseValue + eyeCloseDurationValue + failFaceValue + failFaceDurationValue; printf("\neyeCloseValue: %d\n", eyeCloseValue); printf("eyeCloseDurationValue: %d\n", eyeCloseDurationValue); printf("failFaceValue: %d\n", failFaceValue); printf("failFaceDurationValue: %d\n", failFaceDurationValue); printf("compreValue: %d\n\n", compreValue); return (compreValue >= thresh) ? 1 : 0;}
    注意之处
    判断按是否处于疲劳驾驶状态的阈值 FATIGUETHRESHOLD 是自己设定的
    改进之处
    让每个因素的贡献值函数更加适合、精确
    根据实验确定更精确的阈值

    三、项目的限制
    基本只能使用于白天光线较好的时候,夜晚无法使用
    戴眼镜的情况无法使用
    低头情况下,人脸检测的效果很差

    四、项目改进方向
    调试参数:使用类似级联滤波器的调试方法,即逐级调试,使得每一级的输出效果都是最佳的!
    将所有阈值定义为常量
    变量太多,有些变量可重复使用的,但是为了方便阅读,定了更多变量,所以转硬件的时候可以最大程度的利用变量,较少变量数量。另外,功能类似的变量可以考虑用结构体整合到一起!
    低头时人脸检测的准确率很低
    人眼状态识别时,闭眼的情况识别不准确,很多时候将闭眼识别为睁开状态,可以考虑自己一个睁眼和闭眼的模板数列,然后比较人眼积分投影数列与模板数列的相似度。
    从二值化时候就分开左右眼进行处理能适应更多特殊情况,比如左右脸亮度相差太大的情况!
    可转化为函数的部分:

    消除眉毛的部分,放到getEyePos模块中
    判断人眼睁闭状态中计算以人眼中心为中心的大致眼眶的模块,放到getEyePos模块中
    计算最小眼睛的矩形区域中的确定最小眼睛区域eyeRectTemp的模块,放到getEyePos模块中
    统计lMinEyeballImg中的1/2区域内黑像素的比例的模块,放到recoEyeState模块中

    模糊综合评价的模型可已选择突出主要因素的模型,指标的分数和权重可考虑用更客观的方式确定。
    对投影曲线进行递推滤波(消除毛刺影响)

    对于很暗的情况先灰度均衡化,然后非线性运算,用查表方式
    在缩小至最小眼球之前用中值滤波或者形态学处理,消除独立小黑块的影响

    对疲劳状态的判断:用数据分析的方法对采集的多组数据不断的进行分析,看数据是否有明显的上升趋势,从而判断驾驶员是否处于疲劳驾驶状态。另外,还可以考虑才采用概率论的假设检验的方法判断是否处于疲劳驾驶状态
    特殊情况

    人眼区域的边界有大片黑块,造成人眼中心定位不准确,如何去除边界大块区域?

    左右脸光照不均匀的情况二值化效果严重不准确

    疲劳状态检测的特殊情况
    1.检测过程中判断全是闭眼和检测不到人脸的情况,没有睁开眼的情况,导致maxEyeCloseDuration = 0;

    2.眨眼与闭眼的频率很相近,即一次眨眼一次闭眼的情况,使得疲劳判断结果为正常!

    3.当判断为全1的时候,程序运行出现内存读取错误!


    分析:原因不明,但是肯定和lEyeballImg 和 EyeCloseDuration有关。重点查看EyeCloseDuration一直增加不跳出的时候,lEyeballImg处的程序如何运行。
    5 评论 30 下载 2018-11-24 17:12:53 下载需要15点积分
  • 基于SSH框架的电影订票系统网站的设计与实现

    1 总体描述1.1 产品前景目前国内市场上的电影购票网站很多,各个网站都有一定的用户量。用户还处于一种培养习惯的阶段。鉴于目前各个购票网站的质量参差不齐,许多网站都把大部分内容堆积在其首页,我们如果能够做一款用户体验极佳的购票网站,用户有好的体验过程,再加上我们网站特有的功能,用户就会慢慢习惯使用我们的网站,长远看来这有很大的商业机遇。
    在我们购票网站中,我们可以通过与影片制片方合作,在我们网页中推荐其即将上映的影片,只要我们的网站流量足够,这无疑会是一个很大的商机。
    1.2 产品功能
    记录销售交易与实时票务统计 支付交易(使用第三方交易网站进行交易)用户账号的安全性管理电影的介绍以及基本了解提供喜欢电影的推荐以及提醒功能针对不同使用场景下的自适应界面基于行业标准,与第三方库进行实时交易,包括电影票务、支付、院线、选座系统等
    1.3 用户类及其特征
    普通用户:以消遣为主,内容消费较少,对网站的使用体验比较在意,对网 站能够提供的附加功能比较敏感电影爱好者:对于电影有内容和题材偏好,有喜欢的导演和演员。在意网站 推荐的的电影,在意网站提供关于影片的评价专业影评人:很在意网站上的评论功能,提供好的评论交互方式会吸引这样 的用户
    1.4 运行环境支持PC端网页访问,移动端暂不支持
    1.5 开发环境和工具
    终端支持:PC
    开发语言框架:HTML5,CSS3,JavaScript
    服务器端支持
    语言:Java,Python
    Web框架:Struts MVC + Spring Boot,Hibernate
    关系数据库:MySQL
    负载均衡机制:Nginx
    开发平台与工具
    IDE:eclipse
    集成与测试:Travis
    源代码管理:Github
    项目管理与自动构建:maven

    1.6 开发规范
    WEB前端
    语言:Javascript,html,CSS
    代码风格:JS ES5代码风格;ES6代码风格;CSS代码风格;HTML/CSS代码风格
    自动化检测工具:ESLint
    WEB后端
    语言:Java
    代码风格 Google Java Style(科学上网),中文翻译
    自动化检测工具:Checkstyle(Ecplise 插件安装教程)
    爬虫脚本
    语言:Python 3.6+
    代码风格:Python风格规范

    1.7 设计和实现上的约束
    设计约束:改变现有购票网站较为杂乱的整体局面,剔除购票流程的冗 余过程,符合现代年轻人审美,尽量做到简洁、美观、大方
    可用性:系统需要提供较为完整的第三方接口,以供院线使用。功能完 备,贴合用户要求,能够提供较好的电影购票体验
    可靠性:使用行业标准,以便于部不同的第三方接口进行信息交换。有 一定的防差错功能,能保证高峰时期的购票正常
    可支持性:标准的接口,在进行信息交换的时候流畅无差错。

    1.8 假设和依赖本平台依赖于PC端运行环境,后台是使用了JAVA进行编写,使用了MySQL数据库管理系统对用户信息、电影及影院信息进行管理,前端页面显示使用了MVC框架。
    2 系统功能


    ID
    Name
    Imp
    Est
    How to demo
    Notes




    0
    搜索框
    7
    8
    可搜索影片、影院



    1
    电影 (按钮)
    8
    12
    点击主页面上方“电影”按钮,根据当前电影热度,票房,评分等列出现在上映的所有电影,每个电影的小项里包括名字,影片时长,类型,主演,评分以及选座购票按钮
    需要用户授权定位,也可点击定位,手动选择定位


    2
    影片详情
    9
    12
    页面包括名字,影片时长,上映日期,影片简介,影片评分,影片海报,可选影院部分(推荐)列表,导演编剧等各项信息及选座购票按钮
    影片评分我们提供豆瓣,烂番茄,时光网等不同影评网站的评分,使用户参考更多样


    3
    选座购票
    10
    9
    从影片列表或者影片详情页都可点击选座购票
    会有影院列表,点击进去就会看到场次票价,用户可根据需求选择场次,座次


    4
    即将上映
    5
    6
    在主页面中,给出近期即将上映的所有电影
    点击每个分项会进入影片详情页


    5
    登录
    3
    11
    点击主页面“登录”,进入登录界面,输入用户名密码即登陆成功
    在登录界面包含注册按钮,账户可与社交账号绑定或绑定邮箱,要与手机绑定(用于短信提醒)


    6
    我的主页
    6
    8
    登陆成功后,点击主页面“我的”,进入我的主页
    含有电影票订单、优惠方式、收藏三大类,其中优惠方式包含折扣卡、红包、现金券等;收藏分为电影收藏、影院收藏、活动收藏



    3 数据库实体关系图

    实体定义

    user 表:用户表,记录用户的信息,用户名,密码的 MD5,电话,邮箱movie 表:记录电影的信息,包括中文名,英文名,电影类型,电影时长,上映日期,电影简介,电影海报的 URL,参演人员名单 person 表:记录电影人的信息,通过 type 列区别是导演还是演员,包括名字,照片 URL,type 电影人的类型(导演/演员) cinema 表:订单编号,电影 id、影院 id、场次 id、座位 id screen 表:荧屏 id,语言,价格,房间 id,时间,影院 id,电影名字,座位 id admin 表:id,名字,密码,email,电话号码movie 表和 person 表是一对多的关联映射关系
    四、总体设计4.1 概念术语描述(后端)4.1.1 Java
    java是纯面向对象编程的语言平台无关性 (一次编译,到处运行;Write Once,Run Anywhere)java提供了许多内置的类库,通过这些类库,简化了开发人员的设计工作,同时缩短了项目开发时间提供了对Web应用开发的支持,例如,Applet,Servlet,和JSP可以用来开发Web应用程序,,Socket,RMI可以用来开发分布式应用程序的类库去除了c++中难以理解,容易混淆的特性(如c++中的多继承,头文件,指针,结构,单元,运算符重载,虚拟基础类,使得程序更加严谨,整洁具有较好的安全性和健壮性。java语言经常会被用在网络环境中,为了增强程序的安全性
    4.1.2 SpringSpring Framework(简称Spring)是根据Rod Johnson著名的《Expert One-on-One J2EE Design and Development》而开发的J2EE应用程序框架。目前主要根据Rod Johnson和Juergen Hoeller而进行开发的,目前发布的最新版为1.1.4。 Spring是J2EE应用程序框架,不过,更严格地讲它是针对Bean的生命周期进行管理的轻量级容器(Lightweight container),可以单独利用Spring构筑应用程序,也可以和Struts,Webwork,Tapestry等众多Web应用程序框架组合使用,并且可以与Swing等桌面应用程序API组合。所以Spring并不仅仅只能应用在J2EE中,也可以应用在桌面应用及小应用程序中。针对Spring开发的组件不需要任何外部库。
    优点:

    Spring能有效地组织你的中间层对象Spring能消除在许多工程中常见的对Singleton的过多使用Spring能消除各种各样自定义格式的属性文件的需要,使配置信息一元化Spring能够帮助我们真正意义上实现针对接口编程在Spring应用中的大多数业务对象没有依赖于Spring使用Spring构建的应用程序易于单元测试Spring支持JDBC和O/R Mapping产品(Hibernate)MVC Web框架,提供一种清晰,无侵略性的MVC实现方式JNDI抽象层,便于改变实现细节,可以方便地在远程服务和本地服务间切换简化访问数据库时的例外处理Spring能使用AOP提供声明性事务管理,可以不直接操作JTA也能够对事务进行管理提供了JavaMail或其他邮件系统的支持
    4.2 概念术语描述(前端)5.2.1 Vue.jsVue.js(读音 /vjuː/, 类似于 view)是一个构建数据驱动的 web 界面的库。Vue.js 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件。
    优点:

    响应式编程:mvvm框架,实现数据的双向绑定组件化:一切都是组件,组件可以套其他组件,增强了可复用性模块化:我们用一个模块打包工具来配合 Vue.js,比如Webpack或者Browserify,然后再加上 ES2015。每一个 Vue 组件都可以看做一个独立的模块动画:Vue 自带简洁易用的过渡动画系统。有很多获奖的互动类网站是用 Vue 开发的。Vue 的反应式系统也使得它可以用来开发高效的数据驱动的逐帧动画路由:Vue 本身是不带路由功能的。但是,有vue-router这个可选的库来配合。vue-router 可以将嵌套的路径映射到嵌套的组件,并且提供了细致的路径跳转控制文档和配套设施:文档和配套设施完善,社区活跃,生态系统完备,易于上手
    4.2.2 ES6(ECMAScript 6)新一代的javascript也被称为ECMAScript 6(也称为 ES6 or Harmony)。
    优点:

    糖语法:首先,语法糖是一种语法,使得语言更容易理解和更具有可读性,它使语言相对我们来说变得更”甜”。这也意味着ES6的一些”新”的特点并不是真的新,只是试图简化语法而已,让我们编程更容易。这样就无需使用老式的取巧的方法编写你的代码,而是可以一种更简单的方式来编写代码,那就是使用糖语法模块Module:如果你想将所有js放在一个文件中,或者你要在应用的不同地方使用相同的功能,你就要使用模块,关键词是exportlet和const:在一段代码块中用let或者const声明的变量会限制它们只在这个块中可见。这叫做块级作用域
    4.3 基本设计描述4.3.1 系统总体逻辑结构图
    4.3.2 系统部署结构图
    4.4 主要界面流程描述
    4.5 模块列表4.5.1 模块划分


    后端
    前端









    4.5.2 前端结构
    4.6 Web服务器4.6.1 返回的状态码


    类型
    stateCode
    info




    成功
    200
    NULL


    错误
    500
    错误信息



    4.6.2 用户登录/注册


    路由
    方法
    说明
    提交格式
    测试




    /api/login
    POST
    提交用户登录表单 username, password, 允许邮箱/手机/用户名登录

    OK


    /api/signup
    POST
    提交用户注册表单 username, password, email, phone

    OK


    /api/logout
    PUT
    登出

    OK


    /api/user
    GET
    获取当前用户信息

    OK


    /api/user
    PUT
    修改当前用户信息,填写需要修改的项,username,email,phone,oldPassword,newPassword

    OK


    /api/user/order
    GET
    查看该用户的所有订单
    OrderModel
    OK


    /api/user/screen/{id}
    PUT
    锁定/购买座位,需要上传 需要用户登录后
    eat={88长字符串, 锁定的位置用1表示,购买位置用2表示,其他用0填充}



    // 购票例子:// 表单格式:seat=1100000000000000000000000000000000000000000000000000000000000000000000000000000000000000// 表示锁定第1,2个位置seat=0022000000000000000000000000000000000000000000000000000000000000000000000000000000000000// 表示购买第1,2个位置// 购买前需要先锁定public class OrderModel { private List<FilmOrder> filmOrderModelList;}public class FilmOrder { private Integer id; private User user; private Integer screenId; private String seat;}
    4.6.3 管理员账号


    路由
    方法
    说明
    提交格式




    /api/admin/login
    POST
    adminname, password 登录



    /api/admin/logout
    PUT
    登出



    /api/admin/newMovie
    POST
    需要填写的域chineseName,englishName,pictureUrl,type,length,releaseDate,introduction



    /api/admin/{id}
    DELETE
    当初对应id的电影



    /api/admin/{id}
    PUT
    更新电影信息,只需要填写需要更新的域,和创建电影的域名字相同



    /api/admin/cinema/{id}
    DELETE
    参数对应id的影院



    /api/admin/cinema/create
    POST
    创建一个影院
    CinemaModel


    /api/admin/cinema/{id}
    PUT
    修改一个影院信息
    CinemaModel



    CinemaModel { private String name; private String address; private String phone; private List<Screen> screens;}
    4.6.4 获取电影信息


    路由
    方法
    说明
    返回值
    测试




    /api/movie/name/{查询电影名}
    GET
    返回电影名对应信息,允许查询中英文电影名,返回一条记录或空
    SimpMovie
    OK


    /api/movie/type/{type}?id=ID
    GET
    返回电影类型列表, 数目为从id开始往后20条,默认id = 0
    List
    OK


    /api/movie/date/day/20170501
    GET
    返回2017-05-01上映的电影列表,如果输入非法日期,返回当天上映列表
    List
    OK


    /api/movie/date/month/201705
    GET
    返回2017-05上映的电影列表,如果输入非法日期,返回当月上映列表
    List
    OK


    /api/movie/date/year/2017
    GET
    返回2017上映的电影列表,如果输入非法日期,返回当年上映列表
    List
    OK


    /api/movie/{id}
    GET
    返回ID=id的电影详细信息
    Movie
    OK


    /api/movie/showing/{number}
    GET
    返回最近一个月上映的电影列表,number条
    List
    OK


    /api/movie/query/count?type={}&area={}&year={}
    GET
    year=2007, 允许type,area,year字段为”all”
    Integer
    OK


    /api/movie/query?type={}&area={}&year={}&page={}&step={}
    GET
    返回 [pagestap, pagestep+step]的数据,允许type,area,year字段为”all”
    List
    OK



    SimpMovie { private String name; private Integer id; private String url;}
    4.6.5 获取演员/导演信息


    路由
    方法
    说明
    返回值
    测试




    /api/person/{id}
    GET
    通过演员/导演的ID获取
    Person
    OK


    /api/person/movie/{id}
    GET
    获取电影ID的演员/导演名单
    List
    OK



    Person { private Integer id; // 名字 private String name; // 照片的URL private String url; // 表示是导演还是演员 private String type; // "actor", "director"}
    4.6.6 获取影院信息


    路由
    方法
    说明
    接受内容
    返回值
    测试




    /api/cinema?number={}&address={}
    GET
    number选填默认10,address必填

    List



    /api/cinema/{id}
    GET
    返回影院详细信息

    Cinema
    OK


    /api/cinema/showing?id={id}
    GET
    返回正在该影院上映的电影简要信息列表

    List
    OK



    SimpCinema { private Integer id; private String name;}Cinema { private Integer id; private String name; private String address; private String phone; private List<Screen> screens;}
    4.6.7 获取排片信息


    路由
    方法
    说明
    接受内容
    返回值
    测试




    /api/screen?cinemaid={}&movieid={}&date={}&time={}
    GET
    获取对应影院对应电影的排片情况列表

    List
    OK


    /api/screen/{id}
    GET
    获取对应id的排片情况

    Screen
    OK



    Seat { private List<Integer> vacancy; private List<Integer> soldOut; private List<Integer> locking;}Screen { private Integer id; private Date time; private String language; private String room; private Double price; private Cinema cinema; private String movieName; private String seats; // '0'->空位,'1'->被锁定,'2'->已售出 8x11 列优先, 比如2行1列下标为8}
    4.6.8 搜索功能


    路由
    方法
    说明
    返回值
    测试




    /api/search?query={}






    五、软件设计技术5.1 前后端分离5.1.1 理解MVCMVC是一种经典的设计模式,全名为Model-View-Controller,即模型-视图-控制器。
    其中,模型是用于封装数据的载体,例如,在Java中一般通过一个简单的POJO(Plain Ordinary Java Object)来表示,其本质是一个普通的java Bean,包含一系列的成员变量及其getter/setter方法。对于视图而言,它更加偏重于展现,也就是说,视图决定了界面到底长什么样子,在Java中可通过JSP来充当视图,或者通过纯HTML的方式进行展现,而后者才是目前的主流。模型和视图需要通过控制器来进行粘合,例如,用户发送一个HTTP请求,此时该请求首先会进入控制器,然后控制器去获取数据并将其封装为模型,最后将模型传递到视图中进行展现。
    综上所述,MVC的交互过程如图1所示。

    5.1.2 MVC模式的优点与不足MVC模式早在上个世纪70年代就诞生了,直到今天它依然存在,可见生命力相当之强。MVC模式最早用于Smalltalk语言中,最后在其它许多开发语言中都得到了很好的应用,例如,Java中的Struts、spring MVC等框架。正是因为这些MVC框架的出现,才让MVC模式真正落地,让开发更加高效,让代码耦合度尽量减小,让应用程序各部分的职责更加清晰。
    既然MVC模式这么好,难道它就没有不足的地方吗?我认为MVC至少有以下三点不足:

    每次请求必须经过“控制器->模型->视图”这个流程,用户才能看到最终的展现的界面,这个过程似乎有些复杂。
    实际上视图是依赖于模型的,换句话说,如果没有模型,视图也无法呈现出最终的效果。
    渲染视图的过程是在服务端来完成的,最终呈现给浏览器的是带有模型的视图页面,性能无法得到很好的优化。

    为了使数据展现过程更加直接,并且提供更好的用户体验,我们有必要对MVC模式进行改进。不妨这样来尝试,首先从浏览器发送AJAX请求,然后服务端接受该请求并返回JSON数据返回给浏览器,最后在浏览器中进行界面渲染。
    改进后的MVC模式如图2所示。

    也就是说,我们输入的是AJAX请求,输出的是JSON数据,市面上有这样的技术来实现这个功能吗?答案是REST。
    REST全称是Representational State Transfer(表述性状态转移),它是RoyFielding博士在2000年写的一篇关于软件架构风格的论文,此文一出,威震四方!国内外许多知名互联网公司纷纷开始采用这种轻量级的Web服务,大家习惯将其称为RESTful Web Services,或简称REST服务。]
    如果将浏览器这一端视为前端,而服务器那一端视为后端的话,可以将以上改进后的MVC模式简化为以下前后端分离模式,如图3所示。

    可见,有了REST服务,前端关注界面展现,后端关注业务逻辑,分工明确,职责清晰。那么,如何使用REST服务将应用程序进行前后端分离呢?我们接下来继续探讨,首先我们需要认识REST。
    5.1.3 认识RESTREST本质上是使用URL来访问资源种方式。众所周知,URL就是我们平常使用的请求地址了,其中包括两部分:请求方式与请求路径,比较常见的请求方式是GET与POST,但在REST中又提出了几种其它类型的请求方式,汇总起来有六种:GET、POST、PUT、DELETE、HEAD、OPTIONS。尤其是前四种,正好与CRUD(Create-Retrieve-Update-Delete,增删改查)四种操作相对应,例如,GET(查)、POST(增)、PUT(改)、DELETE(删),这正是REST与CRUD的异曲同工之妙!需要强调的是,REST是“面向资源”的,这里提到的资源,实际上就是我们常说的领域对象,在系统设计过程中,我们经常通过领域对象来进行数据建模。
    REST是一个“无状态”的架构模式,因为在任何时候都可以由客户端发出请求到服务端,最终返回自己想要的数据,当前请求不会受到上次请求的影响。也就是说,服务端将内部资源发布REST服务,客户端通过URL来访问这些资源,这不就是SOA所提倡的“面向服务”的思想吗?所以,REST也被人们看做是一种“轻量级”的SOA实现技术,因此在企业级应用与互联网应用中都得到了广泛应用。
    下面我们举几个例子对REST请求进行简单描述:可以查看API来更好地理解。
    可见,请求路径相同,但请求方式不同,所代表的业务操作也不同,例如,/advertiser/1这个请求,带有GET、PUT、DELETE三种不同的请求方式,对应三种不同的业务操作。
    虽然REST看起来还是很简单的,实际上我们往往需要提供一个REST框架,让其实现前后端分离架构,让开发人员将精力集中在业务上,而并非那些具体的技术细节。下面我们将使用Java技术来实现这个REST框架,整体框架会基于Spring进行开发。
    5.2 Vue渐进式框架5.2.1 为什么要有框架框架的存在是为了帮助我们应对复杂度
    前端框架特别多,那么为什么要有框架呢?框架的存在是为了帮助我们应对复杂度。当我们需要解决一些前端上工程问题的时候,这些问题会有不同的复杂度。如果你用太简陋的工具应对非常复杂的需求,就会极大地影响你的生产力。所以,框架本身是帮我们把一些重复的并且已经受过验证的模式,抽象到一个已经帮你设计好的API封装当中,帮助我们去应对这些复杂的问题。
    框架自身也有复杂度
    框架本身也会带来复杂度。相信大家在调研各种框架或学习各种框架时,会遇到学习曲线问题——有些框架会让人一时不知如何上手。
    工具复杂度是为了处理内在复杂度所做的投资
    工具的复杂度是可以理解为是我们为了处理问题内在复杂度所做的投资。为什么叫投资?那是因为如果投的太少,就起不到规模的效应,不会有合理的回报。这就像创业公司拿风投,投多少是很重要的问题。如果要解决的问题本身是非常复杂的,那么你用一个过于简陋的工具应付它,就会遇到工具太弱而使得生产力受影响的问题。
    反之,是如果所要解决的问题并不复杂,但你却用了很复杂的框架,那么就相当于杀鸡用牛刀,会遇到工具复杂度所带来的副作用,不仅会失去工具本身所带来优势,还会增加各种问题,例如培训成本、上手成本,以及实际开发效率等。
    Pick the right tool for the job
    “Pick theright tool for the job”——在国外,跟开发者讨论一些框架选型问题时,大家都会说这句话——一切都要看场景。因为,前端开发原生开发或者桌面开发模式相比,有自己的独特之处,它跟其实并不那么固定。在Web上面,应用可以有非常多的形态,不同形态的Web应用可能有完全不同程度的复杂度。这也是为什么要谈工具复杂度和所要做的应用复杂度的问题。
    怎么看前端框架的复杂度
    目前的前端开发已经越来越工程化,而我们需要解决的实际问题也是不同的。我们就下图进行分析。

    我们可能在任何情况下都需要声明式的渲染功能 ,并希望尽可能避免手动操作,或者说是可变的命令式操 ,希望尽可能地让DOM的更新操作是自动的,状态变化的时候它就应该自动更新到正确的状态;我们需要组件系统,将一个大型的界面切分成一个一个更小的可控单元; 客户端路由 ——这是针对单页应用而言,不做就不需要,如果需要做单页应用,那么就需要有一个URL对应到一个应用的状态,就需要有路由解决方案; 大规模的状态管理 ——当应用简单的时候,可能一个很基础的状态和界面映射可以解决问题,但是当应用变得很大,涉及多人协作的时候,就会涉及多个组件之间的共享、多个组件需要去改动同一份状态,以及如何使得这样大规模应用依然能够高效运行,这就涉及大规模状态管理的问题,当然也涉及到可维护性,还有构建工具。现在,如果放眼前端的未来,当HTTP2普及后,可能会带来构建工具的一次革命。但就目前而言,尤其是在中国的网络环境下,打包和工程构建依然是非常重要且不可避免的一个环节。
    5.2.2 渐进式框架Vue.jsVue.js现状
    以下数据可以体现出Vue.js的现状。

    前一段时间突破了三万星(如下图所示),总下载量过百万。

    官网上每个月的用户量为26万,这个应该是不包含中国区数据。官方开发者插件的周活跃用户数在5万5左右。这个数据是我觉得最有说服力的数据。安装并且使用开发者插件的Vue用户,应该会在实际生产中真正频繁使用Vue。
    Google搜索趋势的相关数据如下图所示。图中,绿色的是Backbone的数据,黄色是Ember,红色是React,蓝色是Vue。可以看出React和Vue近两年发展势头都比较迅猛。可以看出,Vue的曲线开始的是很早,2013年已经开始,但是有很长一段时间的增长是比较低的。因为在那一段时间我还在谷歌工作,Vue基本上是作为个人项目在运营。在过去一两年中,Vue获得了非常大的突破性发展。这个图里没有Angular,因为Angular的量还是非常大的,如果放进去就破表了。

    这些数据并不能绝对地代表框架当前的热度,但有一定的参考价值。可以看到React的势头很足。而由Vue的曲线还可以看出它的增长速度还在不停上扬。
    Vue的定位
    它与其他框架的区别就是渐进式的想法,也就是“Progressive”——这个词在英文中定义是渐进,一步一步,不是说你必须一竿子把所有的东西都用上。
    Vue的设计
    接下来我们回到之前看的图:

    Vue从设计角度来讲,虽然能够涵盖这张图上所有的东西,但是并不需要一上手就把所有东西全用上 ,因为没有必要。无论从学习角度,还是实际情况,这都是可选的。声明式渲染和组建系统是Vue的核心库所包含内容,而客户端路由、状态管理、构建工具都有专门解决方案。这些解决方案相互独立,你可以在核心的基础上任意选用其他的部件,不一定要全部整合在一起。
    6 系统演示电影订票网站首页

    会员注册

    会员登录

    查找电影
    4 评论 65 下载 2018-10-05 23:12:23 下载需要12点积分
  • 基于QT的考试管理系统设计与实现

    一、项目概要1.1 项目名称
    考试管理系统
    1.2 项目目标
    培养快速学习新的知识,解决问题的能力规划项目的整体功能以及相关需求分析,并设计出合理的数据库,并熟悉整个试题系统的开发流程。
    1.3 软件概要开发一个考试管理系统,考生可以进行练习,和在线考试,管理员负责管理题库以及生成试卷,登陆主界面如图1.3所示。

    1.4 功能描述
    涉及到两个模块:学生登录和管理员登陆。
    基于学生的功能有:
    练习试题(此试题为题库中所有试题类型的所有题,考生可以任意答题且参考标准答案)
    在线考试(试卷从后台试卷库里面随机挑选,考生必须在指定时间内答完试题,交卷后显示考生成绩以及所用时间等信息)
    基于管理员的功能有:
    试题管理(管理题库中所有题,可以进行增删改查,支持关键字、难度等级查询)
    试卷管理(按照要求从题库随机挑选试题生成试卷,对已生成的试卷进行增删改查,支持成批生成试卷,成批删除试卷,根据试卷名,试卷内容,难度等级查询试卷,以及查看试卷内容)

    1.5 开发环境
    操作系统:Microsoft Windows 10开发环境:Qt Creator 8.1数据库:MySql Server 5.5
    1.6 关键技术
    面向对象设计与分析C++Qt数据库编程Qt信号槽
    二、软件详细需求2.1 学生功能主界面学生登录后进入如图2.1所示的界面,进行考试考试或者练习。

    2.2 管理员功能主界面管理员登陆后进入如图2-2所示界面,管理员可以对题库里的题增删改查,也可为学生在线考试随机组卷。

    2.3 学生在线考试系统实现学生进入考试系统,从已生成的试卷随机抽取答题,进入如图2-3-1所示界面,要求学生在规定的时间内答完试卷,可随机跳转试题,且将已做过或即时更新的的答案保存到数组。点击交卷或者退出考试将显示如图2-3-2所示界面,显示考试用时和考试成绩等信息。


    2.4 学生练习系统实现该考试练习从题库按照各种类型题抽取,考生可以切换题型,答完题也可查看正确答案。

    2.5 试题管理系统实现该试题管理系统将对题库类型题分类管理,每个类型题对应一个增删改查界面,如图2-5-2所示。



    题型
    添加题目
    修改题目
    删除题目
    查询题目




    选择题
    题目id自动增加,填写相关题干,abcd选项内容,答案,设置分数,等级难度等信息,点提交即可写入数据库
    页面显示题库该类型题所有信息,点击修改
    页面显示题目该类型题所有信息,选中行点击删除弹出是否删除页面,若确定则删除该题,可刷新页面
    支持难度和关键字以与关系的四种查询方式


    判断题
    题目id自动增加,填写内容题干,答案,设置分数,等级难度等信息,点击提交即可
    页面显示题库该类型题所有信息,点击修改
    页面显示题目该类型题所有信息,选中行点击删除弹出是否删除页面,若确定则删除该题,可刷新页面
    支持难度和关键字以与关系的四种查询方式


    填空题
    题目id自动增加,填写内容题干,输入空格数量,在下面随机生成,填写入对应空格即可设置分数,等级难度等信息,点击提交即可
    页面显示题库该类型题所有信息,点击修改,其中空格数量不可修改
    页面显示题目该类型题所有信息,选中行点击删除弹出是否删除页面,若确定则删除该题,可刷新页面
    支持难度和关键字以与关系的四种查询方式







    2.6 组卷系统主界面
    2.7 试卷生成实现可按照要求从题库随机抽选题型组成填写的试卷数量,要求所选题型与对应个数成绩相加等于总分,且题库里有该填写内容的要求的试题,否则弹出相关不满足要求的题型表,如图2-7-2所示,若不填知识点描述默认为综合,不选择难度等级则是随机。


    2.8 查看已生成试卷信息


    试卷查询
    支持试卷名,难度等级,知识点查询的三种方式随机组合查询




    现有试卷数
    始终随着查询,删除的更新变化,显示当前试卷数


    删除试卷
    选中行点击删除弹出是否删除试卷,若确定则删除该试卷,可刷新页面,支持成批删除(选中多行删除)


    查看试卷
    对选中试卷查看具体信息,显示试卷名,以及改试卷所有题型(使用QScrollArea控件显示)





    三、系统整体设计3.1 系统结构图
    3.2 模块要求
    功能界面层
    Qt主界面
    学生功能界面
    管理员功能界面
    DataBase MySql
    提供给上层的数据库访问,完成指定试卷试题学生管理员等数据信息的取得
    各种类型题已保存在数据库中
    对数据的添加、修改、删除,查询提供指定数据表

    四、登陆测试



    Accou:wrong Answ:wrong
    Accou:wrong Answer:right
    Account:right Answ:wrong
    Account:right Answer:right





    请选择身份
    请选择身份
    请选择身份
    请选择身份


    学生
    用户名或密码错误
    用户名或密码错误
    用户名或密码错误
    进入学生功能界面


    管理员
    用户名或密码错误
    用户名或密码错误
    用户名或密码错误
    进入管理员功能界面





    五、数据库设计(试卷管理系统)
    6 评论 51 下载 2018-10-05 22:46:51 下载需要15点积分
  • 基于springboot的自动化办公系统(企业人事管理系统)

    面向组织的日常运作和管理,员工及管理者使用频率最高的应用系统,极大提高公司的办公效率。oasys是一个OA办公自动化系统,使用Maven进行项目管理,基于springboot框架开发的项目,mysql底层数据库,前端采用freemarker模板引擎,Bootstrap作为前端UI框架,集成了jpa、mybatis等框架。
    1.项目介绍oasys是一个OA办公自动化系统,使用Maven进行项目管理,基于springboot框架开发的项目,mysql底层数据库,前端采用freemarker模板引擎,Bootstrap作为前端UI框架,集成了jpa、mybatis等框架。作为初学springboot的同学是一个很不错的项目,如果想在此基础上面进行OA的增强,也是一个不错的方案。
    2.框架介绍项目结构

    前端



    技术
    名称
    版本
    官网




    freemarker
    模板引擎springboot
    1.5.6.RELEASE集成版本
    https://freemarker.apache.org/


    Bootstrap
    前端UI框架
    3.3.7
    http://www.bootcss.com/


    Jquery
    快速的JavaScript框架
    1.11.3
    https://jquery.com/


    kindeditor
    HTML可视化编辑器
    4.1.10
    http://kindeditor.net


    My97 DatePicker
    时间选择器
    4.8 Beta4
    http://www.my97.net/




    后端



    技术
    名称
    版本
    官网




    SpringBoot
    SpringBoot框架
    1.5.6.RELEASE
    https://spring.io/projects/spring-boot


    JPA
    spring-data-jpa
    1.5.6.RELEASE
    https://projects.spring.io/spring-data-jpa


    Mybatis
    Mybatis框架
    1.3.0
    http://www.mybatis.org/mybatis-3


    fastjson
    json解析包
    1.2.36
    https://github.com/alibaba/fastjson


    pagehelper
    Mybatis分页插件
    1.0.0
    https://pagehelper.github.io



    3.部署流程
    把oasys.sql导入本地数据库
    修改application.properties,
    修改数据源,oasys——>自己本地的库名,用户名和密码修改成自己的
    修改相关路径,配置图片路径、文件路径、附件路径
    OasysApplication.java中的main方法运行,控制台没有报错信息,数据启动时间多久即运行成功
    在浏览器中输入localhost:8088/logins

    账号:test 密码:test账号:soli 密码:123456

    4.项目截图




    0 评论 3 下载 2019-04-03 19:31:30 下载需要20点积分
  • 《WINDOWS黑客编程技术详解》配套资源下载

    《WINDOWS黑客编程技术详解》是一本面向黑客编程初学者的书,较为全面的地总结黑客编程技术。其内容重在实践,着重剖析技术实现原理,向读者讲解黑客编程技术的实现方法。
    本书介绍的是些黑客编程的基础技术,涉及用户层下的Windows编程和内核层下的Rootkit编程。全书分为用户篇和内核篇两部分,用户篇包括11章,配套49个示例程序源码;内核篇包括7章,配套28个示例程序源码。本书每个技术都有详细的实现原理分析,以及对应的示例代码(配套代码均支持32位和64位Windows 7、Windows 8.1及Windows 10系统),帮助初学者建立起黑客编程技术的基础技能。
    本书面向对计算机系统安全开发感兴趣,或者希望提升安全开发水平的读者,以及恶意代码分析研究方面的安全人员。
    购书方式
    淘宝、天猫、京东等各大电商网站均有纸质书和电子书销售,请搜索 “WINDOWS黑客编程技术详解”。
    当当:http://product.dangdang.com/25859838.html
    京东:https://item.jd.com/12464379.html
    天猫:https://detail.tmall.com/item.htm?spm=a230r.1.14.76.cb1940a5YFnLgL&id=582626540408&ns=1&abbucket=18
    书籍封面(正面)

    书籍封面(反面)

    目录

    第1篇 用户篇

    第1章 开发环境

    1.1 环境安装1.2 工程项目设置1.3 关于Debug模式和Release模式的小提示
    第2章 基础技术

    2.1 运行单一实例2.2 DLL延时加载2.3 资源释放
    第3章 注入技术

    3.1 全局钩子注入3.2 远线程注入3.3 突破SESSION 0隔离的远线程注入3.4 APC注入
    第4章 启动技术

    4.1 创建进程API4.2 突破SESSION 0隔离创建用户进程4.3内存直接加载运行
    第5章 自启动技术

    5.1 注册表5.2 快速启动目录5.3 计划任务5.4 系统服务
    第6章 提权技术

    6.1 进程访问令牌权限提升6.2 Bypass UAC
    第7章 隐藏技术

    7.1 进程伪装7.2傀儡进程7.3 进程隐藏7.4 DLL劫持
    第8章 压缩技术

    8.1 数据压缩API8.2 ZLIB压缩库
    第9章 加密技术

    9.1 Windows自带的加密库9.2 Crypto++密码库
    第10章 传输技术

    10.1 Socket通信10.2 FTP通信10.3 HTTP通信10.4 HTTPS通信
    第11章 功能技术

    11.1 进程遍历11.2 文件遍历11.3 桌面截屏11.4 按键记录11.5 远程CMD11.6 U盘监控11.7 文件监控11.8 自删除

    第2篇 内核篇

    第12章 开发环境

    12.1 环境安装12.2 驱动程序开发与调试12.3 驱动无源码调试12.4 32位和64位驱动开发
    第13章 文件管理技术

    13.1 文件管理之内核API13.2 文件管理之IRP13.3 文件管理之NTFS解析
    第14章 注册表管理技术

    14.1 注册表管理之内核API14.2 注册表管理之HIVE文件解析
    第15章 HOOK技术

    15.1 SSDT Hook15.2过滤驱动
    第16章 监控技术

    16.1 进程创建监控16.2 模块加载监控16.3 注册表监控16.4 对象监控16.5 Minifilter文件监控16.6 WFP网络监控
    第17章 反监控技术

    17.1 反进程创建监控17.2 反线程创建监控17.3 反模块加载监控17.4 反注册表监控17.5 反对象监控17.6 反Minifilter文件监控
    第18章 功能技术

    18.1 过PatchGuard的驱动隐藏18.2 过PatchGuard的进程隐藏18.3 TDI网络通信18.4 强制结束进程18.5 文件保护18.6 文件强删

    附录 函数一览表

    PS:源码下载可以直接点击下述附件下载,也可以到 github 和人民邮电出版社-异步社区上面下载:
    https://github.com/DemonGan/Windows-Hack-Programming
    https://www.epubit.com/book/detail/39391
    PS:若对书中内容有疑惑或者发现错误,可以直接戳下面的勘误收集链接哦
    https://www.write-bug.com/article/1966.html
    19 评论 306 下载 2018-11-26 11:48:09
  • 基于mtcnn与facenet实现人脸登录系统

    一、介绍本文主要介绍了系统涉及的人脸检测与识别的详细方法,该系统基于 python2.7.10/opencv2/tensorflow1.7.0 环境,实现了从摄像头读取视频,检测人脸,识别人脸的功能。
    人脸识别是计算机视觉研究领域的一个热点。目前,在实验室环境下,许多人脸识别已经赶上(超过)人工识别精度(准确率:0.9427~0.9920),比如face++,DeepID3,FaceNet等(详情可以参考:基于深度学习的人脸识别技术综述)。但是,由于光线,角度,表情,年龄等多种因素,导致人脸识别技术无法在现实生活中广泛应用。本文基于python/opencv/tensorflow环境,采用FaceNet(LFW:0.9963 )为基础来构建实时人脸检测与识别系统,探索人脸识别系统在现实应用中的难点。下文主要内容如下 :

    利用htm5 video标签打开摄像头采集头像并使用jquery.faceDeaction组件来粗略检测人脸
    将人脸图像上传到服务器,采用mtcnn检测人脸
    利用opencv的仿射变换对人脸进行对齐,保存对齐后的人脸
    采用预训练的facenet对检测的人脸进行embedding,embedding成512维度的特征
    对人脸embedding特征创建高效的annoy索引进行人脸检测

    二、人脸采集采用html5 video标签可以很方便的实现从摄像头读取视频帧,下文代码实现了从摄像头读取视频帧,faceDection识别人脸后截取图像上传到服务器功能。
    在html文件中添加video,canvas标签
    <div class="booth"> <video id="video" width="400" height="300" muted class="abs" ></video> <canvas id="canvas" width="400" height="300"></canvas> </div>
    打开网络摄像头
    var video = document.getElementById('video'),var vendorUrl = window.URL || window.webkitURL;//媒体对象navigator.getMedia = navigator.getUserMedia || navagator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;navigator.getMedia({video: true, //使用摄像头对象audio: false //不适用音频}, function(strem){ video.src = vendorUrl.createObjectURL(strem); video.play();});
    利用jquery的facetDection组件检测人脸
    $('#canvas').faceDetection()检测出人连脸的话截图,并把图片转换为base64的格式,方便上传
    context.drawImage(video, 0, 0, video.width, video.height);var base64 = canvas.toDataURL('images/png');
    将base64格式的图片上传到服务器
    //上传人脸图片function upload(base64) { $.ajax({ "type":"POST", "url":"/upload.php", "data":{'img':base64}, 'dataType':'json', beforeSend:function(){}, success:function(result){ console.log(result) img_path = result.data.file_path } });}
    图片服务器接受代码,php语言实现
    function base64_image_content($base64_image_content,$path){ //匹配出图片的格式 if (preg_match('/^(data:\s*image\/(\w+);base64,)/', $base64_image_content, $result)){ $type = $result[2]; $new_file = $path."/"; if(!file_exists($new_file)){ //检查是否有该文件夹,如果没有就创建,并给予最高权限 mkdir($new_file, 0700,true); } $new_file = $new_file.time().".{$type}"; if (file_put_contents($new_file, base64_decode(str_replace($result[1], '', $base64_image_content)))){ return $new_file; }else{ return false; } }else{ return false; }}
    三、人脸检测人脸检测方法有许多,比如opencv自带的人脸Haar特征分类器和dlib人脸检测方法等。对于opencv的人脸检测方法,有点是简单,快速;存在的问题是人脸检测效果不好。正面/垂直/光线较好的人脸,该方法可以检测出来,而侧面/歪斜/光线不好的人脸,无法检测。因此,该方法不适合现场应用。对于dlib人脸检测方法 ,效果好于opencv的方法,但是检测力度也难以达到现场应用标准。
    本文中,我们采用了基于深度学习方法的mtcnn人脸检测系统(mtcnn:Joint Face Detection and Alignment using Multi-task Cascaded Convolutional Neural Networks)。mtcnn人脸检测方法对自然环境中光线,角度和人脸表情变化更具有鲁棒性,人脸检测效果更好;同时,内存消耗不大,可以实现实时人脸检测。本文中采用mtcnn是基于python和tensorflow的实现(代码来自于davidsandberg,caffe实现代码参见:kpzhang93)
    model= os.path.abspath(face_comm.get_conf('mtcnn','model'))class Detect: def __init__(self): self.detector = MtcnnDetector(model_folder=model, ctx=mx.cpu(0), num_worker=4, accurate_landmark=False) def detect_face(self,image): img = cv2.imread(image) results =self.detector.detect_face(img) boxes=[] key_points = [] if results is not None: #box框 boxes=results[0] #人脸5个关键点 points = results[1] for i in results[0]: faceKeyPoint = [] for p in points: for i in range(5): faceKeyPoint.append([p[i], p[i + 5]]) key_points.append(faceKeyPoint) return {"boxes":boxes,"face_key_point":key_points}
    具体代码参考fcce_detect.py。
    四、人脸对齐有时候我们截取的人脸了头像可能是歪的,为了提升检测的质量,需要把人脸校正到同一个标准位置,这个位置是我们定义的,假设我们设定的标准检测头像是这样的。

    假设眼睛,鼻子三个点的坐标分别是a(10,30) b(20,30) c(15,45),具体设置可参看config.ini文件alignment块配置项。
    采用opencv仿射变换进行对齐,获取仿射变换矩阵
    dst_point=【a,b,c】tranform = cv2.getAffineTransform(source_point, dst_point)
    仿射变换
    img_new = cv2.warpAffine(img, tranform, imagesize)
    具体代码参考face_alignment.py文件。
    五、产生特征对齐得到后的头像,放入采用预训练的facenet对检测的人脸进行embedding,embedding成512维度的特征,以(id,vector)的形式保存在lmdb文件中。
    facenet.load_model(facenet_model_checkpoint) images_placeholder = tf.get_default_graph().get_tensor_by_name("input:0") embeddings = tf.get_default_graph().get_tensor_by_name("embeddings:0") phase_train_placeholder = tf.get_default_graph().get_tensor_by_name("phase_train:0") face=self.dectection.find_faces(image) prewhiten_face = facenet.prewhiten(face.image) # Run forward pass to calculate embeddings feed_dict = {images_placeholder: [prewhiten_face], phase_train_placeholder: False} return self.sess.run(embeddings, feed_dict=feed_dict)[0]
    具体代码可参看face_encoder.py。
    人脸特征索引
    人脸识别的时候不能对每一个人脸都进行比较,太慢了,相同的人得到的特征索引都是比较类似,可以采用KNN分类算法去识别,这里采用是更高效annoy算法对人脸特征创建索引,annoy索引算法的有个假设就是,每个人脸特征可以看做是在高维空间的一个点,如果两个很接近(相识),任何超平面都无法把他们分开,也就是说如果空间的点很接近,用超平面去分隔,相似的点一定会分在同一个平面空间。
    #人脸特征先存储在lmdb文件中格式(id,vector),所以这里从lmdb文件中加载lmdb_file = self.lmdb_fileif os.path.isdir(lmdb_file): evn = lmdb.open(lmdb_file) wfp = evn.begin() annoy = AnnoyIndex(self.f) for key, value in wfp.cursor(): key = int(key) value = face_comm.str_to_embed(value) annoy.add_item(key,value) annoy.build(self.num_trees) annoy.save(self.annoy_index_path)
    具体代码可参看face_annoy.py。
    六、人脸识别经过上面三个步骤后,得到人脸特征,在索引中查询最近几个点并就按欧式距离,如果距离小于0.6(更据实际情况设置的阈值)则认为是同一个人,然后根据id在数据库查找到对应人的信息即可。
    #根据人脸特征找到相似的def query_vector(self,face_vector): n=int(face_comm.get_conf('annoy','num_nn_nearst')) return self.annoy.get_nns_by_vector(face_vector,n,include_distances=True)
    具体代码可参看face_annoy.py。
    七、安装部署建表,用于存在用户注册信息,并在web/DqMysql.php 中配置数据库信息。
    create database face;CREATE TABLE `face_user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `img` varchar(1024) NOT NULL DEFAULT '', `user_name` varchar(1024) NOT NULL DEFAULT '', `email` varchar(1024) NOT NULL DEFAULT '', PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8
    系统采用有两个模块组成:

    face_web:提供用户注册登录,人脸采集,php语言实现
    face_server:提供人脸检测,裁剪,对齐,识别功能,python语言实现

    模块间采用socket方式通信通信格式为:length+content;face_server相关的配置在config.ini文件中。
    使用镜像

    face_serverdocker镜像:shareclz/python2.7.10-face-image
    face_web镜像:skiychan/nginx-php7

    假设项目路径为/data1/face-login
    安装face_server容器
    docker run -it --name=face_server --net=host -v /data1:/data1 shareclz/python2.7.10-face-image /bin/bashcd /data1/face-loginrm -rf /data1/face-login/models/facedetect/20180408-102900/._model-20180408-102900.metapython face_server.py安装face_web容器
    docker run -it --name=face_web --net=host -v /data1:/data1 skiychan/nginx-php7 /bin/bashcd /data1/face-login;php -S 0.0.0.0:9988 -t ./web/最终效果:
    face_server加载mtcnn模型和facenet模型后等待人脸请求

    未注册识别失败

    人脸注册

    注册后登录成功

    注意由于模型文件过大,在此无法上传,整个项目放在百度云盘,地址:
    链接: https://pan.baidu.com/s/1z9CFnK6fNsUl-1jVmnaOiw提取码: ktdp
    1 评论 4 下载 2019-03-28 14:30:22 下载需要13点积分
  • 基于VC++实现的支持视频和图片的车牌定位与识别系统

    1 引言在建设平安城市的进程中,安全是政府日常管理工作中的重要任务。随着城市报警和监控系统的建设,对于监控数据的分析也日显重要。
    目前需要对重点街道或路口采集的视频数据进行分析,主要包括两点:

    对于视频中过往机动车辆的机动车牌提取出来,生成截屏图片;
    对提取的图片中机动车牌号码进行提取,并输出文件进行记录。

    通过系统的自动分析提取,可以对街道或路口的车辆流量进行统计,一方面解决了从海量数据中人工分析的困难,另一方面通过机动车辆的统计,对城市道路建设以及交通管理都具有重要指导意义。
    1.1 编写目的该文档是基于视频文件的车辆分析系统的设计说明书,主要是向开发人员介绍系统软件的详细设计。
    1.2 背景为了对街道或路口的车辆流量进行统计,以及解决从海量数据中人工分析的困难,开发此车辆分析系统。
    2 需求分析2.1 功能性需求分析视频文件转换为有车牌号码框的图片集合,将现有的视频文件按每帧进行分析,识别出存在车牌号码框的帧,并且将视频的该帧转换为图片,在图片中将识别出的车牌框使用明显的色彩框进行标注。约束条件:

    视频文件支持MPEG格式,导出的图片集合为BMP格式车牌支持蓝牌、黄牌
    导出图片车牌号码框标识,从视频文件所转换的图片中,将识别到的车牌号码用色

    车牌号码文字识别,对图片中的车牌号码识别形成车牌号码列表,并保存号码列表到文件。约束条件:

    图片为BMP格式
    车牌支持蓝牌,黄牌
    车牌号码支持中国车牌格式,例如:苏AXXXXX

    车流量统计,通过对视频的车牌框识别与生成图片的号码文字识别,对号码进行去重处理,并计算该视频中的车流量。约束条件:

    在车流量计算中,同一个车牌号码只计算为1个流量单位。
    2.1 非功能性需求分析
    系统内存消耗控制在1GB以内
    系统可以在Windows XP操作系统上运行
    支持清晰度为CIF(352*288)、4CIF(705*576)的视频文件

    3 系统总体设计3.1 软件界面
    3.2 系统流程图
    3.3 系统详细说明3.3.1 图像处理图像处理包括灰度化,灰度均匀化,Robert算法轮廓提取,中值滤波,自适应二值化,图像的腐蚀和膨胀。

    图像灰度化函数:cvThreshold()。灰度均匀化函数:cvEqualizeHist()。Robert算法是自己写的,函数名为:RobertDIB()。中值滤波函数是自己写的,这里用的是3*3的大小滤波。函数名为:myMedianFilter()。自适应二值化函数为:cvThreshold(),其中参数为:CV_THRESH_BINARY| CV_THRESH_OTSU,实现自适应。图像的膨胀与腐蚀虽然是使用的opencv封装的cvDilate函数,和cvErode函数,但是膨胀与腐蚀的结构元素和处理次数都是根据实际情况设定的。
    3.3.2 车牌定位车牌定位主要是在前面对图像的处理后,车牌区域已经基本处于一块白色的区域,然后用CvMemStorage类创造出一块内存空间,再用cvFindContours()方法提取所有连通区域的所有轮廓保存到这个内存中。再对每一个轮廓进行筛选。这里进行筛选的方法是,根据轮廓用cvBoundingRect()方法得到每个轮廓的矩形区域,这里因为车辆的远近导致大小不一,所以给车牌的大小设定范围,这里我设定的范围是,宽度比高度大18个像素点,宽度在50像素和25像素之间,高度在25像素和5像素之间,宽度与高度的比值取值可以是2或者3或者4。筛选条件的代码为:
    area_Rect->width-area_Rect->height>=18 &&area_Rect->width<=40 &&area_Rect->width>25 &&area_Rect->height<25 &&area_Rect->height>5 &&(area_Rect->width/area_Rect->height==2 || area_Rect->width/area_Rect->height==3 || area_Rect->width/area_Rect->height==4)
    这样筛选之后,大区域的轮廓就被过滤掉了,但是还有和车牌特征相近的区域存在,比如车灯,车辆的牌子等等。于是,还需要进行下一步过滤。由于,车牌的自身特点是里面会有字符,每个字符之间有一定空隙。于是,我先灰度化,二值化这些被筛选过一次的区域,然后竖直膨胀20次(为了让竖直方向全是白色),最后从左到右的计算0变1,1变0的次数,大于12次(相当于6个字符)的时候就当做是车牌区域。
    3.3.3 车牌识别车牌识别部分我用的是SVM算法+HOG特征提取的方式。先把车牌里的字符进行分割,这里分割的方式和定位车牌的时候很像。提取出每个矩形轮廓基本就是字符区域。然后用另外一个工程制作SVM分类器进行识别,制作过程分为:训练集的收集,通过训练得到分类器,这里的分类器数据我保存在HOG_SVM_DATA.xml文件中,然后主工程文件中只需要调用里面的数据生成cvSvm分类器,最后再将分割出来的每一个字符去进行识别,最后将车牌号输出到主界面的编辑框和外部文件中。
    4 代码结构代码结构很简单,选择输入的视频文件之后,就设置定时器消息函数,定时为0.5s/帧。主要的代码都在MFCShowVideoTestDlg.cpp文件的onTimer()方法中,其中需要的一些函数还放在PlateLocateMethod.cpp文件中。
    5 主要的算法和思路在图像处理部分用到的中值滤波算法和Robert边缘检测算法都已经很成熟,这里不作介绍。主要的算法在于字符识别阶段用到SVM算法,生成分类器,生成分类器的工程名:plateTest。
    这里面用到一个比较特殊的方式,就是先在外部hb.txt文档中写好训练集每一幅图片的路径,下一行则是类别。
    然后在程序中按照文件的基数偶数行分别提取出图片和类别加入训练。使用svm.train()训练,最后通过svm.save(“D://HOG_SVM_DATA.xml” )保存到D盘中。
    在主工程中Findchar()函数用于识别字符,首先创建CvSVM类型的一个分类器,然后加载D盘下HOG_SVM_DATA.xml文件中的数据,代码为:svm.load(“D:/HOG_SVM_DATA.xml”)。
    然后创建HOGDescriptor对象,并且用这个对象里面的compute()方法进行Hog特征计算,其中创建vector类型的动态数组用于存放结果,然后把结果归总到CvMat类型的一块空间中,再通过svm.predict()检测出最后的结果。
    这里需要说明的是,最后结果是一个整型的值。因为字母我也是经过一并放入其中。所以最后的结果需要进行判断其值,如果是值在65以上的就是字符,需要进行强制转换。
    6 总结由于我是一个人参赛,而且时间仓促,给我造成了很大的困难。没有时间去完善代码,和完善其功能,只实现了主体功能。并且因为SVM算法的训练集也没有时间去收集,本应该还有非样本集的,也没有时间去收集,导致了识别率严重偏低。由于识别率太低,没有办法进行去重处理,所以没办法实现流量统计。还有就是
    采用SVM算法太耗时,算法的处理不应该放在主线程,代码的结构还没有时间做最后的改进,即使效果不如意,但是
    方法和思路应该是正确的,只是时间和人员不够,完善好了效果应该还行。
    3 评论 21 下载 2018-10-05 23:57:03 下载需要4点积分
  • 基于Android实现的锁屏软件APP

    一、概述1.1 选题背景首先现在各种安卓的游戏和APP充斥应用市场,但是创意还是有的。
    首先,锁屏软件虽然不是一个创新点,我也承认好多人都做过了,也有做的很绚丽的,这个事实我也承认,但是当前背景是,很多锁屏软件,仅仅是PIN或者是九宫格密码形式,但是造成的问题是,破解起来也很容易,网上就有好多破解教程,简单粗暴。
    所以锁屏软件的安全性还是一个值得考究的部分。同时网上有很多需求,百度知道等等,有好多人都需求一个真正属于他们自己的软件,能够真正的自定义。
    所以基于上述两点,我选择了这个方向的开发,我的APP“ALL IN ONE”就是能为了实现这种的需求实现的,既有防盗功能,也有自定义锁屏界面的功能。
    1.2 项目意义这个项目的意义十分的明显,首先是解决了当前的需求,所以说是一个使用的app,不是说为了创意而创意的产物。
    其次,他的功能很多,应用层面很广阔。首先可以个人使用这款软件,用作娱乐,放松身心以及自由的用户体验。同时他也可以给企业用,企业可以用这种功能来宣传,来投放广告,可以进行营利。所以项目意义重大,one here,use in many place。所以总体上来讲,有足够多的理由证明其意义还是存在的。
    二、可行性分析和目标群体2.1 可行性分析首先可行性是毋庸置疑的,首先,市场方面是存在的,我在做这个项目之前进行了简单的调研,返现有很多人还是希望能够自定义锁屏界面的,因为许多市面上的APP没有这种功能,同时对于防盗功能更是必须的,举例来说在我们的图书馆,很多同学看书看累了或者是粗心大意,就会直接把手机丢到桌子上,同时每个月都有好多同学不是丢电脑就是丢手机的。所以做一个这种的防盗功能的app更是情势所迫,这样可以在某种程度上减少手机的丢失概率。正是因为市场的需求造成这种app的诞生。
    2.2 目标群体目标群体分为两种,主要是分为个人和组织,个人主要面向群体是在校学生这种手机长用户,因为他们容易丢手机,同时也容易成为盗窃的受害者,同时比如像图书管这种防盗不好进行地方,更有这种软件市场,同时对于学生来说,更喜欢个性化,喜欢追求自由。
    而对于组织来说,主要面向各种手机公司,尤其是卖手机的地方,我们的APP的市场更为广阔,企业可以通过自定义锁屏的方式不断循环播放广告,低成本,但是高效率。同时也可以起到防盗的功能,比如某些不可以取下的手机。
    三、作品功能和原型设计3.1 总体功能结构
    3.2 具体功能模块设计3.2.1 锁屏/解锁模块监听android系统级广播,通过service和broadcast receiver共同实现锁屏界面实现。
    3.2.2 九宫格密码加密模块自定义View控件,实现OnCompleteListener,同时使用sharedpreferences操作存贮密码,同时定义相关函数。
    3.2.3 防盗功能模块监听Sensor,和密码加密模块通信。
    3.2.4 自定义锁屏背景模块使用viewpager轮播,app自带锁屏背景,只有当你在相关文件夹中添加了相关图片才会自动载入,实现自定义。
    3.3 界面设计3.3.1 主界面设计
    3.3.2 密码功能界面设计


    3.3.3 防盗功能界面设计

    四、作品实现、特色和难点4.1 作品实现
    作品实现:使用工具
    IDE:adt-bundle-windows-x86_64-20140702,
    SDK:API-19
    Tools: sdk tool:hierarchyviewer.bat, draw9patch.bat

    4.2 特色分析特色有三:

    首先是正常的密码加密以及锁屏功能,有基本的保护功能
    防盗功能,正如同前面分析的,有广泛的应用空间和很强的实用性
    自定义锁屏背景,可以让你的生活不再单调,手机也能透露你的个性,一个好的锁屏背景不如一个真正属于你自己的锁屏背景更加适合你

    4.3 难点和解决方案难点有监听系统消息,同时最重要的是对于九宫格密码的存储识别以及最后校验的过程,同时还有在进行防盗功能时候设置移动度量以及最后senor的register和unregister的事件相应问题。还有就是关于一些逻辑的设计问题,很是个难题,要考虑很多,要不就会留下bug,或者是做出交互性不好的软件。
    解决方案即使查找资料,并且多次试验,找人进行用户体验试验以及重复测试,同时通过画状态图的方式进行相关状态的制约问题。
    1 评论 1 下载 2019-02-27 13:53:19 下载需要16点积分
  • 基于80x86汇编实现的双任务管理系统-贪吃蛇小游戏

    一、题目设计双任务系统设计
    二、设计内容采用8086汇编语言,设计并实现双任务系统设计,对两个任务(两个窗口)进行管理和调度,能够实现任务之间的切换,保存上下文信息。任务调度程序使用循环程序来完成。在左边显示窗口,能够运行简单的贪吃蛇游戏,在右边显示窗口,能够画出等边三角形。
    三、需求分析
    贪吃蛇游戏采用键盘按键控制贪吃蛇前进方向,如“W、S、A、D”键分别为上下左右方向控制按键
    游戏终止条件为贪吃蛇碰触窗口边框、蛇头碰触身体、身体长度达到上限,以“R”键为游戏重新开始。若游戏进行当中无键按下,则贪吃蛇保持当前方向不变直至撞墙
    等边三角形位置在该显示区域的中部,参数边长由键盘输入确定
    三角形每次根据输入的参数,在该窗口将三角形重新绘制出来
    初始工作窗口为右边显示窗口,以后每按一次Tab键切换至旁边显示窗口
    当某个显示窗口被选中时,则焦点停留在该窗口,键盘输入对当前窗口有效
    整个系统按ESC键退出,返回DOS操作系统界面

    四、概要设计4.1 方案设计
    4.2 模块功能说明4.2.1 I/O模块说明
    getInt() 读入整数函数:键盘输入一串字符,检测该字符是否为数字字符。若不是数字字符,则做无效处理。直至输入完整的数值字符保存在AX中并将ZF置1(调用者可用JZ判断是否发生特殊情况)。并做如下处理:①若键盘输入Esc键,返回AX=0,并将ZF置0。②若键盘输入Tab键,返回AX=1,并将ZF置0
    getchar() 函数:输入一个字符,回显
    puts() 函数:输出字符串
    getch() 函数:输入一个字符,不回显
    putInt() 函数:将AX寄存器中的数字以十进制的形式输出

    4.2.2 控制模块说明
    movCursor() 移动光标模块:将光标移动至y行x列
    sDelay() 延时函数模块:控制贪吃蛇移动速度
    kbhit() 模块:检测键盘有无输入
    rand() 随机数发生模块:生成一定范围内的随机数

    五、详细设计及运行结果5.1 流程图三角形模块流程图

    贪吃蛇模块流程图

    5.2 函数之间相互调用的图示函数内部调用图

    顶层调用图示

    5.3 程序设计主要代码任务切换
    yield proc pushf ;先保存一下flags cmp ax,0 ;判断是否是esc jne yEls1 mov AH, 4CH ;esc exit int 21h yEls1: ;是tab,先在它自己的栈里保存寄存器, push ax push bx push cx push dx push di push si push bp ;再切换栈,恢复另一组寄存器 cmp yFunc,0 ;当前是tri? je yTri mov stack_snake_sp,sp mov sp,stack_tri_sp ;当前是1:snake,要切换成tri mov ax,STACK_RTI mov ss,ax mov yFunc,0 jmp yTriEd yTri: mov stack_tri_sp,sp mov sp,stack_snake_sp ;当前是0:tri,要切换成snake mov ax,STACK_SNAKE mov ss,ax mov yFunc,1 ;恢复寄存器 yTriEd: pop bp pop si pop di pop dx pop cx pop bx pop ax popf ret ;切换yield endp
    贪吃蛇移动
    sMove proc pushf push ax push bx push di mov ah,0 mov al,sbody_move mov di,ax ;擦除身体最后一节 shl di,1 mov bl,sbody[di] ;x di*2+0 inc di mov al,sbody[di] ;y call movCursor cmp bl,-1 jz SMEls0 mov al,' ' call putchar SMEls0: mov ah,0 mov al,sbody_move mov bx,ax mov al,x mov di,bx shl di,1 mov sbody[di],al mov al,y inc di mov sbody[di],al dec sbody_move cmp sbody_move,0 ja sMElse1 ;手动取余 mov al,blength mov sbody_move,al sMElse1: ;不需要擦除头部 ;switch(m) cmp m,'s' je sMCs1 cmp m,'a' je sMCs2 cmp m,'d' je sMCs3 cmp m,'w' je sMCs4 ;case sMCs1: inc y jmp SCsEd1 sMCs2: dec x jmp SCsEd1 sMCs3: inc x jmp SCsEd1 sMCs4: dec y jmp SCsEd1 SCsEd1: ;end of switch-case mov al,y ;重新绘制头部 mov bl,x call movCursor mov al,HEAD_CHAR call putchar cmp HEAD_CHAR,'#' jne sMElse2 mov HEAD_CHAR,'*' jmp sMIfEd2 sMElse2: mov HEAD_CHAR,'#' sMIfEd2: ;移动光标 mov bl,0 mov al,SCY call movCursor pop di pop bx pop ax popf retsMove endp
    画三角形主要流程
    triangle proc near pushf push AX push BX push CX push DX mov triLenth,AX ;记录AX输入的边长 call triHypotenuse ;第一次call画左边 call triHypotenuse ;第二次call画右边 call triBase ;画横线 pop DX pop CX pop BX pop AX popf ret
    5.4 运行结果开始界面

    输入等边三角形边长
    若输入超出范围,清除输入数据并等待重新输入(此处为500)

    输入符合规范(此处为200)

    按Enter画等边三角形并重新打印提示信息

    可循环读入等边三角形边长(此处为80)

    按Enter清除右半部分屏幕并重新打印

    按Tab键画等边三角形程序停止,贪吃蛇程序开始运行

    食物出现在随机位置,用W A S D键控制贪吃蛇运动

    若撞墙或碰到自己游戏结束并暂停

    游戏过关

    按R键重新开始游戏

    按Tab键贪吃蛇程序暂停,画等边三角形程序等待输入

    六、总结6.1 不足和改进
    不足

    贪吃蛇游戏制作的精细度和画面质量有待提高贪吃蛇的移动速度无法改变三角形使用整数近似计算
    改进

    可以通过改变蛇身的样子和食物的颜色来提高画面质量可以在界面中设置游戏的速度:慢速 中速 高速三角形可使用浮点数寄存器进行划线

    6.2 体会在本次竞赛学习过程中,项目经过功能划分、肢解分为一层一层的模块调用。将计算机基本指令和中断调用整合起来形成底层模块,再由底层模块整合起来形成较大的功能模块,再由功能模块进行逻辑组合形成snake和tri模块,使用yield任务切换模块作为桥梁将整个程序整合在一起。
    在实际操作中我们认识到熟练掌握汇编语言中的指令的基本用法和组织结构的重要性。只有熟练掌握指令的基本用法,我们才能在实际的编程中像运用高级语言一样灵活的变通,认清计算机组织结构才能灵活设计程序整体架构。汇编语言作为一种低级设计语言,它是最底层的、与计算机内部的结构联系密切,我们在这次竞赛过程中深刻地了解到了这一点。
    在贪吃蛇程序和画等边三角形的程序设计中,加深了对计算机体系结构的理解,深刻理解汇编语言和其他语言的不同。在代码设计中也遇到很多的困难,比如一些寄存器使用冲突的问题,还有一些宏的使用问题和两个程序切换的问题等,以及如何对程序调用时对参数和返回值做一系列约定。在这个方面,我们深刻理解了团队协作能力的重要性。
    1 评论 1 下载 2019-02-13 17:40:41 下载需要16点积分
  • Windows驱动级云安全主动防御系统

    1 使用说明本系统分成四个模块,包含一个应用层可执行文件(.exe),四个内核层驱动(.sys),一个规则库数据库文件(.mdb),请保证六个文件在同一目录。
    1.1 安装服务与启动监控程序不需要安装,双击即可打开可执行文件。打开后,如果需要启动服务,请点击“安装服务”,请切换至“主要”标签页,接着“启动进程监控”、“启动注册表监控”、“启动文件监控”、“启动内存加载监控”,程序开始拦截系统调用。

    1.2 停止监控与停止服务如果需要停止监控,请先在各标签页“停止功能”,例如停止注册表监控,请先点击注册表监控标签页“停止功能”按钮。接着打开“主要”标签页,停止相当服务即可, 如图:

    2 系统设计与架构2.1 开发环境本系统应用层采用Visual Studio 2008(C++)开发,驱动层开发环境为WDK 6001.18002、Notepad++,测试环境为VMware 6.0、Windows XP Sp3。
    2.2 系统结构病毒在计算机运行之后将根据自身的目的呈现出一系列的动作,包括写注册表项,生成文件,远程线程注入等等。本系统通过拦截系统调用对程序行为进行监控,将监控的行为信息交给监控中心和网络服务器分析处理,根据程序行为分析判断病毒,云安全概念的加入,本地特征库极小,占用系统资源很少。本系统设想根据这一系列的动作所组成的行为进行智能的逻辑判断该程序是不是病毒。
    系统结构示意图

    2.3 系统流程本系统分为六大模块:进程监控模块、注册表监控模块、内存加载监控模块、文件创建加载模块、云安全模块、安全监控中心模块。各个模块相互独立,系统流程图如下:

    3 系统实现原理3.1 Hook SSDT在 Windows NT 下,用户模式(User mode)的所有调用,如Kernel32.dll,User32.dll, Advapi32.dll等提供的API,最终都封装在Ntdll.dll中,然后通过Int 2E或SYSENTER进入到内核模式,通过服务ID,在System Service Dispatcher Table中分派系统函数。这些本地系统服务的地址在内核结构中称为系统服务调度表(System Service Dispatch Table,SSDT)中列出,该表可以基于系统调用编号进行索引,以便定位函数的内存地址。下图是Windows NT系列操作系统的体系结构,系统服务调用只是一个接口,它提供了将用户模式下的请求转发到Windows 2000内核的功能,并引发处理器模式的切换。在用户看来,系统服务调用接口就是Windows内核组件功能实现对外的一个界面。系统服务调用接口定义了Windows内核提供的大量服务。

    KeServiceDescriptorTable是由内核导出的表,这个表是访问SSDT的关键,具体结构是:
    typedef struct ServiceDescriptorTable { PVOID ServiceTableBase; PVOID ServiceCounterTable(0); unsigned int NumberOfServices; PVOID ParamTableBase;}
    其中:

    ServiceTableBase是 System Service Dispatch Table 的基地址NumberOfServices 由 ServiceTableBase 描述的服务的数目ServiceCounterTable 包含着 SSDT 中每个服务被调用次数的计数器ParamTableBase 包含每个系统服务参数字节数表的基地址
    System Service Dispath Table(SSDT):系统服务分发表,给出了服务函数的地址,每个地址4子节长。
    System Service Parameter Table(SSPT):系统服务参数表,定义了对应函数的参数字节,每个函数对应一个字节。

    为了调用特定函数,系统服务调度程序KiSystemService将该函数的ID编号乘以4以获取它在SSDT中的偏移量。注意KeServiceDescriptorTable包含了服务数目,该值用于确定在SSDT或SSPT中的最大偏移量。下图也描述了SSPT。该表中的每个元素为单字节长度,以十六进制指定了它在SSDT中的相应函数采取多少字节作为参数。在这个示例中,地址0x804AB3BF处的函数采用0x18个字节的参数。

    当调用INT 2E或SYSENTER指令时会激活系统服务调度程序。这导致进程通过调用该程序转换到内核模式。若将SSDT改为指向rootkit所提供的函数,而不是指向Ntoskrnl.exe或Win32k.sys,当非核心的应用程序调用到内核中时,该请求由系统服务调度程序处理,并且调用了rootkit的函数。这时,rootkit可以将它想要的任何假信息传回到应用程序,从而有效地隐藏自身以及所用的资源。
    由于Windows系统版本对某些内存区域启用了写保护功能,SSDT就包括在其中,若向只读内存区域中执行写入操作,则会发生蓝屏死机(Blue Screen of Death,BSoD)。Windows中可以通过Memory Descriptor List(MDL)修改内存属性, 把原来SSDT的区域映射到我们自己的MDL区域中,并把这个区域设置成可写。MDL的结构:
    typedef struct _MDL { struct _MDL *Next; CSHORT Size; //设置成MDL_MAPPED_TO_SYSTEM_VA ,这块区域就可写 CSHORT MdlFlags; struct _EPROCESS *Process; PVOID MappedSystemVa; PVOID StartVa; ULONG ByteCount; ULONG ByteOffset;} MDL, *PMDL;
    知道KeServiceDscriptorTable的基址和入口数后,用MmCreateMdl创建一个与KeServiceDscriptorTable对应地址和大小的内存区域,然后把这个MDL结构的flag改成MDL_MAPPED_TO_SYSTEM_VA ,那么这个区域就可以写了。最后把这个内存区域调用MmMapLockedPages锁定在内存中。
    通过微软已经导出的几个宏可以很方便地Hook SSDT。
    SYSTEMSERVICE 宏采用由ntoskrnl.exe导出的Zw*函数的地址,并返回相应的Nt*函数在SSDT中的地址。Nt*函数是私有函数,其地址列于SSDT中。Zw*函数是由内核为使用设备驱动程序和其他内核组件而导出的函数。注意,SSDT中的每一项和每个Zw*函数之间不存在一对一的对应关系。
    SYSCALL_INDEX宏采用Zw*函数地址并返回它在SSDT中相应的索引号。该宏和SYSTEMSERVICE宏发挥作用的原因在于Zw*函数起始位置的操作码。通过将该函数的第二个字节看作ULONG类型,这些宏能得到该函数的索引号。
    HOOK_SYSCALL和UNHOOK_SYSCALL宏采用被钩住的Zw*函数的地址,获取其索引号,并自动将SSDT中该索引的相应地址与_Hook函数的地址进行交换。
    #define SYSTEMSERVICE(_func) \ KeServiceDescriptorTable.ServiceTableBase[ *(PULONG)((PUCHAR)_func+1)] #define SYSCALL_INDEX(_Function) *(PULONG)((PUCHAR)_Function+1) #define HOOK_SYSCALL(_Function, _Hook, _Orig ) \ _Orig = (PVOID) InterlockedExchange( (PLONG) \ &MappedSystemCallTable[SYSCALL_INDEX(_Function)], (LONG) _Hook) #define UNHOOK_SYSCALL(_Func, _Hook, _Orig ) \ InterlockedExchange((PLONG) \ &MappedSystemCallTable[SYSCALL_INDEX(_Func)], (LONG) _Hook)
    3.2 监视进程创建和销毁原理监视系统进程开始和结束的方法是通过DDK中的PsSetCreateProcessNotifyRoutine函数设置一个CALLBACK函数。该函数的原形如下:
    NTSTATUS PsSetCreateProcessNotifyRoutine( IN PCREATE_PROCESS_NOTIFY_ROUTINE NotifyRoutine, IN BOOLEAN Remove);

    NotifyRoutine指定了当进程被创建和结束的时候所需要调用的回调函数
    Remove是用来告诉该函数是设置该回调还是移除

    NotifyRoutine的类型为PCREATE_PROCESS_NOTIFY_ROUTINE,其定义为:
    VOID (*PCREATE_PROCESS_NOTIFY_ROUTINE) ( IN HANDLE ParentId, IN HANDLE ProcessId, IN BOOLEAN Create);
    NotifyRoutine的传入参数中,ParentId和ProcessId用来标识进程,Create则是用来表示该进程是正在被创建还是正在被结束。这样,每当进程被创建或者结束的时候,操作系统就会立刻调用NotifyRoutine这个回调函数并正确提供参数。
    由于回调函数NotifyRoutine传入参数的类型为HANDLE,而PID为DWORD类型,可以通过强制转换为DWORD得到父进程、正在创建的进程的PID。
    但此时仍不能得到父进程和正在创建的路径,通过调用一个未公开函数 PsLookupProcessByProcessId得到该进程的PEPROCESS,每个进程都有一个 EPROCESS 结构,里面保存着进程的各种信息,和相关结构的指针。这个函数的原型如下:
    NTSTATUS PsLookupProcessByProcessId( IN ULONG ulProcId, OUT PEPROCESS * pEProcess);
    得到该进程的PEPROCESS对象后,由于PEPROCESS是微软未公开的结构,而且在不同的操作系统下结构还不一样。EPROCESS 偏移0X1fc中保存着一个给人看的进程名,更准确的叫法是映像名称。
    EPROCESS +1fc byte ImageFileName[16]
    ImageFileName[16] 是一个16个字节长的字节数组,保存着进程名。当进程名的长度大于等于16个字节时,在 ImageFileName[16] 只保存进程名的前15个字节,ImageFileName[16] 最后一个字节为0,字符串的结束符。不同进程的进程名可以相同,比如打开多个记事本,那么每个记事本的 ImageFileName[16] 都是 “NOTEPAD.EXE”,进程名只是给人看的,每个进程的 进程ID 都是不同的。对于父进程,在PEPROCESS保存进程名,通过自定义的一个函数当前进程可以得到完整的路径,该函数定义如下:
    PCWSTR GetCurrentProcessFileName() { DWORD dwAddress = (DWORD)PsGetCurrentProcess(); DWORD dwAddress1; if(dwAddress == 0 || dwAddress == 0xFFFFFFFF) return NULL; dwAddress += 0x1B0; if((dwAddress = *(DWORD*)dwAddress) == 0) return 0; dwAddress += 0x10; if((dwAddress = *(DWORD*)dwAddress) == 0) return 0; dwAddress1 = dwAddress;//2000 dwAddress += 0x3C; if((dwAddress = *(DWORD*)dwAddress) == 0) return 0; if (dwAddress < dwAddress1) dwAddress = dwAddress+dwAddress1; return (PCWSTR)dwAddress; }
    但是对于正在创建的进程,并不能如此。必须使用ObOpenObjectByPointer得到进程名柄,ObOpenObjectByPointer的原型如下:
    NTSTATUS ObOpenObjectByPointer( IN PVOID Object, // 进程的pEProcess IN ULONG HandleAttributes, IN PACCESS_STATE PassedAccessState OPTIONAL, IN ACCESS_MASK DesiredAccess, IN POBJECT_TYPE ObjectType, IN KPROCESSOR_MODE AccessMode, OUT PHANDLE Handle //此处将得到名柄);
    得到名柄后可进一步通过未微软未公开的函数ZwQueryInformationProcess 可得到进程的镜像路径。ZwQueryInformationProcess原型如下:
    NTSTATUS ZwQueryInformationProcess( IN HANDLE ProcessHandle, //传入进程句柄 IN PROCESSINFOCLASS ProcessInformationClass,// 指定此处为ProcessImageFileName OUT PVOID ProcessInformation,//此处得到进程镜像信息 IN ULONG ProcessInformationLength, OUT PULONG ReturnLength OPTIONAL);
    得到镜像路径、进程PID等信息后可通知应用层用进程创建,应用层便能迅速取得进程信息,从而可以对进程在未完全加载完成时处理,在进程监控中,应该有内核事件对象、驱动层与用户层、用户层与驱动层通信的知识,首先是内核事件对象。
    在内核中创建的事件对象,用户层只能读,不能给事件置信号,这多少给应用程序与驱动程序之间同步造成麻烦。
    在使用KEVENT 事件对象前,需要首先调用内核函数KeInitialize Event 对其初始化,这个函数的原型如下所示:
    VOID KeInitializeEvent( IN PRKEVENT Event, IN EVENT_TYPE Type, IN BOOLEAN State);

    第一个参数Event 是初始化事件对象的指针
    第二个参数Type 表明事件的类型,事件分两种类型:

    一类是“通知事件”,对应参数为NotificationEvent另一类是“同步事件”,对应参数为SynchronizationEvent
    第三个参数State 如果为TRUE,则事件对象的初始化状态为激发状态,否则为未激发状态。如果创建的事件对象是“通知事件”,当事件对象变为激发态时,需要我们手动将其改回未激发态。如果创建的事件对象是“同步事件”,当事件对象为激发态时,如果遇到相应的KeWaitForXXXX 等内核函数,事件对象会自动变回到未激发态

    设置事件的函数是KeSetEvent,可通过该函数修改事件对象的状态,这个函数的原型如下:
    VOID KeClearEvent( IN PRKEVENT Event);
    在本系统进程监控模块中,我采用的方案是在内核监视进程的创建和销毁,并且应用程序开启新线程一直等待驱动中创建的命名事件。
    在驱动层中,一旦检测到有进程进程的创建立即将事件对象置上信号, 这时应用层立即取得进程PID等信息,利用ZwSuspendProcess将进程挂起,从而可以对进程的信息进行进一步的检验,但是,ZwSuspendProcess是NTDLL.DLL中一个未导出的函数,在《Windows NT 2000 Native API Reference》中找不到其原型。我尝试过在驱动中申明NTSYSAPI的方法尝试利用其在内核层中挂起进程,可惜的是编译不能通过。
    当然在用户层中挂起线程与在内核层中挂起进程是没有差别的,于是采用了在用户态下挂起进程的方法。
    在用户态中使用未导出的内核函数,必须先将ZwSuspendProcess导出。导出.DLL内的函数方法之一是利用GetProcAddress ,这个函数的原型是:
    FARPROC WINAPI GetProcAddress( __in HMODULE hModule, __in LPCSTR lpProcName);
    传入的HMODULE的原型是:
    HMODULE WINAPI LoadLibrary( __in LPCTSTR lpFileName);
    于是通过下面两行代码可以将ZwSuspendProcess内核函数导出:
    HANLDE hdll=LoadLibrary(L"NTDLL.DLL");SuspendProcess=(pfsuspend)GetProcAddress(hdll, "ZwSuspendProcess");
    同时,还得利用相同方法将ZwResumeProcess函数导出,当然,必须得先申明函数原型。经过查阅《Windows System Call Table》,这两函数的原型如下(Nt与Zw系统函数完全相同):
    NTSYSAPI NTSTATUS NTAPI NtSuspendProcess( IN HANDLE Process);NTSYSAPI NTSTATUS NTAPI NtResumeProcess( IN HANDLE Process);
    使用OpenProcess传入PID即可得到进程句柄,将获得的进程句柄传入 ZwSuspendProcess/ZwResumeProcess 中即可实现进程的挂起与恢复。
    应用层传入信息的时候,可以使用WriteFile,也可以使用DeviceIoControl。
    DeviceIoControl是双向的,在读取设备的信息也可以使用。DeviceIoControl 函数会使操作系统产生一 IRP_MJ_DEVICE_CONTROL 类型的IRP,然后这个IRP 会被分发到相应的派遣例程中。因此在驱动入口函数中需要先设置 IRP_MJ_DEVICE_CONTROL 的派遣例程,如
    DriverObject->MajorFunctions[IRP_MJ_DEVICE_CONTROL] = MyDeviceIoControl;
    DeviceIoControl 函数的原型如下:
    BOOL DeviceIoControl( HANDLE hDevice, // handle to device DWORD dwIoControlCode, // operation LPVOID lpInBuffer, // input data buffer DWORD nInBufferSize, // size of input data buffer LPVOID lpOutBuffer, // output data buffer DWORD nOutBufferSize, // size of output data buffer LPDWORD lpBytesReturned, // byte count LPOVERLAPPED lpOverlapped// overlapped information);
    第二个参数dwIoControlCode,它是I/O 控制码,即IOCTL 值,是一个32 位的无符号整型数值。实际上DeviceIoControl 与ReadFile 和WriteFile 相差不大,不过它可以同时提供输入/输出缓冲区,而且还可以通过控制码传递一些特殊信息。IOCTL 值的定义必须遵循DDK 的规定,使用宏CTL_CODE 来声明,如下:
    #define MY_DVC_IN_CODE \(ULONG)CTL_CODE( FILE_DEVICE_UNKNOWN, \0x900, \ // 自定义IOCTL 码METHOD_BUFFERED, \ // 缓冲区I/OFILE_ALL_ACCESS)
    当然在应用DeviceIoControl时,应用程序应该与驱动程序包含相同的IOCTL定义,最好的方法是共享一个头文件,并且应用程序需要包含winioctl.h头文件。
    下为本系统危险进程主动防御模块的流程图

    3.3 监视注册表修改/创建原理注册表监视原理是通过HOOK_SYSCALL宏勾住ZwSetValueKey, 该宏定义如下:
    #define HOOK_SYSCALL(_Function, _Hook, _Orig ) \ _Orig = (PVOID) InterlockedExchange( (PLONG) \
    具体挂接语句为:
    HOOK_SYSCALL(ZwSetValueKey,HookZwSetValueKey,RealZwSetValueKey);
    其中ZwSetValueKey用于更新或者新建一个键值,它的原型如下:
    NTSTATUS ZwSetValueKey( IN HANDLE KeyHandle, IN PUNICODE_STRING ValueName, IN ULONG TitleIndex, IN ULONG Type, IN PVOID Data, IN ULONG DataSize);
    由于DDK文档中没有该函数,在驱动中使用它时必须申明其原型,方法如下:
    NTSYSAPI NTSTATUS NTAPI ZwSetValueKey( IN HANDLE KeyHandle, IN PUNICODE_STRING ValueName,// The name of the value to be set. IN ULONG TitleIndex, IN ULONG Type, IN PVOID Data, // Points to a caller-allocated buffer or variable that contains the data of the value. IN ULONG DataSize);
    HOOK_SYSCALL时,为了保存ZwSetValueKey 函数的真实地址在RealZwSetValueKey中,须定义一个与之类似的结构:
    typedef NTSTATUS (*REALZWSETVALUEKEY)( IN HANDLE KeyHandle, IN PUNICODE_STRING ValueName, IN ULONG TitleIndex OPTIONAL, IN ULONG Type, IN PVOID Data, IN ULONG DataSize);REALZWSETVALUEKEY RealZwSetValueKey=NULL;//初始化
    Hook ZwSetValueKey后,当应用层修改注册时,总会先进入我们挂接的函数HookZwSetValueKey,假若在HookZwSetValueKey函数中不再调用真实的ZwSetValueKey,那么应用层无法修改、创建注册表键键值。HookZwSetValueKey定义与ZwSetValueKey一致,如下:
    NTSTATUS HookZwSetValueKey( IN HANDLE KeyHandle, IN PUNICODE_STRING ValueName, IN ULONG TitleIndex OPTIONAL, IN ULONG Type, IN PVOID Data, IN ULONG DataSize);
    HookZwSetValueKey传入参数包含了将要修改的信息:ValueName指向了要修改的键名。
    在Win32 API中,管理内核对象是通过HANDLE,而在内核内部是通过Object来管理内核对象的。下面是Object属性结构:
    typedef struct _OBJECT_ATTRIBUTES { ULONG Length; //结构长度 HANDLE RootDirectory; //根目录 UNICODE_STRING *ObjectName; //Object的名称 ULONG Attributes; //属性 PSECURITY_DESCRIPTOR SecurityDescriptor;//安全描述符 PSECURITY_QUALITY_OF_SERVICE SecurityQualityOfService;} OBJECT_ATTRIBUTES,*POBJECT_ATTRIBUTES;
    我们感兴趣的是ObjectName成员,因为它包含了注册表名。
    但要想进一步得到要修改的键值则必须进一步通过ObReferenceObjectByHandle得到对象,这是一个在驱动开发中经常用到的函数,该函数原型如下:
    NTSTATUS ObReferenceObjectByHandle( IN HANDLE Handle, //此处传入HookZwSetValueKey的参数KeyHandle IN ACCESS_MASK DesiredAccess, IN POBJECT_TYPE ObjectType OPTIONAL, IN KPROCESSOR_MODE AccessMode, OUT PVOID *Object, //这里返回Object指针 OUT POBJECT_HANDLE_INFORMATION HandleInformation OPTIONAL);
    得到PObject后,理论上可以其通过获得键名信息,由于OBJECT_ATTRIBUTES是不公开的结构,不同操作系统可能不同。为了跨越不同的版本,这里采用未公开的内核函数ObQueryNameString,才能得到键名信息,至此,只得到了操作注册表方面的信息,因为还需得到是哪个进程引发的操作,进程名可通过当前进程偏移量方法获取,而进程的完整路径同样是通过一个自定义函数取得:
    PCWSTR GetCurrentProcessFileName() { DWORD dwAddress = (DWORD)PsGetCurrentProcess(); DWORD dwAddress1; if(dwAddress == 0 || dwAddress == 0xFFFFFFFF) return NULL; dwAddress += 0x1B0; if((dwAddress = *(DWORD*)dwAddress) == 0) return 0; dwAddress += 0x10; if((dwAddress = *(DWORD*)dwAddress) == 0) return 0; dwAddress1 = dwAddress;//2000 dwAddress += 0x3C; if((dwAddress = *(DWORD*)dwAddress) == 0) return 0; if (dwAddress < dwAddress1) dwAddress = dwAddress+dwAddress1; return (PCWSTR)dwAddress; }
    相关方面的信息获取成功后,下一步是检查这些信息,或者让用户决定是否执行这个操作,如果执行,在HookZwSetValueKey只需将传入的各参数原样交给真实的RealZwSetValueKey的处理即可,否则不交给真实RealZwSetValueKey处理,从而导致应用程度修改操作失败。
    由于应用层不能简单地通知驱动层的事件,所以本系统采用了双事件:驱动层通知应用层事件、应用层通知驱动层事件,其中应用层通知驱动层事件通过传递IOCTL ,驱动在IOCTL的派遣函数中设置事件。IOCTL定义如下:
    #define IOCTL_NTPROCDRV_SET_APPEVENT_OK CTL_CODE(FILE_DEVICE_UNKNOWN,0x0911, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
    在驱动的派遣例程中KeSetEvent置信号方法如下:
    case IOCTL_NTPROCDRV_SET_APPEVENT_OK: { KdPrint(("放行!应用层通知驱动层的事件")); RegPass=1; //全居变量,通过,事件设置必须在后面 KeSetEvent(RegAppEvent,IO_NO_INCREMENT,FALSE); status=STATUS_SUCCESS;}break;
    下为本系统注册表主动防御模块的流程图

    3.4 监视文件修改/创建原理文件监视原理是通过HOOK_SYSCALL宏勾住ZwWriteFile, 该宏定义如下:
    #define HOOK_SYSCALL(_Function, _Hook, _Orig ) \ _Orig = (PVOID) InterlockedExchange( (PLONG) \
    具体挂接语句为:
    HOOK_SYSCALL(ZwWriteFile,HookZwWriteFile,RealZwWriteFile);
    ZwWriteFile用于向文件写入数据. 为了能成功挂接ZwWriteFile,与注册表监控相似,必须先申明其原型、Hook函数的原型、用于保存真实地址的RealZwWriteFile。
    NTSYSAPI NTSTATUS NTAPI ZwWriteFile( IN HANDLE FileHandle, IN HANDLE Event OPTIONAL, IN PIO_APC_ROUTINE ApcRoutine OPTIONAL, IN PVOID ApcContext OPTIONAL, OUT PIO_STATUS_BLOCK IoStatusBlock, IN PVOID Buffer, IN ULONG Length, IN PLARGE_INTEGER ByteOffset OPTIONAL, IN PULONG Key OPTIONAL);
    申明保存真实地址的RealZwWriteFile:
    typedef NTSTATUS (*ZWWRITEFILE)( IN HANDLE FileHandle, IN HANDLE Event OPTIONAL, IN PIO_APC_ROUTINE ApcRoutine OPTIONAL, IN PVOID ApcContext OPTIONAL, OUT PIO_STATUS_BLOCK IoStatusBlock, IN PVOID Buffer, IN ULONG Length, IN PLARGE_INTEGER ByteOffset OPTIONAL, IN PULONG Key OPTIONAL);ZWWRITEFILE RealZwWriteFile=NULL;
    申明Hook函数原型:
    NTSTATUS HookZwWriteFile( IN HANDLE FileHandle, IN HANDLE Event OPTIONAL, IN PIO_APC_ROUTINE ApcRoutine OPTIONAL, IN PVOID ApcContext OPTIONAL, OUT PIO_STATUS_BLOCK IoStatusBlock, IN PVOID Buffer, IN ULONG Length, IN PLARGE_INTEGER ByteOffset OPTIONAL, IN PULONG Key OPTIONAL );
    由于本软件不是做文件加密,因些目标是从传入HookZwWriteFile的参数中解析出进程名、进程完整路径、写入(新建)文件的完整路径。其中,进程名、进程完整路径与注册表监视中的原理相似,通过进程上下文和偏移量可获得。
    将要写入的文件的完整路径(以下称目标路径)获得需几步,首先通过传入的FileHandle句柄,使用ObReferenceObjectByHandle得到文件对象,ObReferenceObjectByHandle原型如下:
    NTSTATUS ObReferenceObjectByHandle( IN HANDLE Handle, //传入FileHandle IN ACCESS_MASK DesiredAccess, IN POBJECT_TYPE ObjectType OPTIONAL,//指定为*IoFileObjectType IN KPROCESSOR_MODE AccessMode, OUT PVOID *Object, //得到文件对象 OUT POBJECT_HANDLE_INFORMATION HandleInformation OPTIONAL);
    由于FileObject是一个没有被文档化的对象(This information class is not implementedby any of the supported file systems.),仅知道在文件对象中FileName域保存着部分目标路径,DeviceObject域保存着卷标信息,但是对于本系统这已经够用了。
    以”C:\windows\1.txt”为例,FileName这个UNICODE_STRING的Buffer中保存着“ \windows\1.txt”,经过转换后的卷标将保存着“C:”,使用的函数是IoVolumeDeviceToDosName,以下是函数原型:
    NTSTATUS IoVolumeDeviceToDosName( IN PVOID VolumeDeviceObject, //传入((PFILE_OBJECT)fileobj)->DeviceObject OUT PUNICODE_STRING DosName //得到卷标);
    文件监控做为本系统的一个辅助模块,主要监视的操作系统内进程的文件操作行为,是必需的一个模块,病毒的破坏操作系统的时,往往伴随着文件的释放与生成,例如木马下载者,本身可能无毒,但是后期可以从网上下载其它木马,再比如本机中了蠕虫后,exe大面积感染必然是对各文件进行了写入操作,通过文件监控即可检测到这种行为。同时,能过文件监控,在查打出木马之后,可以对其“同伙”一伙打尽,下图是本系统文件监控的流程图:

    3.5 内存映射监控原理内存监视原理是通过HOOK_SYSCALL宏勾住ZwCreateSection, ZwCreateSection用于routine creates a section object.Hook方法与上类似。该函数原型为:
    NTSYSAPI NTSTATUS NTAPI ZwCreateSection( OUT PHANDLE SectionHandle, IN ACCESS_MASK DesiredAccess, IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL, IN PLARGE_INTEGER MaximumSize OPTIONAL, IN ULONG SectionPageProtection, IN ULONG AllocationAttributes, IN HANDLE FileHandle OPTIONAL//此处包含文件路径信息 );
    其中最后一个参数FileHandle句柄中包含有目标文件的路径信息,与文件监视原理相同,使用ObReferenceObjectByHandle得到文件对象,在FileObject中可进一步取得文件路径与卷标。进程名可通过当前进程偏移量方法获取,进程的完整路径同样是通过一个自定义函数取得,以上原理中已多次提到。
    一个程序得以运行必然要经过内存映射,勾住ZwCreateSection可以拦截记录风险程序的行为,与注册表监控模块、文件监控模块、进程监控模块共同组成完整的程序行为监控系统。
    以下是本系统内存加载监控的流程图

    3.6 云安全模块原理“云安全(Cloud Security)”计划是网络时代信息安全的最新体现,它融合了并行处理、网格计算、未知病毒行为判断等新兴技术和概念,通过网状的大量客户端对网络中软件行为的异常监测,获取互联网中木马、恶意程序的最新信息,传送到Server端进行自动分析和处理,再把病毒和木马的解决方案分发到每一个客户端。
    未来杀毒软件将无法有效地处理日益增多的恶意程序。来自互联网的主要威胁正在由电脑病毒转向恶意程序及木马,在这样的情况下,采用的特征库判别法显然已经过时。云安全技术应用后,识别和查杀病毒不再仅仅依靠本地硬盘中的病毒库,而是依靠庞大的网络服务,实时进行采集、分析以及处理。整个互联网就是一个巨大的“杀毒软件”,参与者越多,每个参与者就越安全,整个互联网就会更安全。
    本系统云安全模块分成服务器,客户端两块。
    服务器采用windows+apache+php+mysql架构,复责返回客户端查询文件信息,处理客户端上传的可疑文件。
    客户端集成在本系统中,可获取文件名、文件详细路径、文件MD5、文件签名等信息,在必要的情况下以这些信息向服务器查询验证信息,并且在客户机允许的情况下自动上传可疑文件,让服务器处理并且反馈结果。
    同时客户端也会将众多用户处理程序行为的信息传至服务器,服务器经过统计分析,可更新各客户机上的规则,实现云规则。
    下图为云模块原理:

    3.7 规则动态加载原理本系统规则存储于一access数据库文件中,程序运行时将从数据库文件初始化规则,规则在程序中是以几条STL中的双向链表(LIST)实现的。
    在规则未经修改时,避免了多次查询数据库而耗时浪费资源。当本系统模块修改、删除数据库时,保存着规则的双向链表将会更新,实现了规则的动态更新。
    下图为规则动态更新原理图:

    4 性能与测试4.1 测试环境本系统的测试环境为:

    VMware 6.0
    Windows XP SP3
    驱动级云安全主动防御
    迅雷
    木马
    等等

    4.2 进程监控测试
    测试项目:测试本系统是否能够有效监控进程创建
    测试工具:驱动级云安全主动防御、记事本程序、SRVINSTW.EXE(病毒)
    测试过程:

    开启本系统进程监控运行”记事本” 程序将”记事本” 程序行为加入白名单,再次运行运行SRVINSTW.EXE(病毒)将SRVINSTW.EXE(病毒)行为加入黑名单,再次运行
    结果及分析如下所示:

    下图为在部署了本系统的主机上打开桌面” 新建 文本文档.txt”时, ” 新建 文本文档.txt”并没有被打开,而是弹出了本系统的用户确认对话框,在云安全模块已经开启的情况下,系统自动联网查询该文件信息,在行为描述一栏有提示“windows自带记事本,该程序是安全的”,用户点击”放行一次”, ” 新建 文本文档.txt”将被打开;用户点击”我认识它,永久放行”, 以后打开记事本程序将都不再提醒。

    下图为把本次程序行为加入白名单,即始终允许本程序启动。

    加入白名单后,再次打开桌面”新建 文本文档.txt”,本系统不再弹出提示信息框,”新建 文本文档.txt”被直接打开,如图:

    此时桌面有一病毒文件SRVINSTW.EXE,在云安全模块已经开启情况下,双击打开,系统将自动联网查询该文件信息,进程打开被阻塞,本系统弹出用户确认对话框,在行为描述一栏有提示“该程序为病毒,请谨慎运行!”,用户选择“禁止一次”,该程序被阻止运行;用户选择“这个很危险,永久禁止”,将自动添加黑名单规则,该程序被阻止运行。如下图:

    用户选择“禁止一次”,进程结束。接着将此进程信息加入黑名单:

    再次双击桌面图标,双击后没有反应,从进程监控主页面拦截信息可知,该程序还没完全开启时已经被主动防御进程结束了。


    从实验结果可以看出,本系统能成功拦截进程创建,在云安全模块和用户维护的规则下能成功阻击病毒的运行。
    4.3 注册表监控测试
    测试项目:测试本系统是否能够有效监控注册表创建、修改
    测试工具:卓然驱动级云安全主动防御、迅雷、DAEMON Tools(虚拟光驱软件)
    测试过程:

    开启本系统注册表监控功能,运行迅雷,运行DAEMON Tools修改迅雷配置,设置“开机启动迅雷”,并放行一次将修改“开机启动迅雷”程序行为加入白名单,再次修改“开机启动迅雷”修改DAEMON Tools开机启动,并拒绝一次将修改DAEMON Tools开机启动行为加入黑名单,再次修改DAEMON Tools开机启动,查看结果
    结果及分析如下所示:

    打开迅雷,配置迅雷,勾选“开机启动迅雷”, 如图:

    本系统立即暂停该操作,并且弹出对话框,请求用户确认是不是要进程该操作,在云安全模块开启的情况下,行为描述一栏会从网络获取该行为的信息,此处为“迅雷开机启动”,如图:

    若此时选择“放行一次”,修改行为将生效;选择“相同一直放行”,修改行为将生效,且以后相同行为不再提醒;选择“禁止一次”,修改请求将被拒绝;选择“相同一直禁止”,修改请求将被拒绝,且以后相同行为不再提醒,直接拒绝。
    测试时选择“放行一次”,再次打开迅雷的配置,可见修改已经成功。

    接着,把迅雷添加启动项行为加入白名单规则,如图:

    再次打开迅雷,修改配置,去掉“开机启动迅雷”前的勾,点击确认,本系统没有弹出确认对话框,修改生效。
    打开DAEMON Tools,修改DAEMON Tools开机启动,如图:

    并且弹出对话框,请求用户确认是不是要进程该操作,在云安全模块开启的情况下,行为描述一栏会从网络获取该行为的信息,此处为“daemon.exe添加启动项可能影响您电脑的开机速度”,如图:

    因为该行为可能影响开机速度,测试时选择禁止一次,拒绝该操作,daemon.exe弹出修改注册表出错,即修改启动不成功,如图:

    将DAEMON Tools添加开机启动行为加入黑名单,如图:

    再次修改DAEMON Tools开机启动,本系统没有弹出确认对话框,DAEMON Tools直接报错,修改不成功,如图:

    4.4 文件监控测试
    测试项目:测试本系统是否能够有效监控文件修改/创建
    测试工具:卓然驱动级云安全主动防御、记事本程序
    测试过程:

    开启本系统文件监控在桌面新建一文本文档,任意写入文本信息,保存。查看结果修改本系统注册表规则,保存。查看结果
    结果及分析如下所示:

    在桌面新建一文本文档,任意写入文本信息,回到本系统文件监控模块,查看信息如下:

    由文件监控信息可知,explorer.exe先创建了“最近打开文件”的链接,接着notepad.exe修改了桌面“新建 文本文档.txt”,完整地记录了修改文件信息。
    在注册表监控测试时,由于本程序需要修改数据库记录,查看文件监控信息,此行为也记录了下来,如图:

    文件监控在新建或修改.exe/.dll/.sys等可执行文件时,在云安全模块开启的情况下,能向服务器验证MD5、签名、文件名等信息,保证不被下载者等木马从网络下载有害程序破坏系统。
    此外,在有蠕虫感染系统时,同一进程将对系统众多可执行文件感染,必然要有打开的修改操作,在文件监控功能与监控中心的配合下,能在早期就察觉出这种危险行为,进而阻止。
    4.5 内存加载监控测试
    测试项目:测试本系统是否能够有效监控内存加载
    测试工具:卓然驱动级云安全主动防御、记事本程序,
    测试过程:

    开启本系统内存加载监控在桌面新建一文本文档,任意写入文本信息,保存。查看结果停止本系统进程监控服务,再次启动,查看结果
    结果及分析如下所示:

    停止本系统进程监控服务,并再次启动,查看内存加载信息,由加载信息可看出,本系统的进程监控服务服务加载时需要一个名为ProcMon.sys加载进入内存,如图:

    在桌面新建一文本文档,任意写入文本信息,回到本系统内存加载模块,查看信息如下:

    从内存加载信息中,可以查看notepad.exe进程加载时哪些文件映射进入了内存,得到结果后可进一步处理。
    在云安全模块开启的情况下,本系统会自己向服务器验证加载进入内存的文件信息,与其它模块相互配合,可将病毒及期衍生物一网打尽。
    参考文献[1] Mark E.Russinovich, David A.Solomon .深入解析Windows操作系统. 电子工业出版社, 2007年6月.
    [2] 张帆.Windows驱动开发详解.电子工业出版社.2008年2月.
    [3] 张静盛.windows编程循序渐进. 机械工业出版社. 2008年6月.
    [4] Jeffrey Richter.WINDOWS核心编程. 清华大学出版社. 2008年9月
    [5] 谭文,杨潇,邵坚磊.寒江独钓—Windows内核安全编程.电子工业出版社.2009.6
    [6] 谭文,邵坚磊.天书夜读—从汇编语言到windows内核编程.电子工业出版社.2009.3
    [7] Greg Hoglund/James Butler .RootKits――Windows内核的安全防护. 清华大学出版社. 2007.4
    [8] liuke_blue.黑客防线.再论进程防火墙.200809
    [9] SVEN B. SCHREIBER .Undocumented Windows 2000 Secrets
    [10] Windows NT 2000 Native API Reference
    +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++经过站内管理员的致电联系教育,听说有人投诉我了,在此声明:这个软件不是我写的,我只是网络的搬运工!!!
    资源是我很早之前从CSDN上下载的,下载自:https://download.csdn.net/download/doomlord/4071653+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    3 评论 6 下载 2018-11-06 18:48:31 下载需要12点积分
  • 基于Python和PyQt5库实现的面向英文文献的编辑与检索

    1 分析1.1 需要完成的功能1.1.1 基本要求
    设计图形界面,可以实现英文文章的编辑与检索功能
    编辑过程包括:

    创建新文件;打开文件;保存文件查找:输入单词在当前打开的文档中进行查找,并将结果显示在界面中替换:将文章中给定的单词替换为另外一个单词,再存盘等
    对于给定的文章片段(30<单词数量<100),统计该片段中每个字符出现的次数,然后以它们作为权值,对每一个字符进行编码,编码完成后再对其编码进行译码。在 图形界面中演示该过程
    对于给定的多篇文章构成的文档集中,统计不同词汇的出现频率,并进行排序,在界面中显示 TOP 20 的排序结果
    对于给定的多篇文章构成的文档集中,建立倒排索引,实现按照关键词的检索,并在界面中显示检索的结果(如:关键词出现的文档编号以及所在的句子片段,可以将关键词高亮显示)

    1.1.2 扩展要求
    界面设计的优化
    对于编码与译码过程,可以自行设计其他算法
    扩展检索:例如,可以实现多于 1 个关键词的联合检索,要求检索结果中同时出现所有的关键词
    优化检索,对于检索结果的相关性排序,例如:包含关键词的数量等信息为依据
    可以自行根据本题目程序的实际应用情况,扩展功能

    1.2 需要处理的数据1.2.1 英文文本多个 txt 文档的导入,主要是文件路径,然后对文档中的内容进行处理。 首先读取文本。然后统计单词、字符的频率以及位置,生成索引;或者直接进行查找等功能。
    1.2.2 用户输入的字符串有多个功能需要用户输入字符串,比如创建新的文档需要命名、搜索单词、修改文档、 在文档中替换内容。
    1.2.3 网络爬取的信息从网站上爬取单词翻译信息,包括 HTML 页面。
    1.2.4 选择的文档信息用户通过“导入文件”,通过窗口选择并导入文档。
    1.3 程序开发运行选用的环境
    操作系统

    版次:Windows 10 家庭中文版版本:1709 OS 内部版本:16299.125
    语言

    Python 3.6.3


    PyQt5==5.9.2 bs4==0.0.1 six==1.11.0 requests==2.18.4

    1.4 用户界面的设计使用 PyQt5 库设计用户界面;主要窗口及功能如下: main:进入程序的 MainWindow,用来导入文件、搜索关键字等。

    item:从 main 进入。主体为显示文章的文本框,同时有多个功能按钮。

    1.5 主要流程图
    2 数据结构设计2.1 所定义主要的数据结构2.1.1 Python 内置结构
    list进行列表建立,以及一些需要排序的操作,其他大部分数据结构无法排序,需要先转化成 list,做排序再考虑转化回去(较麻烦),或者直接进行操作。用[]表示 。
    dict进行词典建立,使用键-值(key-value)存储,具有极快的查找速度,用于在不同目录下查找文本,不同文本下查找单词位置的功能,用牺牲空间的方式加快查找速度。用{}表 示。
    tuple与列表一样,也是一种序列,唯一不同的是元组不能被修改。用()表示。

    2.1.2 哈夫曼树给定 n 个权值作为 n 个叶子结点,构造一棵二叉树,若带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。
    本次构建的哈夫曼树的权值是文章中出现的所有字符的频率。并由此生成每个字符的编 码,频率越高,其叶子结点距根越近,对应的编码也就越短。
    2.1.3 索引结构构建的索引,用来更快更方便的搜索单词或统计频率。利用 Python 本身的 dict, list 以及 tuple 建立索引,结构如下:
    [(文件地址 1, [{'word': 单词 1, 'pos': [位置下标]}, {'word': 单词 2, 'pos': [位置下 标…]},…]), (文件地址 2, [{'word': 单词 1, 'pos': [位置下标]} , {'word': 单词 2, 'pos': [位置下 标…]},…]),…]
    2.2 程序整体结构以及各模块的功能描述
    main.py:总的 main 启动文件
    img.py:资源文件。图片转为文本格式,以便于程序的封装
    KMP.py

    def get_next(p):寻找前缀后缀最长公共元素长度,计算字符串 p 的 next 表def kmp(s, p):核心计算函数,返回字符串 p 的位置下标def positions(string, pattern):查找 pattern 在 string 所有的出现位置的起始下标def count(string, pattern):计算 pattern 在 string 的出现次数
    Huffman.py

    class Node:
    def __init__(self, freq):初始化属性 left, right, father, freqdef isLeft(self):返回该节点是否是父节点的左节点
    def create_Nodes(freqs):根据频率表创建叶子结点def create_Huffman_Tree(nodes):根据频率大小,使用队列创建哈夫曼树def Huffman_Encoding(nodes, root):用哈夫曼树生成哈夫曼对照表def cal_count_freq(content):计算字符频率def cal_Huffman_codes(char_freqs):整合上述 functions
    File.py

    def search(files, keyword):用 KMP 算法搜索计算所有传入的文件中 keyword 的数量,返回有序的文件及数量列表def cal_words_freq(files, reverse=True):用 KMP 算法搜索计算所有传入的文件中所有单词各自的数量,返回有序的文件及单词列表及数量def cal_words_freq(files, reverse=True):用 KMP 算法搜索计算所有传入的文件中所有单词各自的数量,返回有序的文件及单词列表及位置class File:
    def __init__(self, file_pos):初始化函数,传入文件路径作为属性def get_content(self):获得文件内容def set_content(self, content):将文件内容修改为 contentdef get_huffman_codes(self):统计文件内容中 word 出现的次数def get_huffman_codes(self):获得哈夫曼编码表def get_encodeStr(self):获得文件内容通过哈夫曼编码表编码后的字符串def get_decodeStr(self, huffmanStr):通过哈夫曼编码表将编码转换成原字 符串

    SplashScreen.py

    class SplashScreen:
    def __init__(self):继承 PyQt5.QtWidgets.QSplashScreendef effect(self):启动页的渐隐渐出特效

    main_UI.py

    class UI:
    def __init__(self):构造函数,将属性 search_status 和 freqs 初始化def init(self):设置事件触发def add_files(self):调用 PyQt5 的 FileDialog 选择要添加的文件def about(self):“关于”窗口def create_file(self):创建文本文档,并添加文件def cal_words_freqs(self):使用索引,统计词频并生成显示降序列表def load_package(self):读取索引def packaging(self):生成索引文件。使用 six 库中 pickle 打包成二进制文件def clear_list(self):清空文件列表def get_research_content(self):获得用户输入的需要检索的内容def get_files_from_table(self):获得当前文件列表def creat_tableWidget(self, files, nums=[], poss=[]):在 GUI 中生成列表及相关信息def closeEvent(self, event):窗口关闭时出发的关闭事件def search(self):根据索引查找多个单词或词组,并显示频率及位置信息def buttonClicked(self):debug 所用,在底部状态栏显示相关信息def itemClicked(self, row, col):文件表单项目点击事件,打开新的 item 窗口

    item_UI.py

    class item_UI
    def __init__(self, file_pos, keyword=None):初始化,将属性 file 初始化为file_pos,同时高亮 keyworddef init(self, keyword, filename):连接按钮点击事件def highlight(self, pattern, color=”yellow”):将所有 pattern 做黄色高亮处理def hightlight_specific(self, pos=(0, 0), color=”gray”):按位置高亮def encode(self):调用 self.file.get_encodeStr()显示编码def decode(self):调用函数进行译码def huffman_codes(self):显示哈夫曼编码表def edit(self):将文本框控件变为可编辑def save(self):将文本框中的文本保存到文件中def search_substitute(self):打开新窗口,查找或替换def translate(self):翻译所选文本

    about_UI.py:“关于”窗口
    freq_UI.py:显示表单的窗口。用来显示词频统计
    huffman_UI.py:显示表单的窗口。用来显示哈夫曼表
    file_UI.py:用来选择文件的窗口
    create_file_UI.py:新建文档的窗口
    progressbar_UI.py:进度条窗口
    search_UI.py

    class search_UI:
    def __init__(self, item_ui):构造函数def init(self):连接按钮事件def prepare(self):计算所有用户查询的单词的位置信息def next_word(self):高亮下一个搜索结果def count(self):计数def substitute(self):替换当前高亮的一个结果def substitute_all(self):替换所有符合条件的结果


    3 详细设计3.1 构造哈夫曼树
    3.2 KMP 算法3.2.1 获取 next 表
    3.2.2 文本匹配
    3.3 UI 设计3.3.1 主界面搜索多个单词和词组

    创建索引

    3.3.2 item 窗口
    查找下一个

    翻译功能

    显示哈夫曼编码表

    4 总结与提高当我刚拿到题目要求的时候,我的内心是抗拒的——需要自己完成哈夫曼编码(包括统计词频,建立哈夫曼树,根据哈夫曼树计算对应的编码),KMP 算法(之前只是听说过,完全不清楚原理及算法),而且这一切都需要用图形用户界面来展现出来,于是只能硬着头皮上了。
    最开始时,我决定从数据结构和算法入手,先不管图形界面。于是开始在网上学习 KMP 算法的原理及步骤。在和同学讨论的过程中,我们慢慢地结合着资料把代码写了出来,并测试了与传统算法的速度对比,很有成就感。
    然后是哈夫曼树。这部分相对简单,因为其原理我们已经在上学期的课程中接触过,所以主要的时间都用来写代码。过程很顺利,达到了理想结果。
    到了最麻烦的图形界面部分,开始时我本想使用 wxpython 库来实现,但在简单的了解 过后,我发现这个库的更新情况不是很理想,尤其是它的 designer 和库的版本不同步,designer 生成的代码甚至直接运行会报错,于是我放弃了,转向更为成熟的、跨平台的 PyQt5。早就 听说过 Qt 的大名,终于有机会使用下了。最开始遇到的问题,每个程序进程只能运行一个 MainWindow 类,在不知道的情况下打开多窗口一直报错,后来得以解决。随之而来的是文本的高亮问题,后来查到的方法是在文本框中控制虚拟光标选中目标然后更改其格式,已达到高亮的效果。
    在完成大部分功能要求后,我开始想其他扩展功能。翻译功能是个很棒的想法。起初, 我想建立本地的词典资源文件,通过索引或二分法进行查找,但后来发现很难找到好用的文本词典资源。于是转而使用爬虫进行翻译,还好效果不错,只是需要联网。
    另一个扩展功能本来是想做通过自然语言识别来对文本进行一些情感分析或者全文概括,但在查阅部分资料后发现很难实现。首先,文本本身多为记叙文,很长且情感并不明显, 其次需要的类似的数据集几乎找不到,所以最终放弃了这个想法。
    最后是将整个程序封装成 Windows 下运行的 exe 应用程序,使用了 pyinstaller 库。过程很简单,只需要在命令行进行一些操作就好了。
    在整个编写课设的过程中,主要学习了 PyQt5 的使用,对于以后编写简单的图形用户界 面非常有帮助。复习巩固了哈夫曼树的创建。学习了 KMP 这种字符串匹配的算法。同时我对于 Python 的使用也更加熟练。
    对于自己完成课设情况的评价呢,我觉得我完成的还是不错的(哈哈哈)。虽然从美观方面来讲很一般,但是功能的实现以及代码的结构还是可以的,基本功能和扩展功能基本都做到了,后期的 Bug 调试也基本保证了正常使用过程中程序不会报错崩溃。如果要改进的话,可能更多会把注意力放在如何把各种功能的表现形式变得更美观。
    非常感谢这种独自完成整个项目的机会,各方面都有所提升,也很有成就感。能看着整个程序跑起来还是非常爽的!
    0 评论 7 下载 2018-12-13 09:26:53 下载需要9点积分
  • 基于VC++实现的Windows平台逆向调试器

    前 言程序不是写出来的,而是调出来的!回忆学生时代写程序的时候,遇到 bug 往往很无奈,常常是把程序从头到尾看一遍,或者是在一些关键的地方,觉得会出错的地方,printf一下,来判断是不是这里出错了。曾经还觉得这个方法不错,沾沾自喜呢。
    后来工作后,或多或少要调一些程序,但是总觉得不得要领。 所以我觉得无论是软件逆向工程的研究者还是开发人员,都有必要自己实现一个调试器,当你的调试器完成的时候,或许你对软件调试会有一种顿悟的感觉。
    而且可以根据自己的需要,写一个适合自己的调试器,对于逆向或者调试程序来说,都会得心应手,而且还可以过针对某些调试器的反调试哦!
    因鄙人没有天马行空的创意,亦没有巧夺天工的技术!所以调试器设计得也有很多不足,但也略有心得,故拿出来,与大家分享之,希望能帮助到有需要的人。
    由于本人能力有限,文中必然有错漏之处,恳请读者不吝赐教。
    第一章 调试器框架1.1 框架的搭建有写过Windows程序的人都知道Windows是基于消息的,当程序创建,窗口移动,键盘按下等,窗口过程函数都会收到消息,然后根据消息做相应的处理。其实调试器也是这样的,在 MSDN 中已经给出了一个现成的框架了,如下,我已经去掉部分注释了,如果想获得祥细信息的话可以查下 MSDN。
    DEBUG_EVENT DebugEv; // debugging event information DWORD dwContinueStatus = DBG_CONTINUE; // exception continuation for(;;) { // Wait for a debugging event to occur. The second parameter indicates // that the function does not return until a debugging event occurs. WaitForDebugEvent(&DebugEv, INFINITE); // Process the debugging event code. switch (DebugEv.dwDebugEventCode) { case EXCEPTION_DEBUG_EVENT: // Process the exception code. When handling // exceptions, remember to set the continuation // status parameter (dwContinueStatus). This value // is used by the ContinueDebugEvent function. switch (DebugEv.u.Exception.ExceptionRecord.ExceptionCode) { case EXCEPTION_ACCESS_VIOLATION: case EXCEPTION_BREAKPOINT: case EXCEPTION_DATATYPE_MISALIGNMENT: case EXCEPTION_SINGLE_STEP: case DBG_CONTROL_C: } case CREATE_THREAD_DEBUG_EVENT: case CREATE_PROCESS_DEBUG_EVENT: case EXIT_THREAD_DEBUG_EVENT: // Display the thread's exit code. case EXIT_PROCESS_DEBUG_EVENT: // Display the process's exit code. case LOAD_DLL_DEBUG_EVENT: // Read the debugging information included in the newly // loaded DLL. case UNLOAD_DLL_DEBUG_EVENT: // Display a message that the DLL has been unloaded. case OUTPUT_DEBUG_STRING_EVENT: // Display the output debugging string. } // Resume executing the thread that reported the debugging event. ContinueDebugEvent(DebugEv.dwProcessId, DebugEv.dwThreadId, dwContinueStatus); }
    Windows 提供了一些 Debug API,他们已经实现了一般调试器所需的大部分功能,通过现有的框架和Windows提供的API我们可以很容易的搭建起一个调试器的框架出来。
    1.2 调试事件种类
    1.3 调试事件处理通过对上面几个事件的处理,基本可以实现我们的调试器了。当我们创建一个调试进程后,调试器会收到 CREATE_PROCESS_DEBUG_EVENT 事件,在这里我们做一些PE解析工作,比如取得OEP等等,然后在OEP处下一个Int3 断点,处理完之后,将调用ContinueDebugEvent函数来继续线程的运行,这里要注意,这个函数的 dwContinueStatus 有二个取值,DBG_CONTINUE 和DBG_EXCEPTION_NOT_HANDLED,如果这个异常我们可以处理则取DBG_CONTINUE , 否 则 取 DBG_EXCEPTION_NOT_HANDLED 。DBG_EXCEPTION_NOT_HANDLED 则告诉操作系统,我的程序不能处理这个异常,给你处理,然后操作系统再问下调试进程能不能处理,如果不能的话,直接给干掉。
    同时还有一点要注意,当创建一个调试进程的时候,系统会先调用一次DebugBreak 函数,我们只要判断一下是不是系统调用的,是的话无视掉就行了。
    接着调试器再调用WaitForDebugEvent函数等待调试事件的发生,直到接收到EXIT_PROCESS_DEBUG_EVENT事件时结束。
    第二章 内存断点2.1 问题的提出与分析2.1.1 问题提出因为内存断点是调试器的一个难点,所以我们这里放在最前面讲,这样也可以避免在后面的设计中一些组合问题的产生。
    一个内存分页可能会存在多个断点,一个断点可能会跨多个内存分页。某个内存分页可能不具有用户要设的断点属性。某内存分页具有读与写的权限,但是用户只下了写的断点,如果将读的权限也一起拿掉,程序在读那块内存分页也要断下来,这样将大大的降低程序的效率,如何优化,以及断点信息与内存信息的存储。
    2.2 问题的解决2.2.1 内断断点与内存分页的关系一个内存分页可能会存在多个断点,一个断点可能会跨多个内存分页。这个不由的让我们想起第一阶段的学生管理系统(一个学生可以选任意门课程,一门课程可能有任意个学生选)。其实二者的关系是一样的,因为是多对多的关系,所以我们要构造出第三张表来管理他们的关系。
    为了检测用户下断点有效性等问题,在下断点的时候取得那个内页分页信息,判断地址是否有效,然后再判断是否具有要断下来的属性。对一个没有读属性的内存分页下读断点是无效的。
    为了提高效率,中间表用了二个,一个是以内存页序号为主键,一个是以断点序号为主键,使用动态邻接表(长度自动增长)存储,这样可以提高程序的处理速度。当调试器收到异常信息,首先取得产生异常的地址,以及错误编码,再通过异常地址取得内存页的序号,再通过内存页的序号去邻接表里查他有多少个断点,如果当前有内存断点的话,恢复内存页原属性,然后再判断当前位置是否处于用户设的断点地址内,是的话,处理用户的请求,最后设单步,在单步中再将内存页的属性设成新的属性。
    添加内存断点流程图如下所示:

    处理异常流程图如下所示:

    内存断点本来就是一件很耗资源的,为了最大的提升效率,当处理某内存分页具有读写的权限,而用户只下读或写的权限的这类情况,我们可以通过对原属性的分析,然后再减去用户新的属性,再根据结果生成新的属性,因为移除某个属性,并不能单单的通过去除某个位而完成。
    相关记录项的结构:

    内存页与断点关系表(邻接表):

    第三章 int3 断点与硬件断点3.1 问题的提出与解决3.1.1 问题提出如何实现软硬件断点?如何判断用户断点的正确性?如何提高效率?
    3.2 问题的解决3.2.1 Int3断点的实现8086/8088 提供断点指令Int3 和单步标志TF,调试工具利用它们可以设置断点和实现单步。从 80386 开始,在片上集成了调试寄存器。利用这些调试寄存器不仅可以设置代码执行断点,而且还可以设置数据访问断点,不仅可以把断点设置在RAM 中,也可以把断点设置在ROM 中。
    Int3 是Intel系列 CPU专门引入的用于表示中断的指令,对于Int3 断点,常见的做法是在用户要下断点的地方,将指令替换成CC,当调试程序运行到 CC 处,调试器将会收到EXCEPTION_BREAKPOINT消息,然后在这里将原CC处的代码还原及 EIP 减一再处理用户的请求。用 Int3 的好处很明显,可以设任意多个断点,缺点是改变了程序的原指令,容易被软件检测到,而且这里可能会遇到一个问题,当用户在某个内存分页设了一个不可读、不可写的断点,这时调试器是无法将CC 写进去的,也无法将原来的指令读出来。所以在设之前,我们先将目标内存页的属性设为可读可写的,设完之后再将内存页置为新的属性(移除了用户断点权限的新属性),这个开销是非常大的。
    本程序中,对于用户断点的正确性检测只做了如下判断,首先是判断用户的断点是否是处于有效的内存分页内,然后再判断是否重复,同一个内存地址,只能设一个断点,而对于Int3 断点和硬件执行断点并没有判断是否处于指令起始处。
    当初的设想是被调试程序跑起来的时候,将被调试程序的指令全部解析一下,然后取得每条指令的正确起始位置,添加到树中,然后用户在下 Int3 断点的时候,再判断一下下的断点是否处于指令的首地址处。后来请教了一下老师以及查看windbg 等同类软件,发现也没有做这类的检测,同时分析觉得此弊大于利,所以放弃了这种做法,程序中并没有检测。
    3.2.2 硬件断点的实现 80386和80486都支持6个调试寄存器,如图:

    他们分别是 DR0、DR1、DR2、DR3,调试状态寄存器 DR6 和调试控制寄存器DR7。这些断点寄存器都是32位寄存器。
    Dr0~3 用于设置硬件断点的线性地址,也就是断点位置,产生的异常是STATUS_SINGLE_STEP 。这些寄存器长 32 位,与 32 位线性地址长度相符,处理器硬件把执行指令所涉及的线性地址和断点地址寄存内的线性地址进行比较,判断执行指令是否触及断点。与 Int3 不同的是,硬件断点不仅可以设置在RAM 中,也可以设置在ROM 中,且速度比Int3 更快。
    Dr7 是一些控制位,用于控制断点的方式。其中有四个GiLi,分别对应四个断点,Gi、Li控制DRi所指示的断点i在断点条件满足是,是否引起异常。
    Gi 和 Li 分别称为全局断点允许位和局部断点允许位。在任务切换时,处理器清各 Li 位,所以 Li 位只支持一个任务范围内的断点,任务切换并不影响 Gi位,所以Gi支持系统内各任务的断点。
    R/Wi分别对应四个DRi,R/Wi字段指示DRi断点信息的类型。每一个R/W占两位,所表示的类型为:

    LENi字段指示DRi断点信息的长度,每一个LENi占两位。所表示的长度如下:

    注意: 指令执行断点的断点长度必须为 1 字节,数据访问的断点长度可以是 1、2、4 字节。
    Dr6 用于显示是哪些引起断点的原因,如果是 Dr0~3 或单步(EFLAGS 的TF)的话,则相应设置对应的位。
    Bi表示由DRi所指示的断点引起的调试陷阱。
    对于 DR7 的设置,原本程序中是使用位移来完成的,但是调试的时候非常之不方便,所以后来改用结构体了。结构体的定义如下:

    3.2.3 对于int3断点的优化当设int3 断点的时候,判断一下当前调试寄存器有没有空闲的,有的话则优先使用调试寄存器,因为 int3 断点涉及内存分页属性,读目标进程内存,写目标进程,而硬件断点只需设调试寄存器,这样可以大大的提高效率。
    第四章 函数名的解析4.1 问题的提出与分析4.1.1 问题的分析最初对于这个问题处理的想法就是分析被调试程序的导入表,后来和同学讨论,发现这样不能解决问题,因为 API 里可能还调用了别的 API,再跟进 API的话,程序就不能正确解析函数名了!
    据说Windows有上万个API,如何组织这些数据,如果提高查询的效率、以及最大化的合理使用空间。
    4.2 问题的解决4.2.1 函数名与函数地址的组织用一个链表用来存储已经加载的 DLL的名字已及当前状态。为了提高查询的效率,同时记录DLL名字的CRC值,当查找时,算出要查找的DLL名的CRC值,再直接查找通过CRC值查找,当 CRC值相同时,再用字符串比较是否完全相等,当加载DLL比较多的时候,或者经常要查询的话,这样可以大大的提升的效率。
    同时用红黑树记录函数名地址(函数名在被调试进程里面的地址)以及函数的绝对地址(基址加偏移后的真实地址)。
    红黑树并不追求“完全平衡”——它只要求部分地达到平衡要求,降低了对旋转的要求,从而提高了性能。红黑树能够以O(log2 n) 的时间复杂度进行搜索、插入、删除操作。此外,由于它的设计,任何不平衡都会在三次旋转之内解决。
    4.2.2 函数名的显示在我的设计中,使用的是 OD的反汇编引擎,结构体中有记录命令的类型,首先判断他是不是call、jmp 这类的指令,然后再根据相应的值,找出API的地址,再判断目标地址是否是 API 函数的地址,是的话则从本地加载 DLL 链表中取得DLL名字,然后再通过函数名地址去调试进程中取得函数名(在程序中,并没有申请空间保存函数名,因为导出函数可能非常之多,不停的申请空间,然后字符串拷备,这个开销是非常之大的,所以在程序中我们只记录函数名在调试进程中的地址,同时 dll 名链表中有个状态值,当状态值为真时,表明那个 dll 当前已经加载,这样可以直接去内存拿函数名)。而对于call ebp 之类的指令,而需要在运行时才解析,因为在没有执行到那条语句的时候,寄存器的值是不确定的。
    效果如下:

    第五章 指令记录5.1 问题的提出与分析5.1.1 问题的分析当初听到这个的时候,觉得无从下手,而且也想到了很多极端的问题,比如运行的时候不停的修改原来的指令来达到往下运行的效果。然后进到 API 里面去了,那个记录量是非常之大的,而且当时 A1Pass 的测试结果是每秒只能跑 7 条指令。于是花了很多时间在想着效率提升这块,后来和同学讨论一下,他叫我学习一下OD的这个功能。
    5.2 问题的解决5.2.1 数据的组织因为可能记录的数据量非常之大,所以要采用一种合理的数据结构,比如用户可以按指令跑的顺序记下来,也可以按地址的高低来记录,程序在运行中,可能某个地址的指令要跑很多次,但是我们不能重复记录很多次,所以这需要一种特定的数据结构,在程序中,我使用的是带头尾指针的链表与红黑树的结合,首先是将指令加到链表尾部,然后再插到红黑树里面去,这样查找起来的话,速度非常快的(因为我们每跑一条指令,都要先判断一下树中有没有,没有的话才插到树中去),而且体积只是比单单的红黑树多一个指针域。

    结合后的数据结构:这样就可以把链表与树的优点结合起来了,而补了链表的查找效率的不足。

    5.2.2 指令记录实现在第一个解决方案中,采用的是置单步记录,但是经过测试这个效率不高,而且处理起来也很复杂,比如对于每个 call 之类的指令,判断是不是调用 API 之类的,而且经常会出现一些小问题,兼容性也不太好。
    在第二个解决方案中,采用的是内存断点的方法,这个实现起来方便快捷,也不用判断是不是调用API,而且可以指定要记录的部分,不过内存断点的开销还是挺大的。
    5.2.3 效率的优化假定计算机访问寄存器需要花费一个单位时间片,访问 cache可能要花费几个单位时间片,或许现代计算机已经可以像访问寄存器那样高速了,但是访问内存却要花费一百多的单位时间,则访问磁盘则更慢了。
    为了充分的利用计算机时间,让写入磁盘与处理同时进行,在树节点中,我们直接记录指令的十六进制的值,而不直接记录反汇编结果,这样最大的节省空间,如果对于调用 API 的话,在节点 JmpConst 中记录,如果没有的话,这个值为零。然后在写入磁盘的时间,调用反汇编引擎将 Dump 的内容解析出来,如果JmpConst有值的话,再转成相应的DLL名和函数名。这样就可以并发处理了,从而充分的利用了CPU的时间以及节省了内存空间。
    结点内存图:

    第六章 反反调试6.1 问题的提出与分析一些恶意软件常常使用一些反调技手段用以阻碍对其进行逆向工程。反调试技术是一种常见的反检测技术,因为恶意软件总是企图监视自己的代码以检测是否自己正在被调试。为做到这一点,恶意软件可以检查自己代码是否被设置了断点,或者直接通过系统调用来检测调试器。
    虽然在本设计中还没有来得及添加这些功能,但是想通过本章的内容,达到一个抛砖引玉的目的。
    下面介绍二种常见的反调试方法:

    检测CC 断点
    检测调试器

    6.2 问题的解决对于检测断点的反调试方法,我们可以动态的生成断点,比如 CC 断点,CD断点等等,然后在处理函数做相应的处理就可以了。
    检测调试器的存在,一般是使用 IsDebuggerPresent 函数,跟进 API 我们发现他的实现很简单。
    7C8130A3 > 64:A1 18000000 MOV EAX,DWORD PTR FS:[18] 7C8130A9 8B40 30 MOV EAX,DWORD PTR DS:[EAX+30] 7C8130AC 0FB640 02 MOVZX EAX,BYTE PTR DS:[EAX+2] 7C8130B0 C3 RETN
    FS 指向的当前活动线程的 TEB 结构,TEB 的第一个结构是 TIB,TIB 偏移0x18 处是一个回指指针,指向自身,这里虽然是指向自身,但是我觉得这可能是微软以后为了扩展功能用的,而且从相关的代码中也可以看出来,他们都是先在TEB 偏0x18 处取值,然后再做相关的操作。 之后再取得PEB 的地址。
    通过Windbg 我们可以到PEB 的结构信息:
    0:000> dt _PEB 0x7ffde000 ntdll!_PEB +0x000 InheritedAddressSpace : 0 '' +0x001 ReadImageFileExecOptions : 0 '' +0x002 BeingDebugged : 0x1 '' +0x003 SpareBool : 0 '' +0x004 Mutant : 0xffffffff +0x008 ImageBaseAddress : 0x01000000 +0x00c Ldr : 0x00181ea0 _PEB_LDR_DATA +0x010 ProcessParameters : 0x00020000 _RTL_USER_PROCESS_PARAMETERS ………………
    这样我们就可以看到,IsDebuggerPresent 只是简单的通过判断 PEB 结构的BeingDebugged 值来判断是否被调试器加载。
    这样很简单的反反调试就出来了,我们只需将 PEB 结构的BeingDebugged的值改成0 就可以了。
    __asm { mov eax, fs:[0x18] mov eax, [eax + 0x30] mov byte ptr [eax+2], 0 }
    只要将这段代码写到目标进程里跑一下就可以了。当然,更好的话当然是自己实现CreateProcess这类函数。
    结束语本系统的功能基本实现,因为个人努力不够的关系,很多地方不尽人意,系统原定的很多功能也被减去了,对于部分反调试功能等等,这个将作为后期的开发计划。
    1 评论 6 下载 2018-12-04 09:27:40 下载需要11点积分
  • 基于QT实现的宠物小精灵人机对战游戏

    1 依赖环境
    C++ 11 Support

    MSVC >= 14 (2015)GCC >= 5
    SQLite 3 (include in ./include)
    QT5

    2 特性
    使用C++11编写
    实现了简单反射,可以通过GetClassByName通过字符串获得相应类的对象
    使用反射工厂来创建Pokemon对象,配合多态大大简化代码并提供扩展性
    通过宏注入获得类元数据,实现ORM(对象关系模型)封装SQLite数据库操作,运用访问者模式来自动生成SQL语句
    使用JSON提高协议的可扩展性
    良好的人机交互界面,支持多用户同时在线
    健全的错误恢复处理

    3 界面设计登陆及注册
    游戏主界面
    用户列表
    抓取
    背包
    对战
    实时对战(动画)
    战斗结束
    4 框架设计
    5 协议设计游戏通讯协议基于json,以达到良好的扩展性登录
    {"define" : LOG_IN, "username" : username, "password" : password}{"define" : LOG_IN_SUCCESS/LOG_IN_FAIL_WP/LOG_IN_FAIL_AO/SERVER_ERROR}
    注册
    {"define" : SIGN_UP, "username" : username, "password" : password}{"define" : SIGN_UP_SUCCESS/SIGN_UP_FAIL/SERVER_ERROR}
    获取在线用户列表
    {"define" : GET_ONLINE_LIST}{"define" : QUERY_SUCCESS/SERVER_ERROR, "info" : [list]}
    获取离线用户列表
    {"define" : GET_OFFLINE_LIST}{"define" : QUERY_SUCCESS/SERVER_ERROR, "info" : [list]}
    获取用户背包内容
    {"define" : GET_USER_BAG, "username" : username}{"define" : QUERY_SUCCESS, "info" : [{"id" : id, "name" : name, "level" : level, "exp" : exp, "type" : type, "atttackPoint" : ap, "defensePoint" : dp, "healthPoint" : hp, "attackFrequence" : af, "property" : property }]}{"define" : SERVER_ERROR}
    获取用户成就
    {"define" : GET_USER_ACH}{"define" : QUERY_SUCCESS, "rate" : rate, "sum_ach" : sum , "advance_ach" : advance_sum}{"define" : SERVER_ERROR}
    获取对战精灵列表
    {"define" : GET_POKEMON_LIST}{"define" : QUERY_SUCCESS, "info" : [list]}{"define" : SERVER_ERROR}
    获取对战精灵信息
    {"define" : GET_POKEMON_INFO}{"define" : QUERY_SUCCESS, "info" : {"name" : name, "type" : type, "attackPoint" : ap, "defensePoint" : dp, "healthPoint" : hp, "attackFrequence" : af, "property" : property, "exp" : exp}}{"define" : SERVER_ERROR}
    反馈对战胜利
    {"define" : GAME_WIN, "get" : acquire_pokemon, #对战胜利小精灵的名字 "name" : user_pokemon, #用户对战小精灵的名字 "type": type, "attackPoint" : ap, "defensePoint" : dp, "healthPoint" : hp, "attackFrequence" : af, "property" : property, "level" : level, "exp" : exp, "id" : id #小精灵信息在数据库中的唯一id}{"define" : ACCEPT/SERVER_ERROR}
    反馈对战失败
    {"define" : GAME_LOSE, "isLose" : true/false}if isLose == true: {"define" : ACCEPT, "info" : [{"id" : id, "name" : name, "level" : level, "exp" : exp, "type" : type, "atttackPoint" : ap, "defensePoint" : dp, "healthPoint" : hp, "attackFrequence" : af, "property" : property }] }else {"define" : ACCEPT/SERVER_ERROR}
    反馈损失小精灵
    {"define" : LOSE_POKEMON, "id" : id}{"define" : ACCEPT/SERVER_ERROR}
    随机获得一个新小精灵
    {"define" : GET_ONE_POKEMON}{"define" : ACCEPT, "name" : name}{"define" : SERVERE_ERROR}
    6 结构说明6.1 通用6.1.1 Connor_Socket::Socket(socket.h)完成WSA最基本的初始化,加载库文件,以及释放相关资源#define DEFAULT_BUFLEN 10000 // Socket传输缓冲区大小#define CLIENT_PORT 5150 // 客户端连接端口#define SERVER_PORT 2222 // 服务器监听端口 // 单机模式下都指向localhost#define CLIENT_ADDR "127.0.0.1" // 客户端IP地址#define SERVER_ADDR "127.0.0.1" // 服务器IP地址
    6.1.2 Reflector(reflector.h)反射类,维护类名与之对应构造函数的map定义:
    // 定义生成Pokemon对象的函数指针typedef Pokemon* (*PTRCreateObject)(std::string, unsigned int, unsigned int, Attribute, int);// 新建全局函数以new类对象,模拟反射#define REGISTER(_CLASS_NAME_) \_CLASS_NAME_* Create##_CLASS_NAME_(std::string name, unsigned int level,\ unsigned int exp, Attribute attribute, int id) \{ \ return new _CLASS_NAME_(name, level, exp, attribute, id); \} \ \RegisterAction createRegister##_CLASS_NAME_( \ #_CLASS_NAME_, (PTRCreateObject)Create##_CLASS_NAME_)
    成员函数:
    // 使用单例模式,获得Reflector实例// @return:// Reflector唯一对象的引用static Reflector& GetInstance(){ static Reflector instance; return instance;}// 通过类名获得类的构造函数// @param:// className 需要获得的类名// @return:// 生成相应类的函数指针PTRCreateObject GetClassByName(std::string className);// 将类名和构造函数注册到map中// @param:// className 需要注册的类名// method 生成相应类的函数指针void RegistClass(std::string className, PTRCreateObject method);// 隐藏Reflector的构造函数,单例模式只运行使用GetInstance获得Reflector唯一实例Reflector() {}
    成员变量:
    // 类名与之对应构造函数的mapstd::map<std::string, PTRCreateObject> _classMap;
    6.1.3 RegisterAction(reflector.h)注册行为类,通过构造函数以实现自动向Reflector注册成员函数:
    // 将类与回调函数注册到Reflector// @param:// className 类名// PtrCreatFn 创建相应类的回调函数RegisterAction(std::string className,PTRCreateObject PtrCreateFn);
    6.1.4 PokemonFactory(pokemonfactory.h)产生小精灵对象的工厂,通过多态获得小精灵对象成员函数:
    // 产生对战电脑的小精灵实体// @param:// name 对战小精灵的名字// client 与服务器连接的socket指针// @return:// 小精灵对象指针static Pokemon* CreateComputer(std::string name, Connor_Socket::Client *client);// 产生用户的小精灵实体// @param:// str 用户小精灵的序列化数据,包含各种属性// @return:// 小精灵对象指针static Pokemon* CreateUser(std::string str);
    6.1.5 Pokemon(pokemon.h)产生小精灵对象的工厂,通过多态获得小精灵对象定义:
    // 获得对象运行时多态类型// 多编译器支持#ifdef __GNUC__#include <cxxabi.h>#define GET_CLASS_TYPE(_OBJECT_) \ std::string(abi::__cxa_demangle(typeid(_OBJECT_).name(), nullptr, nullptr, nullptr))#elif _MSC_VER && !__INTEL_COMPILER#include <typeinfo>#define GET_CLASS_NAME(_OBJECT_) \ std::string(typeid(_OBJECT_).name())#define GET_CLASS_TYPE(_OBJECT_) \ GET_CLASS_NAME(_OBJECT_).substr(GET_CLASS_NAME(_OBJECT_).find("class ") + 6, \ GET_CLASS_NAME(_OBJECT_).length() - 6)#else#define GET_CLASS_TYPE(_OBJECT_) \ std::string(typeid(_OBJECT_).name())#endif#define CRTICAL_RATE 0.1 // 暴击概率#define MISS_RATE 0.2 // 闪避概率// 小精灵主属性类别enum class Type{ Strength, //高攻击 Tanker, //高生命值 Defensive, //高防御 Swift, //低攻击间隔};// 小精灵属性typedef struct struct_Attribute{ Type type; // 小精灵主属性类型 int attackPoint; // 攻击力 int defensePoint; // 防御力 int healthPoint; // 生命值 int attackFrequence; // 攻击间隔}Attribute;// 经验值列表const int LEVEL_EXP_LIST[15] = {0, 100, 250, 500, 800, 1200, 1800, 2500, 3300, 4500, 6000, 7000, 8000, 9000, 10000};
    成员函数:
    // 各种获取属性函数Type GetType() const { return _attribute.type; }std::string GetName() const { return _name; }int GetLevel() const { return _level; }unsigned long GetExp() const { return _exp; }int GetAttackPoint() const { return _attribute.attackPoint; }int GetHealthPoint() const { return _attribute.healthPoint; }int GetDefensePoint() const { return _attribute.defensePoint; }int GetAttackFrequence() const { return _attribute.attackFrequence; }int GetHp() const { return _hp; }int GetId() const { return _id; }// 判断是否最近一次攻击为暴击bool IsCritical() const { return _critical; }// 小精灵受伤函数// 小精灵真正受到的伤害 = (damage-小精灵的防御值) * 随机系数// 同时有几率闪避// @param:// damage 受到的伤害// @return:// 小精灵是否死亡virtual bool Hurt(int damage);// 小精灵攻击函数// @param:// opPokemon 攻击小精灵的指针// @return:// 攻击造成的伤害virtual int Attack(Pokemon * opPokemon) = 0;// 根据获得的经验增加经验值并自动升级,返回是否升级// @param:// exp 小精灵获得的经验值// @return:// 小精灵是否升级virtual bool Upgrade(int exp);// 小精灵升级时各属性的成长// @param:// master 小精灵主属性virtual void Grow(int *master);// 在攻击、防御、升级时有一定的随机性// @return:// 产生随机的比例系数(0~1)virtual double Bonus();
    成员变量:
    // 精灵名字std::string _name;// 等级int _level;// 经验值unsigned long _exp;// 各种属性Attribute _attribute;// 对战时的实时血量int _hp;// 在数据库中对应的唯一idint _id;// 暂存最近一次攻击是否暴击bool _critical;
    6.2 客户端6.2.1 Connor_Socket::Client(client.h)继承于Socket类,封装winsock,管理socket的发送与接受信息成员函数:
    // 默认构造函数// 委托到带名字的构造函数,传参为UNKOWNClient() : Client("UNKNOWN") { }// 带名字的构造函数,表明与该socket绑定的用户名// @param:// name 用户名Client(string name);// 注册、登陆时使用的函数// 建立与服务器的连接// @param:// requestInfo json序列化后请求信息// @return:// json序列化后的返回信息// @exception:// Server关闭连接/发送失败,抛出 runtime_error;std::string Connect(std::string requestInfo);// 建立连接后使用此函数向服务器发送数据// @param:// requestInfo json序列化后请求信息// @return:// json序列化后的返回信息// @exception:// Server关闭连接/发送失败,抛出 runtime_error;std::string Send(std::string requestInfo);// 关闭与服务器的连接void Close();// 获取该client绑定的用户名// @return:// 用户名std::string GetUserName();
    成员变量:
    // 与服务器通信的socketSOCKET _connectSocket;// 服务器的地址信息SOCKADDR_IN _serverAddr;// 用户名std::string _username;
    6.2.2 Widget(widget.h)登录及注册界面成员函数:
    // 登陆void Login();// 注册void Signup();// 初始化UI界面void InitUi();// 初始化信号槽void InitConnect();
    成员变量:
    // 该widget的ui界面指针Ui::Widget *ui;// 与服务器连接的socket指针Connor_Socket::Client *_client;
    6.2.3 StackWidget(stackwidget.h)管理多个视图的切换成员函数:
    // 刷新用户列表信号void refreshUserList();// 刷新用户背包信号void refreshBag();// 刷新对战界面信号void refreshFight();// 设置当前显示的视图// index = 0 游戏主界面// index = 1 用户列表界面// index = 2 抓取界面// index = 3 用户背包界面// index = 4 对战选择界面// @param:// index 视图的编号void SetCurrentIndex(int index);// 返回到主界面void BackToLobby();// 初始化UI界面void InitUi();// 初始化信号槽void InitConnect();
    成员变量:
    // 该widget的ui界面指针Ui::StackWidget *ui;// 与服务器连接的socket指针Connor_Socket::Client *_client;// 管理多个视图切换的stacklayoutQStackedLayout *_stackLayout;// 各个分视图指针GameLobby *_gameLobby;UserListWidget *_userListWidget;BagWidget *_bagWidget;FightWidget *_fightWidget;ScratchWidget *_scratchWidget;
    6.2.4 GameLobby(gamelobby.h)游戏主界面,包括进入四个功能界面的入口成员函数:
    // 鼠标点击在相应的区域信号void clicked(int type);// 关闭程序信号void closeAll();// 登出void LogOut();// 重载鼠标点击事件,对四个不规则区域监测鼠标点击事件void mousePressEvent(QMouseEvent *event);// 初始化UI界面void InitUi();// 初始化信号槽void InitConnect();
    6.2.5 UserListWidget(userlistwidget.h)用户列表界面,包括查看在线用户和所有用户的背包及胜率成员函数:
    // 返回到主界面信号void back();// 设置用户列表界面的内容void SetUserList();// 显示背包内容// @param:// username 要查看背包的所属用户void ShowBag(QString username);// 初始化UI界面void InitUi();// 初始化信号槽void InitConnect();
    成员变量:
    // 该widget的ui界面指针Ui::UserListWidget *ui;// 与服务器连接的socket指针Connor_Socket::Client *_client;// 在选择用户背包按钮的mapperQSignalMapper *_signalMapper;
    6.2.6 ScratchWidget(scratchwidget.h)抓取界面,点击精灵球有机率随机获得小精灵成员函数:
    // 返回到主界面信号void back();// 重载事件过滤器// 当鼠标移动到精灵球上对其实现震动动画bool eventFilter(QObject *watched, QEvent *event);// 初始化UI界面void InitUi();// 初始化信号槽void InitConnect();// 该widget的ui界面指针Ui::ScratchWidget *ui;// 与服务器连接的socket指针Connor_Socket::Client *_client;
    成员变量:
    // 该widget的ui界面指针Ui::ScratchWidget *ui;// 与服务器连接的socket指针Connor_Socket::Client *_client;
    6.2.7 BagWidget(bagwidget.h)用户背包界面,包括查看背包内小精灵属性和用户胜率及成就的显示成员函数:
    // 返回到主界面信号void back();// 设置背包界面的内容void SetBag();// 初始化UI界面void InitUi();// 初始化信号槽void InitConnect();
    成员变量:
    // 该widget的ui界面指针Ui::BagWidget *ui;// 与服务器连接的socket指针Connor_Socket::Client *_client;
    6.2.8 FightWidget(fightwidget.h)对战选择界面,包括选择用户精灵和对战的电脑精灵成员函数:
    // 返回到主界面信号void back();// 设置背包界面的内容void SetBag();// 对战开始,构建fightroom// @param:// isLose 对战失败用户是否会损失小精灵void FightBegin(bool isLose);// 重载事件过滤器,监测鼠标对背包小精灵label的点击bool eventFilter(QObject *watched, QEvent *event);
    成员变量:
    // 该widget的ui界面指针Ui::FightWidget *ui;// 与服务器连接的socket指针Connor_Socket::Client *_client;// 父对象指针QWidget *_parent;// 选中出战的小精灵QObject *_select;
    6.2.9 FightRoom(fightroom.h)实时对战界面,包括小精灵打斗动画和血量显示以及实时计算成员函数:
    // 开始对战void Fight();// 重载关闭事件void FightRoom::closeEvent(QCloseEvent *event);// 小精灵普通攻击信号void attack(QLabel *, QLabel *);// 小精灵特殊攻击信号void attack_SP(std::pair<Pokemon *, QLabel *> *, std::pair<Pokemon *, QLabel *> *);// 游戏结束信号void over(Pokemon *);// 小精灵受伤害信号void hurt(QLabel *attacker, QLabel *suffer);// 窗口关闭信号void isClosed();// 清除掉血文字信号void clearText();// 隐藏招式标签信号void hideLabel();// 设置普通攻击动画// @param:// attacker GUI中攻击方的Label指针// suffer GUI中被攻击方的Label指针void setAnimation(QLabel *attacker, QLabel *suffer);// 设置特殊攻击动画// @param:// attacker 攻击方的小精灵对象指针和GUI中的显示Label// suffer 被攻击方的小精灵对象指针和GUI中的显示Labelvoid setAnimation_SP(std::pair<Pokemon *, QLabel *> *attacker, std::pair<Pokemon *, QLabel *> *suffer);// 对战完成// @param:// winner 胜利小精灵的对象指针void GameComplete(Pokemon *winner);// 更新双方血量// @param:// attacker GUI中攻击方的Label指针// suffer GUI中被攻击方的Label指针void UpdateHp(QLabel *attacker, QLabel *suffer);// 设置掉血数值void setText();// 设置招式Labelvoid setLabel();// 选择损失的小精灵void Choose(int);// 初始化UI界面void InitUi();// 初始化信号槽void InitConnect();
    成员变量:
    // 该widget的ui界面指针Ui::FightRoom *ui;// 与服务器连接的socket指针Connor_Socket::Client *_client;// 对战用户方的小精灵对象指针和GUI中的显示Labelstd::pair<Pokemon *, QLabel *> _fighter;// 对战电脑方的小精灵对象指针和GUI中的显示Labelstd::pair<Pokemon *, QLabel *> _againster;// 该场对战用户失败是否会损失小精灵bool _isLose;// 在选择损失小精灵所用的信号mapperQSignalMapper *_signalMapper;// 标识用户在对战中是否中途退出bool _quit;
    6.3 服务器6.3.1 Connor_Socket::Server(server.h)继承于Socket类,封装winsock,管理socket的发送与接受信息成员函数:
    // 构造函数,打开监听接口等待请求Server();// 查询用户是否在线// @param:// username 需要查询的用户名// connection 与该用户名绑定的socket// @return:// 是否在线bool Online(std::string username, SOCKET connection);// 将某用户从在线列表移除// @param:// username 需要移除的用户名void Offline(std::string username);// 获得在线用户列表// @return:// 装载有所有在线用户名的liststd::list<std::string> GetOnlineList();
    成员变量:
    // 监听客户端访问的socketSOCKET _listeningSocket;// 地址信息SOCKADDR_IN _serverAddr;// 持有与各个客户端保持通信的线程std::vector<std::thread> _socketThreads;// 持有用户名相对应的socket链接std::unordered_map<std::string, SOCKET> _sockets;// 连接到服务器的客户端数size_t _count;
    6.3.2 Dispatch(dispatch.h)继承于Socket类,封装winsock,管理socket的发送与接受信息成员函数:
    // 传入SOCKET和Server的构造函数// @param:// connection 与相应客户端建立的socket连接// parent server对象指针Dispatcher(SOCKET &connection, Connor_Socket::Server *parent);// 根据请求信息,分发到相应的函数处理请求// @param:// requestInfo json序列化后请求信息// @return:// json序列化后的返回信息std::string Dispatch(json requestInfo);// 登陆处理逻辑json LoginHandle(json&);// 注册处理逻辑json SignupHandle(json&);// 获取在线用户列表处理逻辑json OnlineListHandle(json &);// 查看用户背包处理逻辑json UserBagHandle(json &);// 获取离线用户列表处理逻辑json OfflineListHandle(json &);// 获取用户成就逻辑json UserAchievementHandle(json &);// 获取对战精灵列表处理逻辑json PokemonListHandle(json &);// 获取对战精灵信息处理逻辑json PokemonInfoHandle(json &);// 对战胜利处理逻辑json GameWinHandle(json &);// 对战失败处理逻辑json GameLoseHandle(json &);// 损失小精灵处理逻辑json LosePokemonHandle(json &);// 为用户随机分发一个宠物小精灵// @param:// username 获得小精灵的用户名// @return:// 获得小精灵的名字std::string DispatchPokemon(std::string username);// 与该Dispatch绑定用户登出void Logout();// 获取Dispatcher的内部状态int getState();
    成员变量:
    // 代表用户处于什么状态,如在线空闲或对战int _state;// 与dispatcher绑定的socket连接SOCKET _connection;// 与socket连接绑定的用户名std::string _username;// server指针,以访问server维护的在线列表Connor_Socket::Server *_parent;
    3 评论 15 下载 2018-11-06 18:59:59 下载需要8点积分
  • 基于Bootstrap框架和SSH框架实现的旅游自助系统网站APP

    1 需求分析1.1 五类地方旅游类App下载量分布图从下载量来看,交通出行类App显然是最受用户欢迎的,在数量少于景点攻略类App的情况下,交通出行类App总下载量为前者2.43倍,占总下载量的70%。六大平台中仅有应用宝上,交通出行类App总下载量低于景点攻略类App。

    1.2 六大商店地方旅游类App下载量分布图六大商店地方旅游类App下载量显示,在百度手机助手上线的地方旅游类App总计下载量为8912449次,明显领先360助手、安卓市场、安智市场、豌豆荚、应用宝五家商店。

    2 可行性分析经济可行性

    服务器:Linux系统的阿里云服务器
    PC机:开发电脑3台
    数据库:MySQL
    建模工具:Rotational Rose 2003

    技术可行性

    版本控制系统:SVN
    前端:Bootstrap框架、Html5+CSS3、JavaScript、Ajax
    后台:Struts2框架、Hibernate框架、Spring框架
    设计模式:拟采用单例模式、适配器和外观模式
    算法:AES加密算法、MD5加密算法、路径规划算法

    社会因素可行性分析

    所有软件都选用正版
    所有技术资料都由提出方保管
    合同制定确定违约责任
    用户使用可行性分析

    3 分析图用例建模


    流程图
    系统分析
    时序图
    E-R图



    数据库设计
    四、关键技术
    版本控制系统:SVN
    前端:Bootstrap框架、Html5+CSS3、JavaScript、Ajax
    后台:Struts2框架、Hibernate框架、Spring框架
    设计模式:拟采用单例模式、适配器和外观模式
    算法:AES加密算法、MD5加密算法、路径规划算法



    五、模块测试景点和旅游路线的查询,支持模糊查询



    预定旅游服务,用户登录注册之后就可以预定旅游服务

    后台管理系统,旅游公司的管理员可以进行接收旅游服务订票操作


    旅游公司管理员可以新开旅游路线

    维护旅游公司的旅游路线

    开发景点,支持图片上传,带Jquery的城市选择器

    进行景点维护,采用UEditor富文本编辑器框架进行编辑,可以将样式写入数据库
    1 评论 13 下载 2018-11-28 11:36:15 下载需要11点积分
显示 0 到 15 ,共 15 条
eject