分类

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

资源列表

  • 基于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





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


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


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





    五、数据库设计(试卷管理系统)
    14 评论 319 下载 2018-10-05 22:46:51 下载需要15点积分
  • 基于JSP的停车场信息管理系统设计与实现

    1 引言1.1 项目背景软件系统的名称是停车场管理系统。我们开发的系统将帮助停车场管理员和物业公司更加智能化的管理停车场,省去很多的人力物力。方便随时查询停车场的情况,也有助于车主方便随时查找附近可以停车的停车场。
    1.1.1用户基本情况介绍
    角色1:停车场管理员
    查看剩余车位数量,状态
    记录车牌号,出入时间,收钱
    查看停车出入记录
    修改停车位类型(临时车位或永久车位,当有业主购买车位的情况下,记录购买的基本信息,停车位的年限等)
    角色2:系统管理员
    增加停车场数量,因为不止有一个停车场,设置停车场的相关信息
    增加停车场管理员数量
    角色3:用户(车主)手机端
    用户可以查看停车场的停车位信息,以及其他停车场的停车位信息(用户除了可以停在自己小区已购买的停车位,还可以停在周围小区的临时收费停车位)

    1.1.2 项目开发目标停车管理系统能够对对进出停车场的车辆进行科学有效的实时管理,通过过网络和服务中心服务器相联,进行数据共享。停车场系统管理软件可方便地完成管理临时车位、长期占有车位、随时查询停车场情况、修改停车位信息、给更多的停车场提供接口等功能。
    自动统计车辆进出数量,在每个入口处设置显示牌显示该区车辆统计。各停车场系统之间应能进行信号传输,方便各个车主查询和物业公司进行管理。
    1.1.3 用户组织结构
    1.1.4 用户相关业务
    停车场管理员
    记录车的出入信息
    查看停车历史记录
    管理车主买车位的信息
    系统管理员
    管理停车场的属性信息
    管理停车场管理员信息
    用户(安卓)
    查看停车位信息

    1.2 业务对象说明及术语定义
    进库、进场:指车辆进入停车场。
    出库、出场:指车辆驶离停车场。
    车主:指拥有车辆、购买停车位的本小区业主,不是指外来临时停靠的司机。
    车位类型:分固定车位和临时车位,固定车位指已经被业主购买的车位,临时车位指没有被业主购买,可供外来车辆临时停车的车位。

    2 任务概述2.1 目标具有操作简单、使用方便、功能先进等特点,停车场使用者可以在最短的时间进入或离开停车场,从而提高停车场的管理水平,取得更高的经济效益和良好的社会效益。它一方面以智能化设备和完善的管理软件来简化人的劳动,实现停车场车辆进入、场内监控以信息化管理;另一方面通过网络化管理实现能够在一个相对广阔的地域内(例如一个城市)了解多个停车场情况,附近停车场的空车位数。
    2.2 运行环境2.2.1 网络及硬件环境一台联网的pc 和一个安卓手机
    2.2.2 支持软件环境该系统为B/S三层结构,它的运行环境分客户端、应用服务器端和数据库服务器端三部分。

    客户端
    操作系统:Windows7或更新版本。 浏览器:IE8以上,其它常见浏览器如FireFox。
    应用服务器端
    操作系统:Windows7或更新版本。
    应用服务器:Tomcat 7或更新版本。
    数据库访问:JDBC。
    数据库服务器端
    操作系统:Windows7或更新版本。 数据库系统:SQL Server 2008 r2
    Android端
    Android4.4版本或以上

    2.3 条件与限制要求用户具有简单的计算机使用知识,系统暂时无法提供收费管理功能
    3 功能需求3.1 总体功能需求停车场管理系统主要有管理车辆进场出场功能、记录查询功能等。停车场车位划分为固定停车位和临时停车位。满足业主拥有固定停车位和周围散客停车的要求。给不同类型的用户赋予不同的权限管理停车场。主要能管理车辆进场入场、查询历史记录、查询当前停车信息(如空余车位量等)。
    3.2 功能划分根据系统的需求分析,将系统设计的功能分为三大模块:车辆进出管理模块、信息查询模块和系统管理模块。

    停车场管理:车辆入场、车辆出场
    车辆进入停车场时,系统管理员记录车辆的车牌号码和自动获取系统时间作为进入时间。车辆离开停车场时,根据车辆车牌号码判断是否为固定车位车辆来决定是否收费。所有进出停车场的信息(包括车牌号、进入时间、离开时间)都记入一个进出记录表以备查询和统计使用。
    信息查询:某时间段的出入场信息,当前在场信息,车辆历史停车记录
    系统的查询功能可以查询包括自由车位空闲数目、自由车位停车情况、固定车位使用情况、固定车位车主信息、自由车位使用率等多种信息。将自由车位空闲数目显示在停车场入口处,可以提示即将进入停车场的车主;如果自由车位已满,更可以给出指示,并不允许继续进行车辆进入自由车位停车场的操作。
    信息维护:用户及停车位续费等
    查询模块包括自由车位空闲数目指示、固定车位停车情况查询、固定车位车主信息查询、自由车位停车情况查询,指定车辆进出记录查询、系统初始化功能。
    系统管理:车位信息
    进出记录表中记录了包括固定车位车辆和自由车位车辆的所有进出信息,每车每次离开停车场时增加一条记录,非常方便日后查询和统计工作的需要。

    将停车场划分为固定车位和自由车位两部分。固定车位又可以称为专用车位或内部车位,它的特点是使用者固定,交费采用包月制或包年制,平时进出停车场时不再交费。对于固定车位的车辆,系统有着详细的信息记录,包括车辆信息和车主信息。自由车位又可以称为公用车位或公共车位,它的特点是使用者不固定,针对临时性散客服务,车辆每次出停车场时,根据停车时间和停车费率交纳停车费用。固定车位的车辆总是停放在自己的车位上,而不停放在自由车位上。不同类型停车场的固定车位和自由车位数目比例是不同的,,系统可以在系统管理功能里对这两类车位的数目进行设定和修改。
    系统包含三类用户:系统管理员、停车场管理员和普通用户。

    系统管理员能够对停车场和停车场管理员实现信息管理,包括开放对更多停车场的接口,管理各个停车场管理员等。
    停车场管理员可以查看剩余停车位信息,查看以前的停车记录,对车辆的入库出库信息进行管理,以及对于车主购买停车位的信息管理,车主购买停车位的信息管理基本包括信息的增删改查。
    普通用户能够通过手机端查看剩余车位信息。

    3.3 功能需求1系统管理员能够对停车场和停车场管理员实现信息管理,包括开放对更多停车场的接口,管理各个停车场管理员等。
    3.3.1 用例描述
    3.3.2 数据概念结构图
    3.3.3 系统业务流程图
    3.4 功能需求2停车场管理员由可以查看剩余停车位信息,查看以前的停车记录,对车辆的入库出库信息进行管理,以及对于车主购买停车位的信息管理,车主购买停车位的信息管理基本包括信息的增删改查。
    3.4.1 用例描述停车场管理员用例图

    3.4.2 数据概念结构图
    3.4.3 系统业务流程图
    3.5 功能需求3普通用户的定位在于私家车主,只需要能够在手机上查看到指定的停车场有没有剩余的停车位信息即可。
    3.5.1 用例描述
    3.5.2 数据概念结构图
    3.5.3 系统业务流程图
    4 性能需求4.1 数据精确度


    数据
    要求




    车牌号
    格式长度正确


    离开、到达时间
    精确到分钟


    手机号
    11位数


    停车场地址
    精确到道路的哪一号



    5 运行需求5.1 安全性权限控制根据不同用户角色,设置相应权限,用户的重要操作都做相应的日志记录以备查看,没有权限的用户禁止使用系统。只有该停车场管理员能对该停车场进行操作。系统管理员才能新增停车场管理员和开放对其他停车场的接口。
    重要数据加密本系统对一些重要的数据按一定的算法进行加密,如用户口令、重要参数等。
    数据备份允许用户进行数据的备份和恢复,以弥补数据的破坏和丢失。
    记录日志本系统应该能够记录系统运行时所发生的所有错误,包括本机错误和网络错误。这些错误记录便于查找错误的原因。日志同时记录用户的关键性操作信息。
    5.2 用户界面
    屏幕尺寸387mm*259mm手机端建议使用5.2寸或以上屏幕
    5.3 接口要求5.3.1 硬件接口
    服务器端建议使用专用服务器
    5.3.2 通信接口
    http协议
    6 系统结构分析6.1 系统静态结构关系分析说明
    其中的类包括:

    普通用户类:具有车牌号属性,完成用户的查询空车位行为。停车场管理员类:具有管理员工号,电话,身份证号,年龄等基本信息,完成查询剩余停车位信息,查看停车记录,记录车辆出入信息,管理车主购买停车位信息等行为。系统管理员类:具有用户名和密码属性,完成停车场信息管理,停车场管理员信息管理行为。车位信息类,停车场信息类,车主购买车位类,车辆进出场信息管理类(车辆进场信息类,车辆出场信息类)。
    6.2 系统体系结构分析说明

    用户查看剩余停车位信息管理包括普通用户查看指定停车场的剩余停车位信息;停车场管理员信息管理包括查看、增加、删除、修改停车场管理员信息的界面类,控制类以及停车场管理员信息实体类。停车场信息管理包括查看、增加、删除、修改停车场信息的界面类,控制类以及停车场信息实体类。车辆出入信息管理包括包含记录车辆的出入场时间,车牌号等信息。车主购买停车位信息管理包括查看、增加、删除、修改车主购买停车位的界面类,控制类以及车主信息以及停车位信息的实体类。
    6.3 系统部署分析说明
    7 系统功能行为分析7.1 系统业务流程说明系统管理员活动图
    系统管理员的主要活动基本为停车场信息管理和停车场管理员的信息管理活动,包括每种信息的查看,增加,删除和修改活动。

    停车场管理员活动图
    停车场管理员由于对系统操作较多,所以活动也较多,包括查看剩余停车位信息,查看以前的停车记录,对车辆的入库出库信息进行管理,以及对于车主购买停车位的信息管理,车主购买停车位的信息管理基本包括信息的增删改查。

    普通用户活动图
    普通用户的定位在于私家车主,只需要能够在手机上查看到指定的停车场有没有剩余的停车位信息即可,所以活动只有一个。

    7.2 系统交互说明因为系统管理员对于停车场信息管理和停车场管理员的管理流程基本相同,所以这里只写明系统管理员对于停车场信息的管理时序图,对于停车场管理员的流程基本相同。
    系统管理员查看停车场信息时序图

    系统管理员删除停车场信息时序图

    系统管理员修改停车场信息时序图

    系统管理员增加停车场信息时序图

    停车场管理员查看剩余停车位信息

    停车场管理员记录车辆入库信息

    停车场管理员记录车辆出库信息

    停车场管理员查看停车记录

    停车场管理员查看车主购买车位信息

    停车场管理员修改车主购买车位信息

    停车场管理员增加车主购买车位信息

    普通用户查看停车场剩余车位信息

    停车场管理员删除车主购买车位信息

    7.3 系统对象状态演化说明系统管理员主要状态图
    系统管理员主要进行停车场管理员和停车场信息的管理操作,所以主要的状态即为对于停车场和停车场管理员的操作状态。

    停车场管理员主要状态图
    停车场管理员在系统当中功能较多,主要功能涉及查看停车场的剩余停车位信息,查看停车的历史记录,对车辆的出入库信息进行管理,以及对车主购买停车位的信息管理,所以主要状态即为查看信息以及对信息进行管理操作。

    普通用户主要状态图

    8 系统展示登陆主界面

    系统管理员登录主界面

    查看管理员信息界面

    查看停车场信息界面

    添加停车场管理员信息界面

    修改停车场管理员信息界面

    查看出入信息界面

    添加停车记录信息界面

    查看停车记录备份界面

    手机端的查询界面

    手机端的显示界面
    10 评论 251 下载 2018-10-05 22:27:42 下载需要13点积分
  • 基于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 功能截图校园论坛主页

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

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

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

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

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

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

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

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

    点击右上角的“退出论坛”按钮,即退出登录状态,回到论坛主页。
    16 评论 457 下载 2018-10-05 22:38:31 下载需要15点积分
  • 基于HTML5实现的消灭星星小游戏

    1 游戏介绍「消灭星星」是一款很经典的「消除类游戏」,它的玩法很简单:消除相连通的同色砖块。

    「消灭星星」存在多个版本,不过它们的规则除了「关卡分值」有些出入外,其它的规则都是一样的。笔者介绍的版本的游戏规则整理如下:
    1. 色砖分布

    10 x 10 的表格
    5种颜色 ——— 红、绿、蓝,黄,紫
    每类色砖个数在指定区间内随机
    5类色砖在 10 x 10 表格中随机分布

    2. 消除规则
    两个或两个以上同色砖块相连通即是可被消除的砖块。
    3. 分值规则

    消除总分值 = n * n * 5
    奖励总分值 = 2000 - n * n * 20

    「n」表示砖块数量。上面是「总」分值的规则,还有「单」个砖块的分值规则:

    消除砖块得分值 = 10 * i + 5
    剩余砖块扣分值 = 40 * i + 20

    「i」表示砖块的索引值(从 0 开始)。简单地说,单个砖块「得分值」和「扣分值」是一个等差数列。
    4. 关卡分值
    关卡分值 = 1000 + (level - 1) * 2000;「level」即当前关卡数。
    5. 通关条件

    可消除色块不存在
    累计分值 >= 当前关卡分值

    上面两个条件同时成立游戏才可以通关。
    2 MVC 设计模式笔者这次又是使用了 MVC 模式来写「消灭星星」。星星「砖块」的数据结构与各种状态由 Model 实现,游戏的核心在 Model 中完成;View 映射 Model 的变化并做出对应的行为,它的任务主要是展示动画;用户与游戏的交互由 Control 完成。
    从逻辑规划上看,Model 很重而View 与 Control 很轻,不过,从代码量上看,View 很重而 Model 与 Control 相对很轻。
    2.1 Model10 x 10 的表格用长度为 100 的数组可完美映射游戏的星星「砖块」。
    [ R, R, G, G, B, B, Y, Y, P, P, R, R, G, G, B, B, Y, Y, P, P, R, R, G, G, B, B, Y, Y, P, P, R, R, G, G, B, B, Y, Y, P, P, R, R, G, G, B, B, Y, Y, P, P, R, R, G, G, B, B, Y, Y, P, P, R, R, G, G, B, B, Y, Y, P, P, R, R, G, G, B, B, Y, Y, P, P, R, R, G, G, B, B, Y, Y, P, P, R, R, G, G, B, B, Y, Y, P, P]
    R - 红色,G - 绿色,B - 蓝色,Y - 黄色,P - 紫色。Model 的核心任务是以下四个:

    生成砖墙
    消除砖块 (生成砖块分值)
    夯实砖墙
    清除残砖 (生成奖励分值)

    2.1.1 生成砖墙砖墙分两步生成:

    色砖数量分配
    打散色砖

    理论上,可以将 100 个格子可以均分到 5 类颜色,不过笔者玩过的「消灭星星」都不使用均分策略。通过分析几款「消灭星星」,其实可以发现一个规律 ——— 「色砖之间的数量差在一个固定的区间内」。
    如果把传统意义上的均分称作「完全均分」,那么「消灭星星」的分配是一种在均分线上下波动的「不完全均分」。

    笔者把上面的「不完全均分」称作「波动均分」,算法的具体实现可以参见「波动均分算法」。
    「打散色砖」其实就是将数组乱序的过程,笔者推荐使用「 费雪耶兹乱序算法」。
    以下是伪代码的实现:
    // 波动均分色砖waveaverage(5, 4, 4).forEach( // tiles 即色墙数组 (count, clr) => tiles.concat(generateTiles(count, clr)); ); // 打散色砖shuffle(tiles);
    2.1.2 消除砖块「消除砖块」的规则很简单 ——— 相邻相连通相同色即可以消除。

    前两个组合符合「相邻相连通相同色即可以消除」,所以它们可以被消除;第三个组合虽然「相邻相同色」但是不「相连通」所以它不能被消除。
    「消除砖块」的同时有一个重要的任务:生成砖块对应的分值。在「游戏规则」中,笔者已经提供了对应的数学公式:「消除砖块得分值 = 10 * i + 5」。
    「消除砖块」算法实现如下:
    function clean(tile) { let count = 1; let sameTiles = searchSameTiles(tile); if(sameTiles.length > 0) { deleteTile(tile); while(true) { let nextSameTiles = []; sameTiles.forEach(tile => { nextSameTiles.push(...searchSameTiles(tile)); makeScore(++count * 10 + 5); // 标记当前分值 deleteTile(tile); // 删除砖块 }); // 清除完成,跳出循环 if(nextSameTiles.length === 0) break; else { sameTiles = nextSameTiles; } } }}
    清除的算法使用「递归」逻辑上会清晰一些,不过「递归」在浏览器上容易「栈溢出」,所以笔者没有使用「递归」实现。
    2.1.3 夯实砖墙砖墙在消除了部分砖块后,会出现空洞,此时需要对墙体进行夯实:










    向下夯实
    向左夯实
    向左下夯实(先下后左)



    一种快速的实现方案是,每次「消除砖块」后直接遍历砖墙数组(10x10数组)再把空洞夯实,伪代码表示如下:
    for(let row = 0; row < 10; ++row) { for(let col = 0; col < 10; ++col) { if(isEmpty(row, col)) { // 水平方向(向左)夯实 if(isEmptyCol(col)) { tampRow(col); } // 垂直方向(向下)夯实 else { tampCol(col); } break; } }}
    但是,为了夯实一个空洞对一张大数组进行全量遍历并不是一种高效的算法。在笔者看来影响「墙体夯实」效率的因素有:

    定位空洞
    砖块移动(夯实)

    扫描墙体数组的主要目的是「定位空洞」,但能否不扫描墙体数组直接「定位空洞」?
    墙体的「空洞」是由于「消除砖块」造成的,换种说法 ——— 被消除的砖块留下来的坑位就是墙体的空洞。在「消除砖块」的同时标记空洞的位置,这样就无须全量扫描墙体数组,伪代码如下:
    function deleteTile(tile) { // 标记空洞 markHollow(tile.index); // 删除砖块逻辑 ...}
    在上面的夯实动图,其实可以看到它的夯实过程如下:

    空洞上方的砖块向下移动
    空列右侧的砖块向左移动

    墙体在「夯实」过程中,它的边界是实时在变化,如果「夯实」不按真实边界进行扫描,会产生多余的空白扫描:

    如何记录墙体的边界?
    把墙体拆分成一个个单独的列,那么列最顶部的空白格片段就是墙体的「空白」,而其余非顶部的空白格片段即墙体的「空洞」。

    笔者使用一组「列集合」来描述墙体的边界并记录墙体的空洞,它的模型如下:
    /* @ count - 列砖块数 @ start - 顶部行索引 @ end - 底部行索引 @ pitCount - 坑数 @ topPit - 最顶部的坑 @ bottomPit - 最底部的坑*/ let wall = [ {count, start, end, pitCount, topPit, bottomPit}, {count, start, end, pitCount, topPit, bottomPit}, ...];
    这个模型可以描述墙体的三个细节:

    空列
    列的连续空洞
    列的非连续空洞

    // 空列if(count === 0) { ...}// 连续空洞else if(bottomPit - topPit + 1 === pitCount) { ...}// 非连续空洞else { ...}
    砖块在消除后,映射到单个列上的空洞会有两种分布形态 ——— 连续与非连续。

    「连续空洞」与「非连续空洞」的夯实过程如下:

    其实「空列」放大于墙体上,也会有「空洞」类似的分布形态 ——— 连续与非连续。

    它的夯实过程与空洞类似,这里就不赘述了。
    2.1.4 消除残砖上一小节提到了「描述墙体的边界并记录墙体的空洞」的「列集合」,笔者是直接使用这个「列集合」来消除残砖的,伪代码如下:
    function clearAll() { let count = 0; for(let col = 0, len = this.wall.length; col < len; ++col) { let colInfo = this.wall[col]; for(let row = colInfo.start; row <= colInfo.end; ++row) { let tile = this.grid[row * this.col + col]; tile.score = -20 - 40 * count++; // 标记奖励分数 tile.removed = true; } }}
    2.2 ViewView 主要的功能有两个:

    UI 管理
    映射 Model 的变化(动画)

    UI 管理主要是指「界面绘制」与「资源加载管理」,这两项功能比较常见本文就直接略过了。View 的重头戏是「映射 Model 的变化」并完成对应的动画。动画是复杂的,而映射的原理是简单的,如下伪代码:
    update({originIndex, index, clr, removed, score}) { // 还没有 originIndex 或没有色值,直接不处理 if(originIndex === undefined || clr === undefined) return ; let tile = this.tiles[originIndex]; // tile 存在,判断颜色是否一样 if(tile.clr !== clr) { this.updateTileClr(tile, clr); } // 当前索引变化 ----- 表示位置也有变化 if(tile.index !== index) { this.updateTileIndex(tile, index); } // 设置分数 if(tile.score !== score) { tile.score = score; } if(tile.removed !== removed) { // 移除或添加当前节点 true === removed ? this.bomb(tile) : this.area.addChild(tile.sprite); tile.removed = removed; }}
    Model 的砖块每次数据的更改都会通知到 View 的砖块,View 会根据对应的变化做对应的动作(动画)。
    2.3 ControlControl 要处理的事务比较多,如下:

    绑定 Model & View
    生成通关分值
    判断通关条件
    对外事件
    用户交互

    初始化时,Control 把 Model 的砖块单向绑定到 View 的砖块了。如下:
    Object.defineProperties(model.tile, { originIndex: { get: () => {...}, set: () => { ... view.update({originIndex}); } }, index: { get: () => {...}, set: () => { ... view.update({index}); } }, clr: { get: () => {...}, set: () => { ... view.update({clr}); } }, removed: { get: () => {...}, set: () => { ... view.update({removed}); } }, score: { get: () => {...}, set: () => { ... view.update({score}); } }, });
    「通关分值」与「判断通关条件」这对逻辑在本文的「游戏规则」中有相关介绍,这里不再赘述。
    对外事件规划如下:



    name
    detail




    pass
    通关


    pause
    暂停


    resume
    恢复


    gameover
    游戏结束



    用户交互 APIs 规划如下:



    name
    type
    deltail




    init
    method
    初始化游戏


    next
    method
    进入下一关


    enter
    method
    进入指定关卡


    pause
    method
    暂停


    resume
    method
    恢复


    destroy
    method
    销毁游戏



    3 问题在知乎有一个关于「消灭星星」的话题:popstar关卡是如何设计的?
    这个话题在最后提出了一个问题 ——— 「无法消除和最大得分不满足过关条件的矩阵」。

    「无法消除的矩阵」其实就是最大得分为0的矩阵,本质上是「最大得分不满足过关条件的矩阵」。
    最大得分不满足过关条件的矩阵
    求「矩阵」的最大得分是一个 「背包问题」,求解的算法不难:对当前矩阵用「递归」的形式把所有的消灭分支都执行一次,并取最高分值。但是 javascript 的「递归」极易「栈溢出」导致算法无法执行。
    其实在知乎的话题中提到一个解决方案:

    网上查到有程序提出做个工具随机生成关卡,自动计算,把符合得分条件的关卡筛选出来

    这个解决方案代价是昂贵的!笔者提供有源码并没有解决这个问题,而是用一个比较取巧的方法:进入游戏前检查是事为「无法消除矩阵」,如果是重新生成关卡矩阵。
    注意:笔者使用的取巧方案并没有解决问题。
    4 评论 28 下载 2018-10-31 18:07:28 下载需要10点积分
  • 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处的程序如何运行。
    9 评论 90 下载 2018-11-24 17:15:31 下载需要12点积分
  • 基于python的中文聊天机器人

    前言发布这篇 Chat 的初衷是想和各位一起分享一下动手来做聊天机器人的乐趣,因此本篇文章适合用于深度机器学习的研究和兴趣发展,因为从工业应用的角度来看使用百度、科大讯飞的 API 接口会更加的适合。在这篇文章中,希望和大家一起共同交流和探索动手实践的乐趣,当然也欢迎大神来做深度的探讨以及吐槽。这篇 Chat 的基础源代码来自互联网,我进行了综合优化和部分代码的重写,我也会在这边文章发布的同时将所有源代码上传到 Git 分享出来,这样在文章中我就不占用篇幅贴出全部的源代码,大家可以从 Git 上 pull 下来对照着文章来看。
    一、系统设计思路和框架本次系统全部使用 Python 编写,在系统设计上遵循着配置灵活、代码模块化的思路,分为数据预处理器、数据处理器、执行器、深度学习模型、可视化展示五个模块。模块间的逻辑关系大致为:数据预处理是将原始语料进行初步的处理以满足于数据处理模块的要求;执行器是整个系统引擎分别在运转的时候调用数据处理器、深度学习模型进行数据处理、模型训练、模型运作等工作;深度学习模型是一个基于TF的seq2seq模型,用于定义神经网络并进行模型计算;可视化展示是一个用Flask前端框架写的简单的人机交互程序,在运行时调用执行器进行人机对话。整体的框架图如下:

    本系统利用seq2seq模型的特点,结合word2vec的思路(当然这样做有点简单粗暴,没有进行分词),将训练语料分为Ask语料集和Replay语料集,并根据一定的比例分为训练语料集和验证语料集,然后就是word2vec。这些步骤都是在数据预处理器和数据处理器中完成的,关于训练语料集与验证语料集的数量都是做成可外部配置的,这也是这个系统我认为设计的最合理的地方(可惜不是我设计的)。在完成数据的处理后,执行器就会根据训练模式(这也是外部可配置的)来调用seq2seq进行神经网络的创建,神经网络的超参数同样也是可以外部进行配置的。在进行训练过程中,使用perprelixy来计算模型的loss,通过自动的调整learning rate来逐步的取得最优值,当learning rate减少为0达到最优值。最后,就是可视化展示模块启动一个进程调用执行器来实时在线提供聊天服务,在语句输入和输出利用seq2seq的特点,直接将输入seq转换成vec作为已经训练好的神经网络,然后神经网络会生成一个seq向量,然后通过查询词典的方式将生成的向量替换成中文句子。在神经网络模型这里,TF有GRU和LSTM模型可供选择,我比较了GRU和LSTM的训练效果,发现还是GRU比较适合对话语句场景的训练,这点在后面的代码分析环节会详细解释。
    二、源码结构
    datautls.py(数据预处理器)包含的函数convertseq2seq_files,将主程序分好的ask语料集和response语料集转换成seq2seq文件,以便数据处理器进行进一步的数据处理。对于整个系统,一般该数据预处理器只需要运行一次即可
    prepareData.py(数据处理器)包含的函数:createvocabulary、converttovector、preparecustomdata、 basictokenizer、initialize_vocabulary,这些函数的作用分别是创建字典、句子转向量、根据超参定制化训练数据、基础数据标记、初始化词典
    execute.py(执行器)包含的函数:getconfig、readdata、createmodel、train、 selftest、initsession、decodeline,这些函数的作用分别是获取配置参数和超参、读取数据、创建模型、训练模型、模式测试、初始化会话、在线对话。
    seq2seqmodel.py(深度机器学习模型)包含的函数:init、sampledloss、seq2seqf、step、getbatch,这些函数的作用分别是程序初始化、loss函数、seq2seq函数、拟合模型、获取批量数据
    app.py(可视化展示模块)包含的函数: heartbeat、reply、index,这些函数的作用分别是心跳、在线对话、主页入口

    三、源码讲解3.1 数据预处理器(data_utls.py)首先在代码的头部声明代码编码类型,#!/usr/bin/env Python #coding=utf-8,然后导入所需的依赖:
    import osimport randomimport getConfig
    os是提供对python进行一些操作系统层面的工具,比如read、open等,主要是对文件的一些操作。 random是一个随机函数,提供对数据的随机分布操作。 getConfig是外部定义的一个函数,用来获取seq2seq.ini里的参数配置。 接下来是对原始语料的处理,主要是通过语句的奇偶行数来区分问答语句(由于语料是电影的台词,所以直接默认是一问一答的方式),然后把区分后的语句分别存储下来,保存为ask文件和response文件。
    gConfig = {}gConfig=getConfig.get_config()conv_path = gConfig['resource_data']if not os.path.exists(conv_path): exit()convs = [] # 用于存储对话集合with open(conv_path) as f: one_conv = [] # 存储一次完整对话 for line in f: line = line.strip('\n').replace('/', '') if line == '': continue if line[0] == gConfig['e']: if one_conv: convs.append(one_conv) one_conv = [] elif line[0] == gConfig['m']: one_conv.append(line.split(' ')[1])ask = [] # 用来存储问的语句response = [] # 用来存储回答的语句for conv in convs: if len(conv) == 1: continue if len(conv) % 2 != 0: # 保持对话是一问一答 conv = conv[:-1] for i in range(len(conv)): if i % 2 == 0: ask.append(conv[i]) #因为这里的i是从0开始的,因此偶数为问的语句,奇数为回答的语句 else: response.append(conv[i])
    然后调用convertseq2seqfiles函数,将区分后的问答语句分别保存成训练集和测试机,保存文件为train.enc、train.dec、test.enc、test.dec。convertseq2seqfiles函数就不一一贴出来,可以对照源码来看。
    3.2 数据处理器(prepareData.py)编码声明和导出依赖部分不再重复,在真正的数据处理前,需要对训练集和测试集中的一些特殊字符进行标记,以便能够进行统一的数据转换。特殊标记如下:
    PAD = "__PAD__"#空白补位GO = "__GO__"#对话开始EOS = "__EOS__" # 对话结束UNK = "__UNK__" # 标记未出现在词汇表中的字符START_VOCABULART = [PAD, GO, EOS, UNK]PAD_ID = 0#特殊标记对应的向量值GO_ID = 1#特殊标记对应的向量值EOS_ID = 2#特殊标记对应的向量值UNK_ID = 3#特殊标记对应的向量值
    接下来定义生成词典函数,这里及后续的函数只贴出函数名和参数部分,详细的代码参见Git上的源码:
    def create_vocabulary(input_file,output_file):
    这个函数算法思路会将input_file中的字符出现的次数进行统计,并按照从小到大的顺序排列,每个字符对应的排序序号就是它在词典中的编码,这样就形成了一个key-vlaue的字典查询表。当然函数里可以根据实际情况设置字典的大小。
    def convert_to_vector(input_file, vocabulary_file, output_file):
    这个函数从参数中就可以看出是直接将输入文件的内容按照词典的对应关系,将语句替换成向量,这也是所有seq2seq处理的步骤,因为完成这一步之后,不管原训练语料是什么语言都没有区别了,因为对于训练模型来说都是数字化的向量。
    def prepare_custom_data(working_directory, train_enc, train_dec, test_enc, test_dec, enc_vocabulary_size, dec_vocabulary_size, tokenizer=None):
    这个函数是数据处理器的集成函数,执行器调用的数据处理器的函数也主要是调用这个函数,这个函数是将预处理的数据从生成字典到转换成向量一次性搞定,将数据处理器对于执行器来说实现透明化。working_directory这个参数是存放训练数据、训练模型的文件夹路径,其他参数不一一介绍。
    3.3 seq2seq 模型seq2seq模型是直接参照TF官方的源码来做的,只是对其中的一些tf参数针对tf的版本进行了修正。如Chat简介中说的,考虑到大家的接受程度不一样,本次不对代码、算法进行太过深入的分析,后面我会开一个达人课专门详细的进行分析和相关知识的讲解。
    class Seq2SeqModel(object):
    前面的导入依赖不赘述了,这里在开头需要先定义一个Seq2SeqModel对象,这个对象实现了一个多层的RNN神经网络以及具有attention-based解码器,其实就是一个白盒的神经网络对象,我们只需拿来用即可,详细的可以参阅http://arxiv.org/abs/1412.7449这个paper。关于这个paper这次不做解读。
    def __init__(self, source_vocab_size, target_vocab_size, buckets, size, num_layers, max_gradient_norm, batch_size, learning_rate, learning_rate_decay_factor, use_lstm=False, num_samples=512, forward_only=False):
    这个函数是整个模型的初始化函数(父函数),这个函数里面会定义多个子函数,简单来讲这个函数执行完之后就完成了训练模型的创建。这个函数中的大部分参数都是通过seq2seq.ini文件进行朝参数配置的,其中uselstm这个参数是决定是使用gru cell还是lstm cell来构建神经网络,gru其实是lstm的变种,我两个cell都测试了,发现在进行语句对话训练时使用gru cell效果会更好,而且好的不是一点。由于时间的缘故,只对超参size、numlayers、learningrate、learningratedecayfactor、use_lstm进行简单对比调试,大家有兴趣的话可以自己进行调参,看看最优的结果值preprlexity会不会小于10。
    sampledloss、seq2seqf、step、get_batch这些子函数不一一的讲了,大家可以百度一下,都有很详细的解释和讲解。如果需要,我会在达人课里对这些子函数进行讲解。
    3.4 执行器_buckets = [(1, 10), (10, 15), (20, 25), (40, 50)]
    这个bukets的设置特别关键,也算是一个超参数,因为这个关系到模型训练的效率。具体设置的时候,有两个大原则:尽量覆盖到所有的语句长度、每个bucket覆盖的语句数量尽量均衡。
    def read_data(source_path, target_path, max_size=None):
    这个函数是读取数据函数,参数也比较简单,其中max_size这个参数默认是空或者是None的时候表示无限制,同时这个参数也是可以通过seq2seq.ini进行设置。
    def create_model(session, forward_only):
    这个函数是用来生成模型,参数简单。model的定义也只有一句:
    model = seq2seq_model.Seq2SeqModel( gConfig['enc_vocab_size'], gConfig['dec_vocab_size'], _buckets, gConfig['layer_size'], gConfig['num_layers'], gConfig['max_gradient_norm'], gConfig['batch_size'], gConfig['learning_rate'], gConfig['learning_rate_decay_factor'], forward_only=forward_only)
    这里可以看到,模型的生成直接调用了seq2seq_model中的对象Seq2SeqModel,将相应的参数按照要求传进去就可以,具体这个对象的作用以及详细的细节如前面所说可以参照具体的paper来研究,但是作为入门的兴趣爱好者来说可以先不管这些细节,先拿来用就可以了,主要关注点建议还是在调参上。
    def train():
    train函数没有参数传递,因为所有的参数都是通过gconfig来读取的,这里面有一个特殊的设计,就是将prepareData函数调用放在train()函数里,这样做的话就是每次进行训练时都会对数据进行处理一次,我认为这是非常好的设计,大家可以参考,因为这个可以保证数据的最新以及可以对增长的数据进行训练。具体代码如下:
    enc_train, dec_train, enc_dev, dec_dev, _, _ = prepareData.prepare_custom_data(gConfig['working_directory'],gConfig['train_enc'],gConfig['train_dec'],gConfig['test_enc'],gConfig['test_dec'],gConfig['enc_vocab_size'],gConfig['dec_vocab_size'],tokenizer=None)def selftest():和def initsession(sess, conf='seq2seq.ini'):
    这两个函数分别是进行测试以及初始会话用的。由于TF的特殊机制,其每次图运算都是要在session下进行的,因此需要在进行图运算之前进行会话初始化。
    def decode_line(sess, model, enc_vocab, rev_dec_vocab, sentence):
    这个函数就是我们整个对话机器人的最终出效果的函数,这个函数会加载训练好的模型,将输入的sentence转换为向量输入模型,然后得到模型的生成向量,最终通过字典转换后返回生成的语句。
    由于执行器包含多种模式,因此我们在最后加上一个主函数入口并对执行模式判断,
    if __name__ == '__main__':if gConfig['mode'] == 'train': # start training train()elif gConfig['mode'] == 'test': # interactive decode decode() else: print('Serve Usage : >> python3 webui/app.py') #当我们使用可视化模块调用执行器时,需要在可视化模块所在的目录下进行调用,而是可视化模块由于包含很多静态文件,所以统一放在webui目录下,因此需要将执行器与可视化模块放在同一个目录下。 print('# uses seq2seq_serve.ini as conf file')#seq2seq_serve.ini与seq2seq.ini除了执行器的模式外所有配置需要保持一致。
    3.5 可视化展示模块def heartbeat():
    由于可视化展示模块需要账期在线运行,为了了解运行状态加了一个心跳函数,定时的输出信息来check程序运行情况。
    def reply():
    这个函数是人机对话交互模块,主要是从页面上获取提交的信息,然后调用执行器获得生成的回答语句,然后返回给前端页面。
    其中有一点设计可以注意一下,就是在进行语句转向量的过程中,为了保证准确识别向量值,需要在向量值中间加空格,比如123,这个默认的会识别成一个字符,但是1 2 3就是三个字符。因此在获取到前端的语句后,在传给执行器之前需要对语句进行字符间加空格处理,如下:
    req_msg=''.join([f+' ' for fh in req_msg for f in fh])def index():
    这个函数是可视化展示模块的首页加载,默然返回一个首页html文件。
    另外,由于TF的特殊机制,需要在可视化模块中初始化会话,这样执行器才能运行,如下:
    import tensorflow as tf import execute sess = tf.Session() sess, model, enc_vocab, rev_dec_vocab = execute.init_session(sess, conf='seq2seq_serve.ini')
    最后和执行器一样,需要加一个主函数入口启动可视化模块,并配置服务地址和端口号,如下:
    if (__name__ == "__main__"): app.run(host = '0.0.0.0', port = 8808)
    四、训练过程和最优值
    5 评论 121 下载 2018-11-21 17:29:20 下载需要11点积分
  • 基于C#实现的支持AI人机博弈的国际象棋游戏程序

    1 背景和意义1.1 项目意义
    该项目的成功推进和完成将达到 AI 比赛过程自动化的目的,有助于比赛的顺畅、成功开展以及比赛时间的有效节约
    该项目的成果将有助于《人工智能原理》课程的学生对于自己编写的 AI 程序的测试
    该项目的成果将有助于国际象棋 AI 的后续研究和教学展示
    该项目的成果由于支持人机、机机博弈,也具有一定的游戏性和观赏价值

    1.2 项目目标完成一个图形界面国际象棋棋盘软件。它主要具备以下功能:

    图形界面显示(显示与用户交互的窗体控件、显示棋盘和棋子)
    游戏参与者加载 AI 程序
    游戏组织者选择游戏模式(自动、手动)
    游戏组织者开始游戏、进行游戏

    软件与 AI 程序通信,完成自动博弈
    游戏参与者/游戏测试者手动走子
    软件判断走法符合规则
    软件判断游戏结束(局面是否出现将军、欠行等,计时是否结束)
    软件对走子计时


    一些性能约束:

    能在时下主流的笔记本电脑(x86和x64 架构的多核 CPU)上运行
    在 Windows 7 及以上操作系统运行

    1.3 开发用语言和环境
    项目的编码用C#语言写成,图形界面用 WinForm(Windows 窗体 API)实现。
    开发环境为 Visual Studio 2017,框架为.NET Framework 4.6。

    2 详细需求描述2.1 对外接口需求2.1.1 用户界面
    UI1:唯一的一个象棋棋盘和控制窗体主界面。该界面的风格为 WinForm (Windows 风格窗体),方便熟悉 Windows 界面语言的人快速上手和操作。该界面的图示为:


    UI1.1:在载入 AI 时,应弹出填入载入 AI 信息的对话框,如下图所示:


    UI1.2:在设置选项时,应弹出含有选项的对话框,如下图所示:

    2.1.2 通信接口本软件与参赛 AI 交互使用的是操作系统的“标准输入输出”;内容协议采用 SAN 格式。
    2.2 功能需求2.2.1 图形化显示棋盘和棋子2.2.1.1特性描述 主界面显示一个 8x8 的正方形国际象棋棋盘。棋子以图形的方式显示在国际象棋 棋盘中。参赛者或组织者开始游戏或重置游戏时,应达到初始化棋盘显示的功能;游戏开始后,棋盘显示游戏的棋局,并随着双方走子不断更新显示。如 UI1 所示。
    2.2.2 加载 AI2.2.2.1 特性描述参与比赛的两方参赛者,均可以在比赛开始前在本软件中载入自己的 AI 作为进程 运行,准备参与博弈。载入内容包括“是白/黑方”、“可执行文件路径”、“执行参数”。
    2.2.2.2 刺激/响应序列
    刺激:用户点击“载入白方 AI”或载入“黑方 AI”
    响应:系统弹出对话框,用户界面如 UI1.1
    刺激:用户在可执行文件文本框中输入可执行文件路径
    响应:文本框成功接受输入
    刺激:用户在对话框中点击“浏览”
    响应:系统弹出二级对话框,允许用户浏览文件选择可执行文件
    刺激:用户选择完毕,点击“确认”


    2.2.3 以不同的模式进行游戏2.2.3.1 特性描述进行游戏可以有以下几种模式:

    其中,”人类手动”指人类利用鼠标点击棋盘上的棋子、选择其移动位置来完成走 子,”AI 自动”指本软件不经用户确认直接从 AI 读入信息完成走子,”AI 手动”指本软件需要用户手动点击“从 AI 读入”按钮才从 AI 读入信息。
    游戏组织者在开始游戏前,需要可以从本软件中选择其中一种模式。
    2.2.3.2 刺激/响应序列
    刺激:用户点击界面 UI 上的“模式选择”单选按钮
    响应:本软件接受用户的点选输入,并相应地更新 UI
    刺激:用户点击“开始游戏”
    响应:本软件进入游戏进行状态,根据选好的模式决定是否向 AI 发送其所在方(黑/白)信息,并开始从 AI 读入走子信息走子

    2.2.3.3 相关功能需求
    2.2.4 与 AI 进程通信、处理用户交互,实现自动、手动博弈;判断走子是否符合规则2.2.4.1 特性描述作为棋盘平台,本软件在象棋游戏进行过程中,要根据模式选择的不同,与 AI 进程通信以获得它们的走子信息,以及处理用户交互以获得人工走子信息,实现无组织 者人工干预的自动、手动博弈。同时,本软件还应充当裁判的作用,预防不合法的走子产生。
    2.2.4.2 刺激/响应序列
    刺激:用户点击棋盘
    响应:UI 以颜色的方式提醒用户点击是否有效合法;若有效合法,推进走子的流程
    刺激:用户成功走子
    响应:更新 UI 为走子后的局面;判断游戏是否结束并给出提醒;若对方 为 AI,向其发送走子信息
    刺激:AI 发来走子信息
    响应:判断走子信息是否有效合法;若有效合法,更新 UI 为走子后的局面;判断游戏是否结束并给出提醒;且若对方为 AI 则向其发送走子信 息。若不合法,则回送特殊信息说明走子错误
    刺激:用户点击“停止”
    响应:等待所有附加线程运行完毕,然后停止游戏,更新 UI,给出提示 “游戏已停止”
    刺激:用户点击“重置”
    响应:等待所有附加线程运行完毕,然后重置游戏,更新 UI

    2.2.4.3 相关功能需求
    2.2.5 给游戏计时2.2.5.1 特性描述由于需要控制 AI 走子的时间,故组织者需要对 AI 的走子进行计时。计时的方法是,轮到该 AI 走子时,AI 在走子前等待的时间累计起来,即是该 AI 所用的时间。
    2.2.5.2 刺激/响应序列
    刺激:由于上一步的用户手动走子或 AI 的自动走子,将主动权让给了我方 AI
    响应:本软件开始对我方 AI 的走子计时
    刺激:我方 AI 思考后走子
    响应:本软件停止对我方 AI 的走子计时,将计时的这段时间累加到我方 AI 所用时间上
    刺激:用户点击“重置”
    响应:两方 AI 的计时均归零

    2.2.5.3 相关功能需求
    2.2.6 保存比赛棋谱2.2.6.1 特性描述一场游戏结束后,组织者可能需要保存其棋谱。本软件允许组织者将比赛棋谱复制到剪贴板,以便粘贴到别处保存。
    2.2.6.2 刺激/响应序列
    刺激:组织者选择历史记录里所有走子
    响应:本软件的 UI 做出相应变化,表示组织者成功选定了这些走子
    刺激:组织者按下“Ctrl-C”键。(“复制”操作快捷键)
    相应:本软件将已经选定的走子送至操作系统的剪贴板上

    2.2.6.3 相关功能需求
    2.2.7 处理和保存用户的设置2.2.7.1 特性描述本软件应处理和保存用户的一些参数偏好,包括默认可执行文件路径、是否在运行时隐藏 AI 窗口、观棋时间、是否自动保存 AI 配置。
    2.3 性能需求
    Performance1:速度:每一步走棋所产生的相应变化需在 1 秒内完成,点击按钮后所产生的变化应在 3 秒内完成
    Performance2:负载:能够接受两个 AI 同时运行
    Performance3:适应性:在不同 Windows 版本上能够运行

    2.4 数据需求2.4.1 数据定义和格式要求本软件需要在计算机上存取的数据只有用户设置。用户设置包含如下定义和格式的数据:

    2.5 安全性需求
    Safety1:本软件的运行应对操作系统的完整性无害
    Safety2:本软件的运行应不违反操作系统规则,不导致操作系统陷入崩溃
    Safety3:本软件应不对用户的其他文件造成危害,包括修改和删除
    Safety4:本软件应对每个 Windows 用户的用户设置数据设置屏蔽,防止互通

    2.6 可靠性需求
    Reliability1:本软件不应在用户对其正常使用时突然退出、崩溃
    Reliability2:本软件不应在 AI 程序出现错误、故障、突然退出时发生故障
    Reliability2.1:本软件应该检测到 AI 出现异常,并弹窗报告用户,并停止 正在进行的游戏
    Reliability3:本软件应当合理管理分配的内存,防止出现内存泄漏
    Reliability4:本软件应当线程安全,防止多个线程同时操作一个对象产生的不 协调和错误

    2.7 用例图
    2.8 用例描述2.8.1 选择模式
    2.8.2 进行国际象棋游戏
    2.8.3 AI接受系统标准输入
    2.8.4 AI给出标准输出
    2.9 概念类图由于本软件从需求来看功能要求不多,故对于所有用例,统一绘制一个概念类图于此。

    解释:ChessGameLogic 是关于游戏运行时逻辑的类,为软件的核心; ChessGameForm 是游戏运行的窗体,充当 UI;ChessGameRule 是判断走子是否合法、游戏是否结束时用到的象棋规则类;AIProcess 和 StopWatch 分别为 AI 进程类和计时类。
    2.10 系统顺序图同概念类图,我们将所有用例集合在一起,以一个用户开启软件到进行完一局游戏的全过程绘制了系统顺序图。

    2.11 状态图
    3 整体架构描述本节将描述本软件的整体架构,从逻辑视角和组合视角来描述,采用 UML 包图、 构件图。
    3.1 逻辑视角3.1.1 体系结构设计风格本软件体系结构设计的风格采用模型-视图-控制器(Model-View-Control, MVC)风格。采用该风格的方案明显较好,因为:

    实际开发时使用 C# 结合.NET Framework,非常适合于实现 MVC 风格的体系结构
    能够促进并行开发,缩短开发时间
    该风格的部件区分明朗,便于进行体系结构设计和详细设计

    MVC 风格将整个系统分为三个部件(子系统):

    模型(Model):封装系统的数据和状态信息,也包含业务逻辑。在本软件 中,模型包含国际象棋的规则部分、国际象棋游戏进程的逻辑
    视图(View):提供业务展现,接受用户行为。在本软件中,视图包含程序 的显示窗体、控件。控件既可向用户展示信息,也可以被用户以点击的形式交互
    控制(Controller):封装系统的控制逻辑。在本软件中,控制包含用户点击 控件后触发的事件函数,以及刷新 UI 所需调用的事件函数

    构件图如下:

    3.1.2 概要功能设计和逻辑包图由于本软件的界面和控制不复杂,实现较为简单,视图和控制部件包均可只用一 个逻辑包实现;模型涉及到功能较多,用多个逻辑包实现。用包图表达的最终软件体系结构逻辑设计方案如下:

    3.2 组合视角3.2.1 开发包设计在逻辑视角的基础上,可以用组合视角对于体系结构进行开发包的设计。由于我们的项目较为简单,故采用以下的开发包设计:

    每一个组合包最多转化为一个开发包
    模型部件中依赖关系较多的包组合为一个开发包
    逻辑包中没有循环依赖需要解决,故无需再增加开发包
    为简洁,不再另设不同部件之间的接口包

    在引入.NET Framework 框架提供的类库之后,整个软件的开发包图如下:

    各包的名称、功能和依赖关系均已在图中呈现,故不另外列开发包表。
    3.2.2 运行时进程由于软件简单,故运行时排除 AI 进程外,只有一个主进程。
    3.2.3 物理部署由于软件简单,只需要一个可执行文件部署在本地计算机。
    4 模块分解对于诸模块的分解设计采用结构视角和接口视角来说明。
    4.1 模块的职责按照 MVC 的部件划分,可以直接将每个部件转换为一个大的模块:Model 模块、 View 模块、Control 模块。其职责如下:

    不同模块之间通过简单的函数调用完成连接。
    4.2 MODEL的分解Model 模块包含与象棋的状态和信息有关的对象类,如 ChessGame(以象棋规则为中心的象棋棋局类)、ChessGameLogic(以象棋游戏进程逻辑为中心的象棋棋局 类)、ChessPieces(象棋的棋子类)等。
    4.2.1 Model分解后的职责 Model 模块包含三个开发包,其职责如下表所示:

    4.2.2 Model 分解后的接口规范注:只列出对于本软件有关键作用的接口,重要性较小的接口如计时、用户设置有关的在此不列出。
    4.2.2.1 ChessGameWithRule 的接口规范
    4.2.2.2 AlProcess 的接口规范
    4.2.2.3 ChessGameLogic 的接口规范
    5 详细设计本软件详细设计的基本方法为面向对象设计方法(Object-oriented Design Method),意在将各个构件实现时,用抽象为一系列对象的方式看待。
    5.1 MODEL的分解5.1.1 模块概述和整体结构Model 模块的职责为记录软件运行的状态、象棋的局面信息,处理象棋的走子、 规则。在软件的体系结构设计中,其下分为 ChessGameWithRule、ChessGameLogic、 AIProcess 三个包,分别包含象棋规则、象棋游戏进程逻辑、AI 进程逻辑三个方面的逻 辑。后两个包可各用一个类实现,而前一个包由于构成与所实现的功能更复杂一些, 故可以用多个类来实现。
    这三个开发包的内部构造和职责、相互协作描述如下:

    ChessGameWithRule 包中的核心实现类是同名类 ChessGameWithRule(在实 际代码编写中,更名为 ChessGame)。它除了对.NET Framework 框架,以及 同一包内的一些数据结构类有依赖之外,是一个自成一体的象棋规则实现 类。一个 ChessGameWithRule 对象可以完备地从规则角度上实现一局象棋游 戏的过程。其内有包含棋盘(棋子对象构成的数组 Piece [][])、历史行棋 (Move 对象构成的列表 List<Move>)等,也有 ApplyMove(实现走子)、 GetValidMoves(获得当前所有合法走子)等具体功能方法。除此之外, ChessGameWithRule 包还包含与象棋规则有关的数据结构,如 Piece(棋 子)、Move->MoreDetailedMove(走子,MoreDetailedMove 继承自 Move,包含更多信息)、Position(位置)等等,被 ChessGameWithRule 核心类所聚合。ChessGameWithRule 还包含了一个用于处理 SAN 字符串为 Move 对象的分析函数 PgnMoveParser,便于游戏进程逻辑层面的使用。
    AIProcess 由一个同名类实现,实现一个 AI 进程的逻辑,如启动、停止、标准 输入输出的读写。它调用.NET Framework 系统类,可以控制启动、停止系统 进程,并操作标准输入输出。可以说它是系统进程与本软件的接洽。
    ChessGameLogic 由一个同名类实现,注重于象棋游戏的进程(开始、循环走 棋、何时结束游戏)来实现一局象棋游戏。由于象棋游戏的进程取决于规 则,故它依赖并使用 ChessGameWithRule 作为规则的实现。同时,它也使用 AIProcess 类,向其发送有关于黑白双方 AI 的命令,以实现机器博弈。至于人 工博弈,人类的行棋是通过用户界面,从 View 模块传导到 Control 模块,再调用 ChessGameLogic 里的 ApplyMove 实现的,不全部由ChessGameLogic 实现。

    5.1.1.1 ChessGameWithRule 内的类图
    5.1.1.2 AIProcess 内的类图
    5.1.1.3 ChessGameLogic 内的类图
    以下将不再分每一个小包进行接口规范的描述,而是直接对 Model 内的类进行接 口规范的描述。
    5.1.2 内部类的接口规范与“体系结构设计”中的 Model 接口有所重合的类接口,这里有些就省略不列出。 现将“体系结构设计”中细化的 Model 内部类接口规范描述如下。
    ChessGameWithRule 类的接口规范

    MoreDetailedMove 类的接口规范

    Piece 类(抽象类,具体棋子类的基类)的接口规范

    AIProcess 类的接口规范

    ChessGameLogic 类的接口规范

    5.1.3 Model 的动态模型由于本软件详细设计中的动态模型中,状态图的设计与软件结构是否分解为类关系不大,故动态模型中的状态图省略不画。 现将进行一盘游戏的系统顺序图扩展为详细顺序图,描绘如下:

    6 核心算法6.1 GETVALIDMOVES算法描述ChessGameWithRule.GetValidMoves 是 Model 里象棋规则实现库 ChessGameWithRule 中的一重要函数,可以获得某一方的所有允许的走子。它由两个同名重载函数实现。
    public virtual ReadOnlyCollection<MoreDetailedMove> GetValidMoves(Position from, bool returnIfAny, bool careAboutWhoseTurnItIs)
    这个函数可以将该行动方从 Position from 出发的所有可行走子返回。returnIfAny 和 careAboutWhoseTurnItIs 是内部使用和调试用参数,可以忽略。
    其具体实现如下:
    public virtual ReadOnlyCollection<MoreDetailedMove> GetValidMoves(Position from, bool returnIfAny, bool careAboutWhoseTurnItIs) { ChessUtilities.ThrowIfNull(from, "from"); Piece piece = GetPieceAt(from); if (piece == null || (careAboutWhoseTurnItIs && piece.Owner != WhoseTurn)) return new ReadOnlyCollection<MoreDetailedMove>(new List<MoreDetailedMove>()); return piece.GetValidMoves(from, returnIfAny, this, IsValidMove); }
    描述为自然语言:

    获取当前棋盘 from 位置的棋子为 piece
    如果那个位置没有棋子,直接返回空集合
    否则 ,调用这个具体棋子的多态函数 GetValidMoves,给出从 from 出发的所有 可行走子。同时,还传入当前 ChessGameWithRule 对象下的,对于本局 游戏特化的走子验证函数,方便 Piece.GetValidMoves 调用用于二次验证 走子的合法性

    public virtual ReadOnlyCollection<MoreDetailedMove> GetValidMoves(Player player, bool returnIfAny, bool careAboutWhoseTurnItIs)
    这个函数可以将该行动方(不受 Position 制约)的所有合法走子返回。
    public virtual ReadOnlyCollection<MoreDetailedMove> GetValidMoves(Player player, bool returnIfAny, bool careAboutWhoseTurnItIs) { if (careAboutWhoseTurnItIs && player != WhoseTurn) return new ReadOnlyCollection<MoreDetailedMove>(new List<MoreDetailedMove>()); List<MoreDetailedMove> validMoves = new List<MoreDetailedMove>(); for (int r = 1; r <= Board.Length; r++) { for (int f = 0; f < Board[8 - r].Length; f++) { Piece p = GetPieceAt((File)f, r); if (p != null && p.Owner == player) { validMoves.AddRange(GetValidMoves(new Position((File)f, r), returnIfAny)); if (returnIfAny && validMoves.Count > 0) { return new ReadOnlyCollection<MoreDetailedMove>(validMoves); } } } } return new ReadOnlyCollection<MoreDetailedMove>(validMoves); }
    描述为自然语言:

    初始化 validMoves 为空集
    对于棋盘上的所有格子(位置 Position)作一遍历:

    获得该格子上的棋子为 p
    若格子上有棋子(p 非空)且 p 的拥有者是 player:

    调用 同名函数 GetValidMoves,获取从该位置出发的所有合法走子
    将 validMoves 并上刚刚返回的走子集合


    返回 validMoves

    6.2 ISMOVEVALID算法描述这个函数同样属于象棋规则库。它判断某一个走子在当前游戏的进行情况中是不是合法的,这对于合法走子的生成和判定非常重要。
    public virtual bool IsValidMove(Move move, bool validateCheck, bool careAboutWhoseTurnItIs) { ChessUtilities.ThrowIfNull(move, "move"); if (move.OriginalPosition.Equals(move.NewPosition)) return false; Piece piece = GetPieceAt(move.OriginalPosition.File, move.OriginalPosition.Rank); if (careAboutWhoseTurnItIs && move.Player != WhoseTurn) return false; if (piece == null || piece.Owner != move.Player) return false; Piece pieceAtDestination = GetPieceAt(move.NewPosition); bool isCastle = pieceAtDestination is Rook && piece is King && pieceAtDestination.Owner == piece.Owner; if (pieceAtDestination != null && pieceAtDestination.Owner == move.Player && !isCastle) { return false; } else if(move is MoreDetailedMove m) if (pieceAtDestination != null && pieceAtDestination.Owner == ChessUtilities.GetOpponentOf(move.Player)) { m.IsCapture = true; m.CapturedPiece = pieceAtDestination; } if (!piece.IsValidMove(move, this)) { return false; } if (validateCheck) { if (!isCastle && WouldBeInCheckAfter(move, move.Player)) { return false; } } return true; }
    自然语言描述如下:

    若 move 为空

    返回 false
    获取 move 的源位置的行、列,并以此获取该位置的棋子到 piece
    若 piece 为空或不是本方棋子

    返回 false
    获取 move 的目标位置的,并以此获取目标位置的棋子到 pieceAtDestination
    对于 move 是王车易位的情况作特殊判断,若王车易位不合法:

    返回 false
    调用该 piece 的 IsValidMove 函数,获知该棋子能否如此移动。若不能:

    返回 false
    若该走子非王车易位,且走子后会陷入将军:

    返回 false
    返回 true
    2 评论 37 下载 2018-11-20 09:06:49 下载需要10点积分
  • Linux环境下基于GTK的CS聊天系统

    1 项目综述本项目是以Linux C 为主体,使用网络套接字编写,并且具有图形界面(GTK)的可视化聊天室软件。完整实现私人聊天与群组聊天,传文件,日志处理,离线消息,管理员管理功能,信息修改等功能。
    2 开发平台与工具
    编辑器:vim gedit
    编译器:gcc
    调试工具:gdb
    开发平台:fredoa 21 linux4.0.6-200.fc21.x86_64

    3 核心模块思路综述3.1 私聊模块客户端发送信息时,需要发送IP/名称,然后发送信息到服务器端,通过服务器端根据IP/名称,确定其链接套接字,然后发送信息到对应的客户端。就是使用服务器作为中转站。将消息转发到对方的套接字中。
    3.2 群聊模块服务器每接收一条信息就将这条信息,发送给已经为人聊天状态的所有用户。
    3.3 文件传送类似于传送消息,FTP功能只有在处理文件传输功能的时候才会被开启,首先,当需要发送文件的时候,打开文件传输窗口,选择相关文件以及目标对象的名字,发送一个询问语句,等待对方接受。测试经过服务器的转发对方已经接受到了一个文件传送的消息或是显示于聊天窗口,或是显示在FTP 专用窗口,但是只有在FTP主窗口中点击RECV按钮,方可接受,这时服务器首先转发RECV 消息,当消息转发完成后,客户端接受到RECV 信息,确认文件名,开始传输文件,当文件传输完毕后发送一个传送完成的信号,其中如果文件大于800K,那就增加分片标识机制。进一步设计:首先发送端,点击发送按钮,此时立即发送一个基本的数据包,其中包括需要传送的文件,然后有一个读取语句,将后边的发送语句挂起等待一个条件变量,此时对方收发到了先遣数据包,当对方点击接受按钮时,首先会发送一个带有接受标识的数据包用来改变对方的条件变量随后开启接受线程等待最终文件接受完毕后,对方会发送一个文件已经传送完成信号,此时最后一个数据包被接受,跳出循环,线程终止。
    3.4 好友处理模块添加一种新的消息包类型,用新的类型来标记好友相关信息:
    其中好友处理函数又包含三个方面。刷新,添加,删除。这三个字函数,他们根据消息传第的不同内容作出三种响应,最终完成好友相关操作。其中,服务器首先接受一个添加好友的请求,然后将请求转发给目标用户,前提:目标在线,暂不考率目标离线情况(离线默认也可以添加,对方会收到一个被添加的离线消息)。然后由目标给与一个反馈信息,此时需求客户端并不是阻塞模式,可以正常收发信息。才用“发出后不管”的策略。其时每个客户端都在等待一个加它为好友的信息。待客户端成功得到结果后,就将反馈信息反馈给服务器,最终由服务器来完成加好友操作工作。
    3.5 离线消息模块对方发送一个消息,服务器判断目标是否在线,如果是离线就暂时存储在服务器端的目标文件中,对方上线后会首先读取这个文件以达到得到离线消息的目的。
    4 线程池技术应用针对不同的客户端分配给它一个线程,这个线程调用work( )函数,work( ): 首先从服务器的存储用户信息的文件中读取所有用户信息进链表然后便利链表查找相应信息,如果没有发送错误信息并且关闭链接套接字,断开本次链接。如果查找成功,修改标志值,保持链接存在,创建发送,获取数据的两个线程,与客户端进行通信。上线后首先读取离线文件,如果文件里有东西就将文件内的离线消息打印出来接下来读取发送过来的信息确定是要私人聊天还是多人聊天,根据标识与IP等信息确定聊天状态,用户成功登录或者注册后,使用gtk弹出一个窗口,让用户选择当前需要的聊天模式,私人聊天,多人聊天,以及文件发送总共三个模块,用户选择后则进入各自不同的界面,聊天模块都是相类似的两个界面,文件传输为一个单独的模块。
    关于服务器上文件的相应处理与使用,首先服务器存在一个服务器-客户文件(server)这个文件用来存储所有客户的信息其中包括用户名,密码,登录状态,套接字等,每次客户登录时首先刷新读取这个文件一遍,将其中信息读取到一个公共的链表上,其次改变自己的状态,与描述符,将这些信息改变后即刻写入文件中(应当使用读写锁),以便于下一个用户在登录后刷新读取链表时得到的是一个正确的链表信息。所以服务器在使用链表前都应当刷新读取文件进链表一边(或者在下线时在改变相关文件的时候,同时更改链表的信息,这种方法更有可行性)。
    5 运行展示5.1 开始菜单
    5.2 登陆示例
    5.3 功能菜单
    5.4 私人聊天功能
    5.5 群组聊天
    5.6 文件传输


    5.7 修改信息和管理员
    2 评论 62 下载 2018-11-06 17:03:09 下载需要9点积分
  • 基于Android studio的答题APP

    从json文件中获取题目,判断JudgeFirstActivity,判断是否第一次登录,如果是第一次登录,则进入广告页面。


    关闭按钮:自定义Button,设置关闭图标,点击关闭,进入注册页面

    点击关闭按钮,注册页面RegisterActivity
    自定义三个EditText,用于添加用户名、密码和重复密码,用户名、密码和

    重复密码均不为空,密码和重复密码相同,则注册
    自定义Button,用于“创建账号”的触发事件,点击则向数据库添加用户
    自定义TextView,用于实现“登录”的触发事件,点击则跳转登录页面LoginActivity


    登录页面LoginActivity

    自定义两个EditText,用于登录验证,若数据库中存在该用户,点击登录,则跳转到首页
    自定义三个Button,分别用于实现返回注册页面、登录跳转、取消登录则清空输入框


    主页面ChooseTypeActivity

    自定义4个Button,分别用于实现选择不同的题型及查看APP的相关内容
    点击“开始答题”的圆形图标进入答题模式,默认设置(直接点击“开始答题”),则混合题型(“填空题”、“判断题”和“选择题”)均要作答AllQuestionAnswer
    分别点击“填空题”、“判断题”和“选择题”的人形图标,再点击“开始答题”,则进入相关答题模式,ChooseActivity,GAPFillActivity,JudgeActivity
    点击“关于”,则跳转到AboutActivity,查看APP的相关内容及作者信息




    选择题ChooseActivity

    自定义Button,用于实现上一题、下一题和交卷,且记住当前选项,未选择则空白,选过则记住选择的选项若为第一题,点击“上一题”则提示“已经到头了”,若为最后一题,点击“下一题”,则提示“已到最后一题”


    点击“提交”,根据题目个数,设置每道题的得分,并统计总分,弹出“确认交卷”的对话框,点击确认则交卷,并将结果传到下一个,跳转到“答题详情”页面ResultActivity
    页面上设置了计时器,点击交卷则将“时间”存储且发送到ResultActivity


    “答题详情”ResultActivity

    页面自定义Button,用于返回到首页
    页面自定义TextView,分别用于显示答题时间和得分
    页面自定义ListView,用于显示题目的详细信息,如题目,选项,正确答案和填写或选择的答案,若回答错误,则将该题的题目设为红色
    布局中使用了ScrollView,用于实现listview的滚动查看

    均为判断题

    均为选择题

    均为填空题


    根据题目类型,设置页面详情,对应不同的题型,设置相关布局,包含三种题型
    0 评论 9 下载 2021-02-22 20:25:04 下载需要10点积分
  • 基于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 评论 99 下载 2018-11-06 18:59:59 下载需要10点积分
  • 基于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.项目截图




    6 评论 109 下载 2019-04-03 19:56:24 下载需要15点积分
  • 基于JSP和SQL SERVER实现的B/S架构的超市管理系统

    1 引言社会生活的现代化,使得市场的走向发生巨大变化,由于经济的发展,人们对生活的需求已经不再满足于丰衣足食的低度要求,许多人们往往不是单纯为满足生活必需去购买,而是凭着喜欢、意欲和感观去购买。如果一个商店能够打动顾客、吸引顾客,自然会顾客盈门,而近几年新兴产业中超级市场ERMARKET)的现代化管理方式和便捷的购物方式,尤其是它轻松的购物环境。往往是打动顾客,吸引顾客的最主要的原因,且良好的周密的销售服务更是赢得信誉、吸引顾客的优势所 在。商品经济的高速现代化发展也促进了竞争,使一切不甘落后的商家都争先恐后地采用最新的管理方法来加强自己的竞争地位。因此,超市经营者如果不掌握当今市场发展的这一走向,不能将超市现代化经营作为努力开拓的目标,就无法使经营活络、财源茂盛。
    随着计算机网络技术以及数据库技术的迅速发展,管理信息系统得到了广泛应用。对于一个超市来讲:货品数量少则数以百计,多则数以万计;另外,不同超市的实际情况也有所不同。要对这些货品进行统一、高效的管理,靠人工完成工作量庞大、难免有错漏之处。为此,一个自动化的超市货品管理系统的开发非常必要。
    1.1 背景最初的超市资料管理,都是靠人力来完成的。但近几年我国超市经营规模日趋扩大,销售额和门店数量大幅度增加,而且许多超市正在突破以食品为主的传统格局,向品种多样化发展。小型超市在业务上需要处理大量的库存信息,还要时刻更新产品的销售信息,不断添加商品信息,并对商品各种信息进行统计分析。因此,在超市管理中引进现代化的办公软件,实现超市庞大商品的控制和传输,从而方便销售行业的管理和决策,为超市和超市管理人员解除后顾之忧。
    1.2 技术方案开发和管理一个基于B/S模式的管理信息系统需要开发和利用高效率的网络资源,并且应该充分利用高技术含量的技术。本系统开发中使用了Java Server Pages和Java Bean。为了能将Java Server Pages、Java Bean以及Java Servlets三种技术有机结合起来,本系统的总体架构采用了MVC模式。

    我们可以只使用JSP构建电子商务系统,但如果想完成一个有效的应用程序并用于商业上,则需要综合Java Server Pages,Java Bean,以及Java Servlets三种技术相互补充的力量。这种情况下就必然要使用MVC模式,它把应用程序划分为独立的数据管理Model,表现形式View,和控制组件Controller,成为最先进的图形用户接口的基础。这些划分模块支持独立开发并且以重复使用组件。
    2 系统所有功能模块详细介绍根据实际购物流程,绘制系统流程图,是编写程序代码的逻辑依据。在系统的开发之初,作为开发者,我们查阅了很多资料,并参考现有电子商务模式,从顾客网上购物真实流程及需求考虑,最终找到了购物的基本流程作为程序编写的结构框架。始终模拟实际购物,摆明线索,划清模块做到了有路可循。
    系统处理流程图如下所示:

    2.1 人力资源信息管理
    2.1.1 人事部门信息管理部门信息添加:作为一个中型超市管理信息系统,其中会存在多个部门,部门信息有时会需要添加,部门信息添加模块可以方便快捷的实现部门添加。

    界面描述:部门管理员输入部门编号和部门名称,点击提交后将数据保存到Departments表中。
    部门信息修改:超市管理信息系统,其中存在多个部门,部门信息有时因为各种原因需要修改,部门信息修改模块可以方便快捷的实现部门修改。

    界面描述:部门编号从数据库中的表Departments中读出,部门名称由部门管理员修改。提交后更新表Departments。
    部门信息删除:中型超市管理信息系统中,可能因为企业改革等,现有的部门已经不在适合企业的管理,部门信息需要删除,部门信息删除模块可以灵活的实现部门删除。

    界面描述:部门管理员输入部门编号查询部门,点击删除链接后将删除所选部门,并更新Departments表中的数据。
    2.1.2 员工详细信息管理员工信息添加:任何一个企业都是由各种各样的人才组成的,一个中型超市管理信息系统对员工的信息管理是必不可少的,员工信息添加模块可以方便快捷的实现员工详细信息的添加。

    界面描述:系统从数据库的Employees表中读取数据,并显示在页面上。
    员工信息修改:企业中员工信息的变化是时常发生的,这就需要对员工的信息实时的做出改变,员工信息修改模块可以快捷实时的实现员工信息的修改。

    界面描述:部门管理员输入员工编号查询表Employees中的员工,员工编号从表Employees中读取,其他可以修改的信息也从表Employees中读取,点击修改后更新此表。
    员工信息删除:企业中员工信息的变化是时常发生的,有时因为员工的离职,或者各种其它原因,员工已经离开了该企业,这就需要对员工的信息相应的改变,员工信息删除模块可以快捷实时的实现员工信息的删除。

    界面描述:部门管理员输入员工编号从表Employees中查询员工信息并显示到页面上,点击修改按钮后将Employees表中的数据删除。
    员工批量删除:企业中多个员工信息需要删除时,逐一手工删除是一件很麻烦的事情,员工批量删除正是考虑以上原因而设计的,员工批量删除模块可以快捷,大量的实现多个员工信息的删除。

    界面描述:系统从Employees表中读取数据并显示到页面上,部门管理员点击删除链接删除一条相应的信息。
    员工信息查询:一个大型企业可能有成千上万的员工,当管理人员需要找某一个特定员工时,如果逐一用人眼查询,这几乎是不可能的,员工信息查询模块可以准确的查找特定的员工。

    界面描述:部门管理员输入员工编号并点击查询按钮,系统会查询Employees表中是否有该信息。
    2.1.3 员工考勤信息管理员工考勤信息添加:一个企业为了使员工高效,积极的实现企业下达的各种任务,这就需要各种监督措施,其中员工考勤信息管理正可以实现对员工的督促和鼓励作用,其次,也可以作为各项奖励的标准,员工考勤添加模块可以实现对每一个员工各个方面的考察。

    界面描述:部门管理员输入相应的信息,点击添加按钮后将数据保存到Evaluation表中。
    员工考勤信息修改:企业中员工考勤信息的有时会因为人为主观原因造成各种错误,这就需要对员工的考勤信息快速的做出修改,员工考勤信息修改模块可以及时的实现员工考勤信息的修改。

    界面描述:部门管理员输入考勤编号查询,系统读取Evaluation表中的数据并显示到页面上,管理员修改相应的数据,点击修改后更新表中的数据。
    员工考勤信息删除:企业中员工考勤信息的删除是时常发生的,有时因为员工的离职,或者各种过期考勤信息,以及各种冗余信息等,这就需要对员工的考勤信息及时的删除,员工考勤信息删除模块可以快捷实时的实现员工考勤信息的删除。

    界面描述:系统读取Evaluation表中的数据,显示到页面上,管理员点击删除后删除表中的数据。
    员工考勤信息查询:一个大型企业可能有成千上万的员工的考勤信息,每一个员工也可能有多个不同方面的考勤信息,当管理人员或者员工个人需要找某一个特定员工考勤信息时,如果逐一查询,这可定是不可能的,也是很不合理的,员工考勤信息查询模块可以准确的查找特定员工的考勤信息,或者特定员工的某一方面的考勤信息。

    界面描述:部门管理员输入考勤编号或者用户编号,点击查询后系统将查询Evaluation表中的数据并显示到页面上。
    2.2 公司财务信息管理
    2.2.1 员工工资信息管理员工工资信息添加:作为企业的一员,当付出劳动时,企业也一定要对他们做出回报,工资管理,就是企业对员工物质奖励的最好表示,员工工资添加模块可以快速的对企业所有员工的工资做出具体详细的管理。

    界面描述:部门管理员输入相应的信息,点击添加后将数据保存到Salary表中。
    员工工资信息修改:企业中工资管理偶尔也会发生各种错误,这就需要管理人员能及时的做出修改,员工工资修改模块可以准确的修改某一个具体员工的工资信息。

    界面描述:部门管理员输入员工工资编号查询Salary表,系统将查询的数据显示到页面上,管理员修改数据后更新Salary表中的数据。
    员工工资信息删除:当企业员工离职时,或者经过一段时间后,会发现员工工资表中一些信息时无用的,员工工资删除模块可以解决这样的问题。

    界面描述:部门管理员输入员工工资编号查询Salary表,系统将查询的结果显示到页面上,管理员点击删除删除表中的相应数据。
    员工工资信息的查询:当企业管理人员要准确的知道某一个员工,某一具体时间的工资是,就会发现工资查询时很必要的,工资查询正是针对这一问题提出的。

    界面描述:部门管理员输入员工工资编号查询Salary表,系统将查询的结果显示到页面上。
    2.2.2 商品销售业绩信息管理商品销售业绩显示:商品业绩显示可以很好的反应公司的运营情况。使得决策人员可以准确的做出相应的决策。

    界面描述:系统读取Checkout表中的数据并显示到页面上。
    商品销售业绩删除:随着时间的推移有很多的商品销售信息时冗余的,这就需要管理人员对各种信息经过判断之后做出删除。商品销售业绩删除功能能尽最大可能满足管理人员的需要。

    界面描述:系统读取Checkout表中的数据并显示到页面上,管理员点击删除后系统将Checkout表中的相应数据删除。
    2.2.3 商品采购费用信息管理商品采购费用显示:当公司采购部每采购一批商品是,都要将信息及时的反应到企业财务部,使得企业财务管理人员对企业帐目有章可循。一个企业为了使员工高效,积极的实现企业下达的各种任务,这就需要各种监督措施,其中员工考勤信息管理正可以实现对员工的督促和鼓励作用,其次,也可以作为各项奖励的标准,员工考勤添加模块可以实现对每一个员工各个方面的考察。

    界面描述:系统从Purchase表中读取所有数据并显示到页面上。
    2.3 商品采购部信息管理
    2.3.1 商品类型信息管理商品类型信息添加:为了对商品做出合理的管理商品类型信息的添加是很必要的。

    界面描述:部门管理员输入相应的信息,点击提交后系统将数据保存到ProType表中。
    商品类型信息修改:根据商品编号可以查询商品详细信息,然后修改商品的所有信息。

    界面描述:系统从ProType表中读取数据并显示到页面上,部门管理员修改数据后,点击修改按钮,系统将更新表中的数据。
    商品类型信息删除:根据商品类型编号可以删除该商品的类型信息。

    界面描述:部门管理员输入商品编号并点击查询按钮,系统将从ProType表中查询相应的数据并显示到页面上,管理员点击删除后将删除ProType表中的相应数据。
    2.3.2 商品详细信息管理商品信息添加:作为超市综合管理系统,商品信息的管理是很重要的每当采购部门采购到新的商品是商品信息就要增加。超市也可能因为其它原因增加商品信息,商品添加模块都可以做出快捷的解决方案。

    界面描述:部门管理员输入相应的信息,点击添加后将数据保存到Product表中。
    商品信息删除:当企业经营策略发生改变时,商品信息也会相应的发生改变,商品信息删除模块可以使商品信息跟随经营而改变。

    界面描述:系统将Product表中所有的商品信息显示到页面上,管理员点击删除后系统删除Product表中相应的数据。
    商品信息修改:商品信息的变化是瞬间千变万化的,同一个商品随时间的不同,它的具体信息也是不同的,只有实时的调整才能适应市场的变化,商品信息修改使该变化的最佳方案。

    界面描述:部门管理员输入商品编号查询Product表中相应的商品,系统将查询结果显示到页面上,管理员修改数据后点击修改按钮,系统将数据保存到Product表中。
    商品信息查询:在成千上万种商品种,如果人为寻找某一个商品肯定是不可能的,只有通过商品信息查询模块才能为用户或管理人员解决这个难题。

    界面描述:部门管理员输入商品编号查询Product表中相应的商品,系统将查询结果显示到页面上。
    2.3.3 商品供应商厂家信息管理商品供应商厂家信息添加:“诚信“是当前企业管理的管理,以诚信建立的企业与企业之间的关系是种巨大的财富,如何保留这种财富,创造这种财富,商品供应商厂家信息可以大量的存储这种信息。

    界面描述:部门管理员输入相应的数据,点击添加后将数据保存到Supplyer表中。
    商品供应商厂家信息修改:每一个企业的信息随时间都会有或多或少的改变,商品供应商厂家信息修改可以适应这种变化。

    界面描述:部门管理员输入供应商编号查询Supplyer表中的数据并显示到页面上,修改相应的数据,点击修改后将数据保存到Supplyer表中。
    商品供应商厂家信息删除:企业倒闭或者经营策略的改变,当它对超市商品的供应没有作用时,商品供应商厂家信息的删除是正常的。

    界面描述:部门管理员输入供应商编号查询Supplyer表中的数据并显示到页面上,点击删除后系统将Supplyer表中的相应数据删除。
    商品供应商厂家信息查询:

    界面描述:部门管理员输入供应商编号,点击查询后系统将查询Supplyer表中的数据,并将结果显示到页面上。
    2.3.4 商品供应商联系人信息管理商品供应商毕竟是一种抽象的信息,只有通过商品供应商联系人这种载体,才能充分的利用,商品供应商联系人管理可以完成如下任务:
    商品供应商联系人信息添加;

    界面描述:部门管理员输入相应的信息,点击添加后将数据保存到Saler表中。
    商品供应商联系人信息修改:

    界面描述:部门管理员输入联系人编号,点击查询按钮,系统查询Saler表中的数据,并将结果显示到页面上,管理员修改相应的数据,点击更新后将数据保存到Saler表中。
    商品供应商联系人信息删除:

    界面描述:部门管理员输入联系人编号,点击查询按钮,系统查询Saler表中的数据,并将结果显示到页面上,管理员点击删除后,系统将Saler表中的相应数据删除。
    2.3.5 商品采购信息管理商品是维系超市正常运行的必要条件,商品采购是维持这一活动必不可少的条件,商品采购信息管理可以高效的实现它,包含的功能如下:
    商品采购信息添加:

    界面描述:部门管理员输入相应的数据,点击添加后将数据保存到Purchase表中。
    商品采购信息修改:

    界面描述:系统读取Purchase表中的数据并显示到页面上,管理员修改数据后点击修改按钮,系统更新Purchase表中的相应数据。
    商品采购信息删除:

    界面描述:系统从Purchase表中读取数据并显示到页面上,部门管理员点击删除后,系统将删除Purchase表中的相应数据。
    2.4 商品销售部信息管理
    商品销售信息管理:作为一个超市正是为出售商品而存在的,因此销售管理显得尤为重要,商品销售模块正是它的重要组成部分。

    界面描述:系统从Product表中读取数据并显示到页面上,点击付账后会加入到Checkout表中。
    商品购物清单管理:每次购物后,如果结帐则系统自动生成购物清单。

    界面描述:系统从商品销售信息管理页面读取数据并显示到页面上,用户点击付账后将数据保存到Checkout表中。
    2.5 用户权限及个人密码修改用户权限修改:超市综合管理信息系统中,肯定存在各种不同角色,不同的角色就应该有不同的权限,而只有超级管理员才有角色赋予权利。

    界面描述:超级管理员点击相应的部门会进入相应的修改页面,更新Users表中的数据。
    用户密码的修改:为了系统的安全,用户的应该只有用户个人才能修改,这不仅保证了整个公司的利益也保护了个人隐私。

    界面描述:管理员填写相应的数据后,点击提交后将数据保存到Users表中。
    3 接口设计3.1 用户接口包括商品基本信息管理、进货管理、销售管理等管理界面,其中商品信息管理对商品信息的增,改,删除等设置;进货管理分供应商档案管理和供应商商品管理,增、改、删除供应商及其商品信息;销售管理提供销售时对商品的信息显示及修改。
    3.2 外部接口应用系统通过ODBC和数据库沟通。

    3.3 内部接口该系统适合windows操作系统,没有和其他软件的接口。
    4 运行设计4.1 运行模块组合 商品信息管理模块用来管理商品的一些基本信息,是本系统中数据管理的基本对象。管理超市的全部商品信息。销售管理模块提供销售时商品信息的确认与更新,是本系统的主要模块。销售模块提取数据库里商品的基本信息然后在销售成功时修改货架上商品的数量,当商品货架上的数量低于一定程度是,系统提示管理员,从仓库提取商品补充货架。仓库管理系统负责管理仓库的货物信息,管理人员通过仓库管理模块将仓库的商品转移到货架上。当仓库的货物数量下降时,管理人员通过进货管理联系供应商以采集相应商品。为了用户方便快捷的使用本系统,可以参考系统说明模块。

    商品信息的收集与修改功能: 商品信息管理模块,仓库管理模块商品数量更新功能: 销售管理模块,仓库管理模块查询,打印功能: 商品信息管理模块,销售管理模块,仓库管理模块
    5 系统数据结构设计5.1 逻辑结构设计要点主要逻辑结构如下:
    1.员工信息表

    包括的必填数据项:员工编号,员工姓名,员工性别;可选填数据项:员工籍贯,出生年月,学历,是否结婚,身份证号码,员工电话,员工地址,员工描述;说明:员工编号是唯一的员工标识,使此表的主键. 系统通过添加员工可以使用户登陆到系统相应的管理模块。如图5-1所示。

    2.商品类别表

    商品类别号、商品类别名称。说明:商品类别编号为主键 如图5-2所示。

    3.商品信息表

    商品编号、商品类别号、商品名称、商品单位、商品当前价格、商品进货价格、商品数量、商品描述。编号方法:商品的编号采用位数分类的方法,如图5-3所示。

    4.商品采购信息表

    商品采购信息编号、商品编号、商品名称、采购人员编号、供应商联系人编号、采购数量、采购时单位商品价格、采购时间、采购地点、采购描述、采购日期。说明:这张表标识的是商品采购信息的信息情况的外部信息,采购信息编号为该表的主键。如图5-4所示。

    5.商品销售信息

    商品编号、商品名称、商品单位、商品库存数量、商品当前价格。说明:这张表标识的是商品销售的内部信息列表,商品编号是该表的主键,它与商品信息一一对应。编号方法:商品编号采用自动生成方式。如图5-5所示。

    6.员工部门信息

    部门编号、部门名称。说明:这张表标识的是超市管理信息系统员工部门的信息列表,部门编号是该表的主键。编号方法:部门编号采用自动生成方式。如图5-6所示。

    7.员工考勤信息

    员工考勤编号、员工编号、考勤时间、考勤主题、考勤结果、考勤分数、考勤描述。说明:这张表标识的是超市管理信息系统员工考勤的信息列表,员工考勤编号是该表的主键。编号方法:员工考勤编号采用自动生成方式,员工编号与人事管理系统中员工编号一一对应。如图5-7所示。

    8.员工工资信息

    员工工资编号、员工编号、员工基本工资、员工季度奖金、员工年度奖金、员工鼓励奖金、员工发工资时间。说明:这张表标识的是超市管理信息系统员工工资的信息列表,员工工资编号是该表的主键。编号方法:员工工资编号采用自动生成方式,员工编号与人事管理系统中员工编号一一对应。如图5-8所示。

    9.商品供应商信息

    供应商编号、供应商名称、供应商地址、供应商邮编、供应商生产产品的名称。说明:这张表标识的是超市管理信息系统中商品采购模块中商品供应商的信息列表,供应商编号是该表的主键。编号方法:商品供应商编号采用自动生成方式。如图5-9所示。

    10.商品供应商联系人信息

    供应商联系人编号、联系人姓名、联系人性别、联系人职位、联系人公司名称、联系人爱好、联系人电话、联系人描述、联系人公司编号。说明:这张表标识的是超市管理信息系统中商品采购模块中商品供应商联系人的信息列表,供应商联系人编号是该表的主键。编号方法:商品供应商联系人编号采用自动生成方式。如图5-10所示。

    11.系统登陆用户信息

    用户编号、用户姓名、用户密码、用户登陆身份。说明:这张表标识的是超市管理信息系统中登陆到系统的用户的信息列表,用户编号是该表的主键,其中用户编号与员工信息表中的用户编号是一一对应的。如图5-11所示。

    6 经验与教训中小型超市综合管理信息系统的开发是在Window7平台上,以JSP+JavaBean为前台,采用SQL Server 2008作为数据库管理系统管理后台数据库。本系统是超市信息管理建设中必不可少的一部分,它实现了现代管理信息系统的大部分功能需要。使用本系统可以使企业管理更加方便快捷,合理的页面设计也使得这个企业用户充分享受到未来基于Internet管理信息系统的优越。本系统开发说明:

    功能完备
    在开发初期,查看了大量关于电子商务,管理信息系统,J2EE等方面的资料,同时借鉴了很多其他电子商务网站和管理信息的流程。经过总结,确定了满足需求分析的基本模块。系统总体设计上实现了整个系统模块的划分,系统主要包含5大模块,分别是:人事管理信息,企业财务管理,商品采购管理,商品销售管理,个人信息咨询,基本上实现了综合管理系统的所有功能。 
    界面友好
    系统用户登陆到管理页面后,每页有导航和引领的作用。系统根据用户角色的不同,直接进入不同的管理页面,同时导航条方便快捷的引导用户进行各种合理的操作。
    管理科学
    本系统一开始就从管理学的角度做出了详细细致的考虑,后来有参考了ERP,现代电子商务管理等,最后才做出了系统总体设计,同时在设计中也遵循现代企业管理的理念,因此可以讲该系统是较为科学的。

    这一次团队开发综合管理信息系统,从开始选择课题的困惑到最终完成了一个我们还算满意的作品,我们学到了很多很多东西。需求分析—>系统架构设计—>总体模块设计—>详细模块设计—>编码—>调试测试,按照这个步骤一步一步走过来,我们的进度可以说是相对比较慢的。后台管理部分就是在后期制作完成的。近1个月的不断磨练,我们最大的收获除了学到了真正可以应用的知识外,更重要的是激发了我们对Java和JSP的强烈兴趣。
    4 评论 63 下载 2018-10-04 21:03:49 下载需要13点积分
  • 基于Java实现的商品推荐系统

    一、介绍商品推荐是针对用户面对海量的商品信息而不知从何下手的一种解决方案,它可以根据用户的喜好,年龄,点击量,购买量以及各种购买行为来为用户推荐合适的商品。在本项目中采用的是基于用户的协同过滤的推荐算法来实现商品的推荐并在前台页面进行展示,我将会使用余弦相似度的度量方法来计算用户与用户之间相似性,最终将相似度较高的用户浏览的商品推荐给用户。
    二、目标
    商品推荐:根据不同用户之间的相似性来推荐给用户合适的商品
    一级类目管理:管理一级类目的相关功能
    二级类目管理:管理二级类目的相关功能
    商品管理:对商品进行上架,下架,修改信息
    管理员管理:管理管理员,用于商城后台的管理平台页面
    商城会员管理:管理商城会员,对商城页面的会员进行管理
    商城会员登录及注册:实现商城用户的登录功能以及注册功能

    三、采用的技术3.1 开发环境
    操作系统:Windows8.1
    IDE:eclipse
    Java版本:1.8
    数据库:mysql5.6.36
    服务器:tomcat8
    项目的构建工具:Maven

    3.2 后台框架
    spring-4.2.1:轻量级的IOC和AOP的容器框架
    springMVC-4.2.1表示层框架,负责匹配请求,处理请求,返回视图mybatis-3.3.1建立与数据库的会话druid-1.1.16为监控而生的数据库连接池

    四、推荐流程
    五、界面
    六、阶段性成果6.1 用户相似度计算功能以下为通过余弦相似度公式计算得出的用户与用户之间的相似度:
    具体的代码实现见 shopping/recommendate/util/RecommendUtils.java。
    1 2 0.9994891442833921 3 0.9510094685910491 4 0.61016991946945632 3 0.95149554552980992 4 0.61048178758052683 4 0.64080979076955366.2 余弦相似度的原理在三角形中,cos30°=二分之根三,cos60°=1/2。很明显,cos30°相比于cos60°更接近于 1,可以看出角度越接近于 0°,对应的余弦值越接近于 1,构成这个角度的两条边也就越接近于重合,也就可以认为这两条边越相似。
    在二维的平面向量中,两个向量之间夹角的余弦值公式为:

    假设两个向量a,b的坐标分别为(x1,y1)、(x2,y2),所以最终可以化简为:

    注:其中的x和y是两个不同的维度,在向量直角坐标系中可以认为是距x和y轴的距离,但是在实际的应用中,可以是把它理解为一个物体的任何一个可以衡量它与其他物体不同之处的属性…咳咳,还是说人话吧,在商品推荐中可以把向量 a 和向量 b 理解为两个不同的用户,把 (x1,y1) 理解为 a 用户对 x 商品的点击次数和对 y 商品的点击次数,再把对应的值带入上述公式即可求出两个用户之间的相似性,越接近于 1 ,说明两个用户的浏览行为越相似,就可以把一个用户浏览过的商品推荐给另一个没有浏览过该商品的用户,从而完成商品推荐。
    设向量 A = (A1,A2,A3,…,An), B = (B1,B2,B3,…,Bn),推广到多维,公式为:

    其中的 A1,A2,A3…就可以理解为该用户对不同的商品的点击量。
    6.3 计算出要推荐给用户的二级类目通过上一阶段对用户相似度之间的求解,可以获得与某个用户最相似的前N个用户,(假设需要被推荐商品的用户为X)然后在这N个用户的浏览记录中查找X没有浏览的类目,但是这N个用户浏览的二级类目的类目的id,作为推荐给用户的二级类目id。
    具体的代码实现见 shopping/recommendate/util/RecommendUtils.java。

    对用户的商品推荐在上一个阶段确定了要推荐给用户的二级类目之后,找出每个类目下点击量最高的商品推荐给用户。
    直观感受一下:
    1 评论 22 下载 2020-11-16 12:09:05 下载需要8点积分
  • 基于python的网易云音乐分析


    MacOS Sierra 10.12.1
    Python 2.7
    selenium 3.4.3
    phantomjs

    前言
    发现自己有时候比挖掘别人来的更加有意义,自己到底喜欢谁的歌,自己真的知道么?习惯不会骗你

    搭建爬虫环境1.安装seleniumpip install selenium# anaconda环境的可用conda install selenium# 网速不好的可用到https://pypi.python.org/pypi/selenium下载压缩包,解压后使用python setup.py install
    2.安装Phantomjs2.1 Mac版本步骤一下载包:去这里下载对应版本http://phantomjs.org/download.html步骤二解压:双击就行,用unzip这都无所谓步骤三切入路径:cd ~/Downloads/phantomjs-2.1.1-macosx/bin # 我下的路径的路径是download,版本不一,注意修改步骤四:chmod +x phantomjs步骤五: 配置环境,因为我装的的zsh,所以文件需要修改的是~/.zshrc这个文件,加上这句话export PATH="/Users/mrlevo/Downloads/phantomjs-2.1.1-macosx/bin/:$PATH",然后source ~/.zshrc 即可生效(没用zsh的同学,直接修改的文件时~/.bash_profile,添加内容和上述一致)查看是否生效:phantomjs -v # 有信息如 2.1.1 则生效mac若遇到问题请参考PhantomJS 安装
    2.2 Win版本官网http://phantomjs.org/下载PhantomJS解压后如下图所示:

    调用时可能会报错“Unable to start phantomjs with ghostdriver”如图:



    此时可以设置下Phantomjs的路径,同时如果你配置了Scripts目录环境变量,可以解压Phantomjs到该文件夹下。可参考Selenium with GhostDriver in Python on Windows - stackoverflow,整个win安装过程可参考在Windows下安装PIP+Phantomjs+Selenium],Mac和Linux/Ubuntu 下可参考[解决:Ubuntu(MacOS)+phantomjs+python的部署问题

    3. 测试安装是否成功# 进入python环境后执行如下操作# win下操作>>> from selenium import webdriver # pip install selenium>>> driver_detail = webdriver.PhantomJS(executable_path="F:\Python\phantomjs-1.9.1-windows\phantomjs.exe")>>> driver_detail.get('https://www.baidu.com')>>> news = driver_detail.find_element_by_xpath("//div[@id='u1']/a")>>> print news.text新闻>>> driver_detail.quit() # 记得关闭,不然耗费内存------------------------------------------------------------------------# mac下操作>>> from selenium import webdriver # pip install selenium>>> driver_detail = webdriver.PhantomJS()>>> driver_detail.get('https://www.baidu.com')>>> news = driver_detail.find_element_by_xpath("//div[@id='u1']/a")>>> print news.text新闻>>> driver_detail.quit() # 记得关闭,不然耗费内存爬取动态数据
    获取自己的id号,这个可以自己登陆自己的网易云音乐后获得,就是id=后面那个值



    构造爬取的id,因为我发现,每个人的id只要被获取到,他的歌单都是公开的!!!这就节省了自动登录的一步,而且,我还有个大胆的想法,哈哈哈,我还要搞个大新闻!这次先不说~

    墙裂推荐先阅读该博客掌握获取元素方法:Python爬虫 Selenium实现自动登录163邮箱和Locating Elements介绍
    # -*- coding: utf-8 -*-import tracebackfrom selenium import webdriverimport selenium.webdriver.support.ui as uifrom selenium.webdriver.common.desired_capabilities import DesiredCapabilitiesimport timeimport random# 存储为文本的子函数def write2txt(data,path): f = open(path,"a") f.write(data) f.write("\n") f.close()# 获取该id喜欢音乐的列表def catchSongs(url_id,url): user = url_id.split('=')[-1].strip() print 'excute user:',user driver = webdriver.PhantomJS()#,executable_path='/Users/mrlevo/phantomjs-2.1.1-macosx/bin/phantomjs') # 注意填上路径 driver.get(url) driver.switch_to_frame('g_iframe') # 网易云的音乐元素都放在框架内!!!!先切换框架 try: wait = ui.WebDriverWait(driver,15) wait.until(lambda driver: driver.find_element_by_xpath('//*[@class="j-flag"]/table/tbody')) # 等待元素渲染出来 try: song_key = 1 wrong_time = 0 while wrong_time < 5: # 不断获取歌信息,假定5次获取不到值,就判无值可获取,跳出循环 try: songs = driver.find_elements_by_xpath('//*[@class="j-flag"]/table/tbody/tr[%s]'%song_key) info_ = songs[0].text.strip().split("\n") if len(info_) == 5: info_.insert(2,'None') # 没有MV选项的进行插入None new_line = '%s|'%user+'|'.join(info_) song_key +=1 #new_line = "%s|%s|%s|%s|%s|%s|%s"%(user,info_[0],info_[1],info_[2],info_[3],info_[4],info_[5]) print new_line write2txt(new_line.encode('utf-8'),user) # mac写入文件需要改变字符,以id命名的文件,存储在执行脚本的当前路径下,在win下请去掉编.endcode('utf-8') except Exception as ex: wrong_time +=1 # print ex except Exception as ex: pass except Exception as ex: traceback.print_exc() finally: driver.quit()# 获取id所喜爱的音乐的urldef catchPlaylist(url): driver = webdriver.PhantomJS()#,executable_path='/Users/mrlevo/phantomjs-2.1.1-macosx/bin/phantomjs') # 注意填上路径 driver.get(url) driver.switch_to_frame('g_iframe') # 网易云的音乐元素都放在框架内!!!!先切换框架 try: wait = ui.WebDriverWait(driver,15) wait.until(lambda driver: driver.find_element_by_xpath('//*[@class="m-cvrlst f-cb"]/li[1]/div/a')) # 根据xpath获取元素 urls = driver.find_elements_by_xpath('//*[@class="m-cvrlst f-cb"]/li[1]/div/a') favourite_url = urls[0].get_attribute("href") except Exception as ex: traceback.print_exc() finally: driver.quit() # print favourite_url return favourite_urlif __name__ == '__main__': for url in ['http://music.163.com/user/home?id=67259702']: # 这里把自己的id替换掉,想爬谁的歌单都可以,只要你有他的id time.sleep(random.randint(2, 4)) # 随机休眠时间2~4秒 url_playlist = catchPlaylist(url) time.sleep(random.randint(1, 2)) catchSongs(url,url_playlist)

    不出意外的话,你的执行脚本的目录下会产生一个以你的id命名的文件,里面打开应该是这样的

    67259702|2|因为了解|None|04:08|汪苏泷|慢慢懂67259702|3|潮鳴り|None|02:37|折戸伸治|CLANNAD ORIGINAL SOUNDTRACK67259702|4|每个人都会|None|02:58|方大同|橙月 Orange Moon67259702|5|Don't Cry (Original)|MV|04:44|Guns N' Roses|Greatest Hits67259702|6|妖孽(Cover:蒋蒋)|None|02:58|醉影An|醉声梦影67259702|7|好好说再见(Cover 陶喆 / 关诗敏)|None|04:06|锦零/疯疯|zero67259702|8|好好说再见(cover陶喆)|None|03:34|AllenRock|WarmCovers ·早# 这边分别爬取的数据结构是: id|歌次序|歌名|是否有MV|时长|歌手|专辑
    Show数据-ROUND1
    接下来就是处理自己下好的自己的歌单了,为了方便起见,我在构造爬取代码的时候,已经构造的比较好了,这也就帮助大家减少了数据预处理的时间了,一般来说,数据不会那么干净的。

    我只是做了最简单的歌手词云的例子,数据比较丰富的情况下,自己处理吧,想做什么统计都可以,或许以后我会补上可视化相关的一些例子
    1. 自定义遮罩层版本# -*- coding: utf-8 -*-# 如果还不清楚词云怎么搞,请参考这里https://mp.weixin.qq.com/s/0Bw8QUo1YfWZR_Boeaxu_Q,或者自行百度,很简单的一个包import numpy as npimport PIL.Image as Imagefrom wordcloud import WordCloud, ImageColorGeneratorimport matplotlib.pyplot as plt# 统计词频# win的用户,把解码去掉即可,因为当时mac写入的文件有编码,所以读出来需要解码def statistics(lst): dic = {} for k in lst: if not k.decode('utf-8') in dic:dic[k.decode('utf-8')] = 0 dic[k.decode('utf-8')] +=1 return dic path = '67259702' # 自己路径自己搞定list_ = []with open(path,'r') as f: for line in f: list_.append(line.strip().split('|')[-2].strip())dict_ = statistics(list_)# the font from github: https://github.com/adobe-fontsfont = r'SimHei.ttf'coloring = np.array(Image.open("screenshot.png")) # 遮罩层自己定义,可选自己的图片wc = WordCloud(background_color="white", collocations=False, font_path=font, width=1400, height=1400, margin=2, mask=np.array(Image.open("screenshot.png"))).generate_from_frequencies(dict_)# 这里采用了generate_from_frequencies(dict_)的方法,里面传入的值是{‘歌手1’:5,‘歌手2’:8,},分别是歌手及出现次数,其实和jieba分词# 之后使用generate(text)是一个效果,只是这里的text已经被jieba封装成字典了image_colors = ImageColorGenerator(np.array(Image.open("screenshot.png")))plt.imshow(wc.recolor(color_func=image_colors))plt.imshow(wc)plt.axis("off")plt.show()wc.to_file('mymusic2.png') # 把词云保存下来

    2. 方块版本# -*- coding: utf-8 -*-# 稍微修改下参数,就是另一幅图,这是没有遮罩层的import numpy as npimport PIL.Image as Imagefrom wordcloud import WordCloud, ImageColorGeneratorimport matplotlib.pyplot as plt# 统计词频def statistics(lst): dic = {} for k in lst: if not k.decode('utf-8') in dic:dic[k.decode('utf-8')] = 0 dic[k.decode('utf-8')] +=1 return dic path = '67259702' # 自己路径自己搞定list_ = []with open(path,'r') as f: for line in f: list_.append(line.strip().split('|')[-2].strip())dict_ = statistics(list_)# the font from github: https://github.com/adobe-fontsfont = r'SimHei.ttf'coloring = np.array(Image.open("screenshot.png"))wc = WordCloud( collocations=False, font_path=font, width=1400, height=1400, margin=2, ).generate_from_frequencies(dict_)# 这里采用了generate_from_frequencies(dict_)的方法,里面传入的值是{‘歌手1’:5,‘歌手2’:8,},分别是歌手及出现次数,其实和jieba分词# 之后使用generate(text)是一个效果,只是这里的text已经被jieba封装成字典了image_colors = ImageColorGenerator(np.array(Image.open("screenshot.png")))plt.imshow(wc)plt.axis("off")plt.show()wc.to_file('mymusic2.png') # 把词云保存下来

    SHOW数据-ROUND2
    刚看到个好玩的,迫不及待的试了下,这是关于语种翻译的API接口,阿里云买的,0.01=1000条,买买买,买来玩玩试试自己歌曲语种

    # -*- coding:utf-8 -*-# 调用的阿里云的API接口实现语种翻译# API官网:https://market.aliyun.com/products/57124001/cmapi010395.html?spm=5176.730005.0.0.UrR9bO#sku=yuncode439500000import urllib, urllib2, sysimport ssldef Lang2Country(text): host = 'https://dm-12.data.aliyun.com' path = '/rest/160601/mt/detect.json' method = 'POST' appcode = 'xxxxx' # 购买后提供的appcode码 querys = '' bodys = {} url = host + path bodys['q'] = text post_data = urllib.urlencode(bodys) request = urllib2.Request(url, post_data) request.add_header('Authorization', 'APPCODE ' + appcode) # 根据API的要求,定义相对应的Content-Type request.add_header('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8') ctx = ssl.create_default_context() ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE response = urllib2.urlopen(request, context=ctx) content = response.read() if (content): # print(content) return content else: return None# 67259702|1|Claux - 水之畔(8lope Remix) (feat. 陶心瑶)|None|02:44|8lope|水之畔(feat. 陶心瑶) (8lope Remix)list_songs = []list_songwithsinger = []with open('67259702') as f: # 文件名写上次爬下来的 for line in f: line_split = line.split('|') list_songs.append(line_split[2]) list_songwithsinger.append(line_split[2]+line_split[5])# 调用接口进行语种识别dict_lang = {}for i in range(537): try: content = Lang2Country(list_songwithsinger[i]) lag_ = json.loads(content)['data']['language'] if lag_ not in dict_lang: dict_lang[lag_]=0 dict_lang[lag_] +=1 except: passprint dict_lang # {u'ru': 1, u'fr': 9, u'en': 111, u'zh': 259, u'pt': 21, u'ko': 8, u'de': 7, u'tr': 15, u'it': 47, u'id': 2, u'pl': 7, u'th': 1, u'nl': 10, u'ja': 17, u'es': 20}

    ok,数据准备好了,接下来可视化就好了!这次我用Echarts,换个口味的就不用云词了,来个统计效果好看点的!


    # 进入该网页:http://echarts.baidu.com/demo.html#pie-simple# 然后把里面的内容替换掉就行option = { title : { text: '哈士奇说喵喜欢的音乐', x:'center' }, tooltip : { trigger: 'item', formatter:'{b} : {c} ({d}%)' }, legend: { orient: 'vertical', left: 'left', data:['中文','英文','俄语','法语','葡萄牙语','韩语','德语','土耳其语','意大利语'] }, series : [ { name: '访问来源', type: 'pie', radius : '55%', center: ['50%', '60%'], itemStyle: { normal: {label:{ show:true, formatter:'{b} : {c} ({d}%)' }, }}, data:[ {value:259, name:'中文'}, {value:111,name:'英文'}, {value:1, name:'俄语'}, {value:9, name:'法语'}, {value:21, name:'葡萄牙语'}, {value:8, name:'韩语'}, {value:7, name:'德语'}, {value:15, name:'土耳其语'}, {value:47, name:'意大利语'}, {value:2, name:'印尼语'}, {value:7, name:'波兰语'}, {value:1, name:'泰语'}, {value:10, name:'荷兰语'}, {value:17, name:'日语'}, {value:20, name:'西班牙语'}, ], } ]};
    Pay Attention
    这里遇到的最大问题,就是网易云的网页竟然还iframe框来做!!!不切入那个内联框架连phantomjs都无能为力!!这是最值得注意的一点,即使你找对了元素,也可能获取不到值!
    如果是win的计算机,在 driver = webdriver.PhantomJS()里面填上phantomjs.exe的路径,上面抓取数据的代码里面有两个需要引擎需要填写路径
    如果有打印出字段,但是记录的数据为0KB,那么是文件没有写进去,对于win的用户,把代码写入的部门,编码方式去掉即可
    有些win的小伙伴反应路径都加载对了,但是还是找不到exe,那么请在路径前面加r比如 executable_path=r"F:\Python\phantomjs-1.9.1-windows\phantomjs.exe"

    结论
    果然一下子就看出是上个世纪九十年代的人(:,还有就是,音乐不分国界,就是动感~

    附录
    对照表

    2 评论 16 下载 2019-04-29 21:42:11 下载需要11点积分
  • 基于OpenCV的批量处理tiff图片黑边(QT环境)

    这几天基础OpenCV,练习写了一个去黑边程序,新手代码,记录一下。
    该方法为扫描线法,遇到非黑边内的(0,0,0)黑色时,用坐标排除(不在边界上跳过)。
    PS:这里还提供一种区域增长思路,找图片黑色区域,面积最大的区域为需要去除的黑边。
    基本思路
    遍历导入图片,遍历像素,找到黑边所在的矩形框坐标,剪切图片(一分为四)
    根据矩形坐标,计算新的地理位置信息
    删除带有黑边的图片和tfw文件信息
    打开和写入保存tif图片和tfw位置信息

    博客链接
    //传入图片 返回图片分割的图片 得到切点坐标QVector<Mat> separationTif(Mat &iplImg, Point2d &cutPnt){ QVector<Mat> vecMat; int width = iplImg.size().width; int height = iplImg.size().height; int begin_wid = 0, end_wid = 0, begin_heig = 0, end_heig = 0; //标记黑边 坐标 int i = 0, j = 0; int blackNumber = 0; for (j = 0; j < height; ++j) { for (i = 0; i < width; ++i) { int tmpb, tmpg, tmpr; tmpb = cvGet2D(&(IplImage)iplImg, j, i).val[0]; tmpg = cvGet2D(&(IplImage)iplImg, j, i).val[1]; tmpr = cvGet2D(&(IplImage)iplImg, j, i).val[2]; if (tmpb == 0 && tmpg == 0 && tmpr == 0) //这里RGB数字可以灵活变动 { ++blackNumber; } } } if (blackNumber == 0) //不存在黑边 { vecMat << iplImg; return vecMat; } else { for (j = 0; j < height; ++j) { bool flag = false; for (i = 0; i < width; ++i) { int tmpb, tmpg, tmpr; tmpb = cvGet2D(&(IplImage)iplImg, j, i).val[0]; tmpg = cvGet2D(&(IplImage)iplImg, j, i).val[1]; tmpr = cvGet2D(&(IplImage)iplImg, j, i).val[2]; if (tmpb == 0 && tmpg == 0 && tmpr == 0) { if (i > 0 && i < width - 1 && j > 0 && j < height - 1) ; else { flag = true; begin_heig = j; break; } } } if (flag) break; } for (j = height - 1; j >= begin_heig; --j) { bool flag = false; //判断 for (i = 0; i < width; ++i) { int tmpb, tmpg, tmpr; tmpb = cvGet2D(&(IplImage)iplImg, j, i).val[0]; tmpg = cvGet2D(&(IplImage)iplImg, j, i).val[1]; tmpr = cvGet2D(&(IplImage)iplImg, j, i).val[2]; if (tmpb == 0 && tmpg == 0 && tmpr == 0) { if (i > 0 && i < width - 1 && j > 0 && j < height - 1) ; else { flag = true; end_heig = j; break; } } } if (flag) break; } for (i = 0; i < width; ++i) { bool flag = false; for (j = 0; j < height; ++j) { int tmpb, tmpg, tmpr; tmpb = cvGet2D(&(IplImage)iplImg, j, i).val[0]; tmpg = cvGet2D(&(IplImage)iplImg, j, i).val[1]; tmpr = cvGet2D(&(IplImage)iplImg, j, i).val[2]; if (tmpb == 0 && tmpg == 0 && tmpr == 0) { if (i > 0 && i < width - 1 && j > 0 && j < height - 1) ; else { flag = true; begin_wid = i; break; } } } if (flag) break; } for (i = width - 1; i >= begin_wid; --i) { bool flag = false; for (j = 0; j < height; ++j) { int tmpb, tmpg, tmpr; tmpb = cvGet2D(&(IplImage)iplImg, j, i).val[0]; tmpg = cvGet2D(&(IplImage)iplImg, j, i).val[1]; tmpr = cvGet2D(&(IplImage)iplImg, j, i).val[2]; if (tmpb == 0 && tmpg == 0 && tmpr == 0) { if (i > 0 && i < width - 1 && j > 0 && j < height - 1) ; else { flag = true; end_wid = i; break; } } } if (flag) break; } if (begin_wid > 0 && begin_wid < (width - 1)) cutPnt.x = begin_wid; else cutPnt.x = end_wid; if (begin_heig > 0 && begin_heig < (height - 1)) cutPnt.y = begin_heig; else cutPnt.y = end_heig; if (cutPnt.x != 0 || cutPnt.y != 0) { //裁剪的中心点不在(0,0)时 剪切图像 cout << "裁剪的中心点为:" << endl; qDebug() << cutPnt.x << cutPnt.y; Mat img0 = Mat(iplImg, Rect(0, 0, cutPnt.x + 1, cutPnt.y + 1)); //左上 Mat img1 = Mat(iplImg, Rect(0, cutPnt.y, cutPnt.x + 1, height - cutPnt.y - 1)); //左下 Mat img2 = Mat(iplImg, Rect(cutPnt.x, 0, width - cutPnt.x - 1, cutPnt.y + 1)); //右上 Mat img3 = Mat(iplImg, Rect(cutPnt.x, cutPnt.y, width - cutPnt.x - 1, height - cutPnt.y - 1)); //右下 vecMat << img0 << img1 << img2 << img3; } else { qDebug() << "Do not have black_border"; vecMat << iplImg; } return vecMat; }}
    0 评论 3 下载 2020-12-03 18:49:03 下载需要8点积分
显示 0 到 15 ,共 15 条
eject