分类

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

资源列表

  • 基于VC++的画图板程序

    1. 概述1.1 简介使用VC开发平台,MFC框架实现一个画图程序,尽可能多的实现Windows自带的画图功能,并扩展其功能。
    1.2 功能需求1.2.1 基本绘图功能
    能够用鼠标操控方式,绘制直线、矩形、椭圆。
    在绘图时,选择绘制某种图像后(如直线),在画布中按住鼠标左键后移动鼠标,在画布中实时的根据鼠标的移动显示相应的图形。在松开鼠标左键后,一次绘图操作完成。
    能够在绘制一图形(如一条直线)前设置线的粗细、颜色。(以菜单方式)
    可以以矢量图方式保存绘制的图形。
    可以读取保存的矢量图形文件,并显示绘图的结果。

    界面友好的要求:

    有画直线、矩形、椭圆的工具箱。
    有颜色选择工具箱。
    对于当前选中的绘图工具,以“下沉”的形式显示。
    在状态栏中显示鼠标的位置。
    在鼠标移向一工具不动时,有工具的功能提示。
    在菜单上有当前选中的菜单项标识(即前面有小钩)
    可以用鼠标操作方式,通过“拖拽”方式,改变画布的大小。
    在画布大而外框小时,应有水平或垂直方向的滚动条。

    1.2.2 高级编辑功能
    具有Undo功能。
    可以用鼠标选中绘制的某一图形。被选中的图形符号有标识(参见Word,如一直线段,其两端点上加了两个小框;矩形上有8个小框点)。
    当鼠标靠近某一目标时,鼠标的形状发生改变
    修改被选中的图形。通过鼠标的“拖拽”,可以改变图形的位置、或大小。
    修改被选中图形的颜色、笔划的粗细。
    删除被选中的图形。
    可以使用鼠标“拖拽”一个虚矩形框,一次选择多个图形。
    可以使用 Ctrl 或Shift加鼠标左键选择多个图形对象。

    1.2.3 附加功能
    可选择打开或关闭工具栏。
    应用程序的标题栏上有程序的图标。
    将图形转换成位图文件的形式保存。
    在选择一个图形元素后(如直线),会有进一步选择线型或线宽的界面。
    仿Word,选择“线型”、“粗细”图标后,会出现进一步选择的选项卡。
    2.主要功能描述

    右键修改选中图形的颜色,粗细,线型,删除选中图形

    右键和鼠标调整图形

    对话框矢量修改所有图形

    3. 技术细节3.1 代码结构3.1.1 代码文件MFC自动生成的文件1个CHDrawPView1个HStroke2个Dialog(HStrokeEditDlg+HStrokeTextDlg)1个ToolBar(HColorBar)
    3.1.2 代码类HDrawPView文件只有一个类:CHDrawPView,该类集成自MFC的CScrollView,主要实现维护画布类CHDrawView和滚动功能。
    HStroke文件里包含目前所有的图形类信息,包括集成与MFC的CObject类的基类HStroke,以及集成自HStroke的具体图形类HStrokeLine(直线),HStrokeRect(矩形),HStrokeEllipse(椭圆),HStrokeText(文本),HStrokePoly(曲线)。
    HStrokeEditDlg文件只有一个类:HStrokeEditDlg,该类集成自MFC的CDialog类,主要用来编辑已有图形类,如下图所示:

    HStrokeTextDlg文件只有一个类:HStrokeTextDlg,该类集成自MFC的CDialog类,主要用来画文本时输入文本信息,如图所示:

    HColorBar类只有一个类:HColorBar类,该类集成自MFC的CToolBar类,呈现一个颜色框,方便用户在绘图时选择不同的颜色。
    3.2 SetROP2实现重绘在画图状态下,鼠标移动时既要擦除旧图形,又要绘制新图形。这里主要有两种实现方法:一是全部重绘,二是先擦除旧图形。
    如果使用矢量图全部重绘,频繁的绘图动作消耗很大,很容易造成屏幕闪动。但是如果将已有图形保存为位图,然后重绘的时候只要绘制位图即可,这样能避免闪动。第二种方法要考虑的就是擦除旧图形的问题,本程序使用SetROP2函数设置MASK的方式,每次绘图时采用非异或运算的方式擦除旧图形:
    pDC->SetROP2(R2_NOTXORPEN); //设置ROP2 DrawStroke(pDC); //画图擦除旧线(自定义函数) SetCurrentPoint(point); //设置新点的坐标(自定义函数) DrawStroke(pDC); //画新线(自定义函数)
    3.3 嵌套View实现画布 m_drawView = new CHDrawView();//创建画布View if (!m_drawView->CreateEx(WS_EX_LEFT | WS_EX_LTRREADING | WS_EX_RIGHTSCROLLBAR, AfxRegisterWndClass(CS_VREDRAW | CS_HREDRAW,LoadCursor(NULL,IDC_CROSS), (HBRUSH)GetStockObject(WHITE_BRUSH),NULL),///白色画布 "",WS_CHILDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, m_tracker.m_rect.left,m_tracker.m_rect.top, m_tracker.m_rect.right-1,m_tracker.m_rect.bottom-1, this->m_hWnd,NULL)){ TRACE0("Failed to create toolbar\n"); return -1; // fail to create } m_drawView->SetDocument((CHDrawDoc*)m_pDocument);//传递CDocument给新View m_drawView->ShowWindow(SW_NORMAL); m_drawView->UpdateWindow(); //设置背景View颜色为灰色 SetClassLong(m_hWnd,GCL_HBRBACKGROUND,(long)GetStockObject(GRAY_BRUSH));
    3.4 鼠标靠近目标时突出显示在鼠标移动的时候,OnMouseMove函数会遍历已有图形,判断鼠标所在点是否属于已有图形范围,如果是,则高亮显示该图形。
    高亮显示的方法比较简单,只要增加CRectTracker即可,而判断当前点是否属于某图形比较有意思:
    3.4.1 判断一点是否属于矩形HStrokeRect使用用MFC的CRect类的IsPointIn方法,当鼠标在矩形边框附近时,认为该点属于HStrokeRect。如图,实线矩形表示HStrokeRect。外矩形为外面的虚线矩形,内矩形为里面的虚线矩形:

    BOOL HStrokeRect::IsPointIn(const CPoint &point){ //矩形左上角x坐标 int x1 = m_points.GetAt(0).x < m_points.GetAt(1).x ? m_points.GetAt(0).x : m_points.GetAt(1).x; //矩形左上角y坐标 int y1 = m_points.GetAt(0).y < m_points.GetAt(1).y ? m_points.GetAt(0).y : m_points.GetAt(1).y; //矩形右下角x坐标 int x2 = m_points.GetAt(0).x > m_points.GetAt(1).x ? m_points.GetAt(0).x : m_points.GetAt(1).x; //矩形右下角y坐标 int y2 = m_points.GetAt(0).y > m_points.GetAt(1).y ? m_points.GetAt(0).y : m_points.GetAt(1).y; //构建外矩行和内矩形 CRect rect(x1,y1,x2,y2), rect2(x1+5,y1+5,x2-5,y2-5); //如果在外矩形内并在内矩形外 if(rect.PtInRect(point) && !rect2.PtInRect(point)) return TRUE; else return FALSE;}
    3.4.2 判断一点是否属于线段首先判断一点是否属于这条线段所属的直线,根据直线的判定公式y1/x1 = y2/x2得到x1y2-x2y1=0,但是在画图中应该在直线附近就能选中,所以在本程序中:|x1y2-x2y1| < 偏差,然后判断该点是否属于这条线段。
    //计算该点到线段HStrokeLine的两个顶点的线段(x1,y1), (x2,y2) int x1 = point.x - m_points.GetAt(0).x; int x2 = point.x - m_points.GetAt(1).x; int y1 = point.y - m_points.GetAt(0).y; int y2 = point.y - m_points.GetAt(1).y; //计算判断量x1*y2 - x2*y1 int measure = x1*y2 - x2*y1; //误差允许范围,也就是直线的“附近” int rule = abs(m_points.GetAt(1).x - m_points.GetAt(0).x) +abs(m_points.GetAt(0).y - m_points.GetAt(1).y); rule *= m_penWidth;//将线宽考虑进去 //属于直线 if(measure < rule && measure > -rule){ //判断该点是否属于这条线段 if(x1 * x2 < 0) return TRUE;; } return FALSE;
    3.4.3 判断一点是否属于椭圆根据椭圆的定义椭圆上的点到椭圆的两个焦点的距离之和为2a,首先计算出椭圆的a, b, c,然后计算出椭圆的两个焦点。
    针对某个点,首先根据点坐标和两个焦点的坐标计算出该点到椭圆焦点的距离,然后减去2a,如果在“附近”,则认为其属于HStrokeEllipse,否则不属于。
    //计算椭圆的a, b, c int _2a = abs(m_points.GetAt(0).x - m_points.GetAt(1).x); int _2b = abs(m_points.GetAt(0).y - m_points.GetAt(1).y); double c = sqrt(abs(_2a*_2a - _2b*_2b))/2; //计算椭圆的焦点 double x1,y1,x2,y2; if(_2a > _2b){//横椭圆 x1 = (double)(m_points.GetAt(0).x + m_points.GetAt(1).x)/2 - c; x2 = x1 + 2*c; y1 = y2 = (m_points.GetAt(0).y + m_points.GetAt(1).y)/2; } else{//纵椭圆 _2a = _2b; x1 = x2 = (m_points.GetAt(0).x + m_points.GetAt(1).x)/2; y1 = (m_points.GetAt(0).y + m_points.GetAt(1).y)/2 - c; y2 = y1 + 2*c; } //点到两个焦点的距离之和,再减去2a //distance(point - p1) + distance(point - p2) = 2*a; double measure = sqrt((x1 - point.x)*(x1-point.x) + (y1 - point.y)*(y1-point.y) ) + sqrt( (point.x - x2)*(point.x - x2) + (point.y - y2)*(point.y - y2)) - _2a; //计算椭圆的“附近” double rule = 4*m_penWidth; if(measure < rule && measure > -rule) return TRUE; else return FALSE;
    3.5 文档序列化MFC提供了良好的序列化机制,只要在类定义时加入DECLARE_SERIAL宏,在类构造函数的实现前加入IMPLEMENT_SERIAL宏,然后实现Serialize方法即可。本程序即使用该方法序列化:首先在CHDrawDoc类实现Serialize方法,保存画布大小和所有图形信息:
    void CHDrawDoc::Serialize(CArchive& ar){ if (ar.IsStoring()) { //保存时,首先保存画布高和宽,然后序列化所有图形 ar<<m_cavasH<<m_cavasW; m_strokeList.Serialize(ar); } else { //打开时,首先打开画布高和宽,然后打开所有图形 ar>>m_cavasH>>m_cavasW; m_strokeList.Serialize(ar); }}
    m_strokeList.Serialize(ar);这一句很神奇,Debug追踪的时候会发现,容器类会自动序列化容器内的元素数量,并调用每个元素的序列化方法序列化,所以还需要对每个图形元素实现序列化,以HStrokeLine为例:在HStrokeLine的类声明中:
    class HStrokeLine : public HStroke {public: HStrokeLine(); DECLARE_SERIAL(HStrokeLine)
    然后在HStrokeLine的构造函数实现前:
    IMPLEMENT_SERIAL(HStrokeLine, CObject, 1)HStrokeLine::HStrokeLine(){ m_picType = PIC_line;}
    最后实现HStrokeLine的序列化函数,因为这里HStrokeLine集成自HStroke类而且没有特殊的属性,而HStroke类实现了Serialize函数,所以HStrokeLine类不需要实现Serilize方法,看一下HStroke的Serialize方法即可:
    void HStroke::Serialize(CArchive& ar){ if(ar.IsStoring()){ int enumIndex = m_picType; ar<<enumIndex<<m_penWidth<<m_penColor; m_points.Serialize(ar); } else{ int enumIndex; ar>>enumIndex>>m_penWidth>>m_penColor; m_picType = (enum HPicType)enumIndex; m_points.Serialize(ar); }}
    3.6 打开保存导出文档序列化实现以后,程序的打开和保存功能就已经完成了。但是从序列化方法可以看出,打开和保存的都是矢量图形,所以这里实现了一个导出为BMP图像的方法,导出:
    //保存文件对话框,选择导出路径 CFileDialog dlg(FALSE, "bmp","hjz.bmp"); if(dlg.DoModal() != IDOK){ return ; } CString filePath = dlg.GetPathName(); // CClientDC client(this);//用于本控件的,楼主可以不用此句 CDC cdc; CBitmap bitmap; RECT rect;CRect r; GetClientRect(&rect); int cx = rect.right - rect.left; int cy = rect.bottom - rect.top; bitmap.CreateCompatibleBitmap(&client, cx, cy); cdc.CreateCompatibleDC(NULL); //获取BMP对象 CBitmap * oldbitmap = (CBitmap* ) cdc.SelectObject(&bitmap); //白色画布 cdc.FillRect(&rect, CBrush::FromHandle((HBRUSH)GetStockObject(WHITE_BRUSH))); //画图 for(int i = 0; i < GetDocument()->m_strokeList.GetSize(); i ++){ GetDocument()->m_strokeList.GetAt(i)->DrawStroke(&cdc); } cdc.SelectObject(oldbitmap); ::OpenClipboard(this->m_hWnd); ::EmptyClipboard(); ::SetClipboardData(CF_BITMAP, bitmap); ::CloseClipboard(); HBITMAP hBitmap = (HBITMAP)bitmap; HDC hDC; int iBits; WORD wBitCount; DWORD dwPaletteSize=0, dwBmBitsSize=0, dwDIBSize=0, dwWritten=0; BITMAP Bitmap; BITMAPFILEHEADER bmfHdr; BITMAPINFOHEADER bi; LPBITMAPINFOHEADER lpbi; HANDLE fh, hDib, hPal,hOldPal=NULL; hDC = CreateDC("DISPLAY", NULL, NULL, NULL); iBits = GetDeviceCaps(hDC, BITSPIXEL) * GetDeviceCaps(hDC, PLANES); DeleteDC(hDC); if (iBits <= 1) wBitCount = 1; else if (iBits <= 4) wBitCount = 4; else if (iBits <= 8) wBitCount = 8; else wBitCount = 24; GetObject(hBitmap, sizeof(Bitmap), (LPSTR)&Bitmap); bi.biSize = sizeof(BITMAPINFOHEADER); bi.biWidth = Bitmap.bmWidth; bi.biHeight = Bitmap.bmHeight; bi.biPlanes = 1; bi.biBitCount = wBitCount; bi.biCompression = BI_RGB; bi.biSizeImage = 0; bi.biXPelsPerMeter = 0; bi.biYPelsPerMeter = 0; bi.biClrImportant = 0; bi.biClrUsed = 0; dwBmBitsSize = ((Bitmap.bmWidth * wBitCount + 31) / 32) * 4 * Bitmap.bmHeight; hDib = GlobalAlloc(GHND,dwBmBitsSize + dwPaletteSize + sizeof(BITMAPINFOHEADER)); lpbi = (LPBITMAPINFOHEADER)GlobalLock(hDib); *lpbi = bi; hPal = GetStockObject(DEFAULT_PALETTE); if (hPal) { hDC = ::GetDC(NULL); hOldPal = ::SelectPalette(hDC, (HPALETTE)hPal, FALSE); RealizePalette(hDC); } GetDIBits(hDC, hBitmap, 0, (UINT) Bitmap.bmHeight, (LPSTR)lpbi + sizeof(BITMAPINFOHEADER) +dwPaletteSize, (BITMAPINFO *)lpbi, DIB_RGB_COLORS); if (hOldPal) { ::SelectPalette(hDC, (HPALETTE)hOldPal, TRUE); RealizePalette(hDC); ::ReleaseDC(NULL, hDC); } fh = CreateFile(filePath, GENERIC_WRITE,0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL); if (fh == INVALID_HANDLE_VALUE) return ; bmfHdr.bfType = 0x4D42; // "BM" dwDIBSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + dwPaletteSize + dwBmBitsSize; bmfHdr.bfSize = dwDIBSize; bmfHdr.bfReserved1 = 0; bmfHdr.bfReserved2 = 0; bmfHdr.bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) + (DWORD)sizeof(BITMAPINFOHEADER) + dwPaletteSize; WriteFile(fh, (LPSTR)&bmfHdr, sizeof(BITMAPFILEHEADER), &dwWritten, NULL); WriteFile(fh, (LPSTR)lpbi, dwDIBSize, &dwWritten, NULL); GlobalUnlock(hDib); GlobalFree(hDib); CloseHandle(fh);
    3.7 友好用户界面菜单项选中和工具栏图标下沉。该功能的实现非常简单,而且用户体验很好,以当前所画的图形为例:第一步:增加3个菜单项名称 ID直线 ID_DRAW_LINE椭圆 ID_DRAW_ELLIPSE矩形 ID_DRAW_RECT第二步:在工具栏上增加3个工具栏项,注意ID要和上面的三个ID相同。第三步:在CHDrawDoc类的ClassWizard中增加消息响应函数,分别为以上三个ID增加COMMAND和UPDATE_COMMAND_UI的Handler,COMMAND的Handler就是针对按下工具栏按钮或菜单项的响应函数,而UPDATE_COMMAND_UI则是显示菜单栏时执行的操作,有点类似OnDraw。以直线为例,ID_DRAW_LINE的COMMAND的Handler为OnDrawLine
    void CHDrawDoc::OnDrawLine() { //设置当前画图的图形类型为直线 m_picType = PIC_line;}ID_DRAW_LINE的UPDATE_COMMAND_UI的Handler为OnUpdateDrawLine:void CHDrawDoc::OnUpdateDrawLine(CCmdUI* pCmdUI) { //如果当前画图类型为直线,设置菜单项前加对号,工具栏项下沉 pCmdUI->SetCheck(PIC_line == m_picType);}
    3.8 右键菜单修改选中图形的属性实现方法如下:第一步:在资源视图中增加一个菜单第二步:在CHDrawView中增加右键菜单响应函数OnRButtonDown:
    void CHDrawView::OnRButtonDown(UINT nFlags, CPoint point) { //检查所有处于选中状态的图形,可以有多个 CHDrawDoc *pDoc = GetDocument(); m_strokeSelected.RemoveAll();//首先清空旧数据 for(int i = 0; i < pDoc->m_strokeList.GetSize(); i ++){ if(pDoc->m_strokeList.GetAt(i)->IsHightLight()) m_strokeSelected.Add(pDoc->m_strokeList.GetAt(i)); } //显示右键菜单 CMenu rmenu; rmenu.LoadMenu(IDR_MENU_SET);//加载资源中的菜单IDR_MENU_SET ClientToScreen(&point);//需要坐标转换 rmenu.GetSubMenu(0)->TrackPopupMenu(TPM_LEFTALIGN, point.x, point.y, this); //因为这里的rmenu是局部变量,所以必须Detach掉 rmenu.Detach(); CView::OnRButtonDown(nFlags, point);}
    第三步:增加菜单响应函数,这里以删除当前所选图形为例:
    void CHDrawView::OnPicDelete() { //获取存储数据的文档类 CHDrawDoc *pDoc = GetDocument(); //移除所有处于选中状态的图形 int i = 0, j = 0; for(; i < m_strokeSelected.GetSize(); i ++){ //这里的j没有归0,是有原因的,可以很有效的提高效率 //遍历复杂度为两个数组的和 for(; j < pDoc->m_strokeList.GetSize(); j ++){ if(m_strokeSelected.GetAt(i) == pDoc->m_strokeList.GetAt(j)){ delete pDoc->m_strokeList.GetAt(j); pDoc->m_strokeList.RemoveAt(j); break; } } } //如果没有处于选中状态的图形,则不需要刷新。 if(i > 0) Invalidate();}
    3.9 撤销和恢复操作MFC提供了默认的撤销和恢复的ID,但是并没有提供默认实现,本程序的思路是,定义一个数组和一个数组索引,每执行一个操作,就把当前状态存储到数组中,并把数组索引加1。撤销时,把索引减一的数组元素恢复到当前文档,恢复时,把索引加一的数组元素恢复到当前文档。在程序中的步骤为:第一步:定义数组,数组索引和备份,恢复函数:
    CObArray m_backup; int m_backup_index; void ReStore(BOOL backward); void BackUp();void CHDrawDoc::BackUp(){ //备份操作,有利有弊。简单,节省内存,序列化有变时不需修改;产生文件占据磁盘 CString fileName; fileName.Format("hjz%d", m_backup.GetSize()); OnSaveDocument(fileName); //这里使用Insert而不是Add是因为恢复是并没有删除 m_backup.InsertAt(m_backup_index++, NULL, 1);}void CHDrawDoc::ReStore(BOOL backward){ m_backup_index -= backward ? 1 : -1;//撤销还是恢复 //…把数组元素恢复到当前文档 OnOpenDocument(m_backup.GetAt(m_backup_index-1));}
    第二步:添加撤销和恢复菜单项,并添加消息句柄:
    void CHDrawDoc::OnEditUndo() { ReStore(TRUE); UpdateAllViews(NULL);}void CHDrawDoc::OnEditRedo() { ReStore(FALSE); UpdateAllViews(NULL);}
    第三步:在每次对文档的修改操作之前,调用GetDocument()->Backup()
    3.10 使用鼠标拖拽选中多个图形
    首先自HStrokeRect类继承一个HStrokeSelect类,实现DrawStroke方法:
    void HStrokeSelect::DrawStroke(CDC *pDC){ m_penColor = RGB(255,0,0); m_penWidth = 1; m_penStyle = PS_DASH; HStrokeRect::DrawStroke(pDC);}
    然后在LButtonUp时选中区域内的图形,并将HStrokeSelect对象删除:
    //Step0.2 选择框 else if(PIC_select == m_stroke->m_picType){ bool refresh = false;//是否需要刷新 CRect rect(m_stroke->m_points.GetAt(0),m_stroke->m_points.GetAt(1)); for(int i = 0; i < pDoc->m_strokeList.GetSize(); i ++){ //是否在所框区域内 if(rect.PtInRect(pDoc->m_strokeList.GetAt(i)->m_points.GetAt(0)) && rect.PtInRect(pDoc->m_strokeList.GetAt(i)->m_points.GetAt(1))){ //设置选中状态 pDoc->m_strokeList.GetAt(i)->m_bSelected = true; refresh = true;//标志需要刷新 } } if(refresh) Invalidate();//刷新 delete m_stroke;//释放内存 }
    3.11 直线HStrokeLine的Tracker只显示两个Point
    CRectTracker在选中状态下会显示8个点,这对于矩形是合理的,而对于线段来讲,只要显示两个点就可以了,这里重载了CRectTracker类的Draw方法:
    void HStrokeTracker::Draw(CDC* pDC) const{ CRect rect; //一般图形用CRectTracker的方法即可 CRectTracker::Draw(pDC); //对于直线 if((m_picType == PIC_line) && ((m_nStyle&(resizeInside|resizeOutside))!=0)){ UINT mask = GetHandleMask(); for (int i = 0; i < 8; ++i) { if (mask & (1<<i)) { int p1, p2; //直线斜率小于0,即左上+右下 if(m_picExtra == 0) { p1 = 1, p2 = 4; } //直线斜率大于0,即左下+右上 else{ p1 = 2, p2 = 8; } if( ((1<<i) == p1) || ((1<<i) == p2)){ GetHandleRect((TrackerHit)i, &rect); pDC->FillSolidRect(rect, RGB(0, 0, 0)); } else{ GetHandleRect((TrackerHit)i, &rect); pDC->FillSolidRect(rect, RGB(255, 255, 255)); } } } }}
    3.12 键盘控制重载PreTranslate函数,响应Ctrl+A,Delete,Shift+(UP|DOWN|LEFT|RIGHT)键盘事件,实现全选,删除所选,控制所选多个图形移动功能。
    CHDrawDoc *pDoc = GetDocument(); BOOL deleted = FALSE; int i, x, y; if (pMsg->message == WM_KEYDOWN) { switch (pMsg->wParam){ //删除 case VK_DELETE: for(i = 0; i <pDoc->m_strokeList.GetSize(); i ++){ if(pDoc->m_strokeList.GetAt(i)->m_bSelected){ pDoc->m_strokeList.RemoveAt(i--); deleted = TRUE; } } if(deleted) Invalidate(); break; //全选 case 'A': case 'a': if(::GetKeyState(VK_CONTROL) < 0){ for(int i = 0; i <pDoc->m_strokeList.GetSize(); i ++){ pDoc->m_strokeList.GetAt(i)->m_bSelected = TRUE; } Invalidate(); } break; //移动 case VK_UP: case VK_DOWN: case VK_LEFT: case VK_RIGHT: x = (pMsg->wParam==VK_RIGHT) - (pMsg->wParam==VK_LEFT); y = (pMsg->wParam==VK_DOWN) - (pMsg->wParam==VK_UP); //Shift键加速移动 if(::GetKeyState(VK_SHIFT) < 0){ x *= 8; y *= 8; } for(int i = 0; i <pDoc->m_strokeList.GetSize(); i ++){ if(pDoc->m_strokeList.GetAt(i)->m_bSelected){ pDoc->m_strokeList.GetAt(i)->Move(x,y); } } Invalidate(); break; } }
    3.13 对话框控制
    HStrokeEditDlg对话框,实现对所有图形的矢量化编辑,可以直接修改图形的坐标,颜色,宽度,删除图形等操作。
    3.14 动画程序图标第一步:在资源中增加5个图标资源ICON:IDI_ICON1~IDI_ICON5第二步:在CMainFrame中增加变量HICON m_icons[5],并在构造函数中加载资源:
    m_icons[0] = LoadIcon(AfxGetApp()->m_hInstance, MAKEINTRESOURCE(IDI_ICON1)); m_icons[1] = LoadIcon(AfxGetApp()->m_hInstance, MAKEINTRESOURCE(IDI_ICON2)); m_icons[2] = LoadIcon(AfxGetApp()->m_hInstance, MAKEINTRESOURCE(IDI_ICON3)); m_icons[3] = LoadIcon(AfxGetApp()->m_hInstance, MAKEINTRESOURCE(IDI_ICON4)); m_icons[4] = LoadIcon(AfxGetApp()->m_hInstance, MAKEINTRESOURCE(IDI_ICON5));
    第三步:在CMainFrame的OnCreate函数中加载资源,并启动计数器:
    SetClassLong(m_hWnd, GCL_HICON, (LONG)m_icons[0]); SetTimer(WM_ICONALT, 1000, NULL); //设置计数器每秒
    第四步:在计数器函数OnTimer中增加修改图标的代码:
    static int iconIndex = 1; //静态变量计算第几个图标 if(nIDEvent == WM_ICONALT){ SetClassLong(m_hWnd, GCL_HICON, (LONG)m_icons[iconIndex]); iconIndex = (++iconIndex) % 5; } CFrameWnd::OnTimer(nIDEvent);
    3.15 LButtonDown流程如果Ctrl键没有被按下,遍历图形数组,如果有Track的图形,执行Track操作
    Step1:If Ctrl键没有被按下For 每个图形 If 该图形被选中If Track 移动图形 标记b_Track为真,表示当前是Track而不是绘图EndIf EndIf EndForEndIfStep2:For每个图形,If当前鼠标点在其范围内 If Ctrl键被按下 该图形的选中状态取反 Else 没有按下Ctrl键 选中当前图形 EndIfElse 当前鼠标点不再其范围内 If Ctrl键被按下 无操作,认为是用户多选时的误操作Else Ctrl键没有被按下取消该图形的选中状态 EndIf EndIfEnd ForStep3:If b_Track为假,表示当前是绘图而不是TrackStep3.1. 设置捕获鼠标SetCapture();Step3.2. 加入新图形m_stroke = pDoc->NewStroke(); Step3.3. 设置起点m_stroke->SetCurrentPoint(point); Step4. 设置文件已经修改状态EndIf3.16 LButtonUp流程If 用户点下鼠标后就松开,等于没有画 删除指针,释放内存ElseIf 画图类型为选择框 For 每个图形 If 该图形在选择框内 设置状态为选中 EndIf EndFor 删除指针,释放内存Else 画图 设置当前鼠标坐标 加入图形 备份EndIf3.17 MouseMove流程If 当前处于捕获状态(参考LButtonDown) 重画图形Else For 每个图形 If 当前鼠标坐标在该图形内 设置该图形高亮状态为真 Else 设置该图形高亮状态为假 EndIf EndForEndIf4. 总结4.1 Tricks4.1.1 子View和父View公用一个Doc本程序使用了两个View,CHDrawPView中创建了CHDrawView,而为了在CHDrawView中使用CHDrawDoc对象,需要将CHDrawPView的Doc传给CHDrawView。这个需求只要为CHDrawView增加一个方法,将m_pDocument指针传过去即可。
    但是这样就引来另外一个问题,在CView析构的时候,它会去析构m_pDocument,这样CHDrawDoc就会被CHDrawView和CHDrawPView一共析构两次,出现错误,为了解决这个问题,在传递m_pDocument的时候,用另外一个CDocument指针储存CHDrawView的m_pDocument,然后在CHDrawView析构函数中,再将m_pDocument修改回去。
    4.1.2 在类中获取其它类的句柄在CMainframe中:
    SDI中获取View ((CMainFrame*)AfxGetApp()->m_hMainWnd)->GetActiveView();SDI中获取View CMainFrame::GetActiveView()SDI中获取Doc((CMainFrame*)AfxGetApp()->m_hMainWnd)->GetActiveDocument();MDI中获取ViewAfxGetApp()->m_pMainWnd)->GetActiveFrame()->GetActiveView();MDI中获取DocAfxGetApp()->m_pMainWnd)->GetActiveFrame()->GetActiveDocument();MDI中获取View GetActiveFrame()->GetActiveView()MDI中获取View MDIGetActive()->GetActiveView()在CxxxDoc类中:MDI中获取View GetFirstViewPosition();MDI中获取View GetNextView()在CxxxView类中:SDI中获取Frame getMainFrame AfxGetApp()->m_hMainWnd;SDI中获取Frame getMainFrame CWnd::GetParentFrame()SDI中获取Frame getMainFrame AfxGetMainWnd()SDI中获取Doc GetDocument()MDI中获取Doc GetDocument();4.1.3 CRectTracker用法CRectTracker是MFC提供的一个很好用的类,简单易用。使用步骤:第一步:声明CRectTracker变量
    CRectTracker m_tracker;
    第二步:初始化CRectTracker的区域m_rect和样式m_style
    m_tracker.m_rect.SetRect(0,0,GetDocument()->m_cavasW, GetDocument()->m_cavasH); m_tracker.m_nStyle=CRectTracker::resizeOutside;
    第三步:override OnSetCursor方法:
    CPoint point; //Step1. get cursor position GetCursorPos(&point); //Step2. convert point from screen to client ScreenToClient(&point); if(m_tracker.HitTest(point) >= 0){ //Step3. set cursor, **notice, use nHitTest instead of return of tracker m_tracker.SetCursor(pWnd, nHitTest); …
    第四步:在OnLButtonDown函数中调用HitTest检测并用Track函数跟踪
    int hit = m_tracker.HitTest(point); switch(hit){ case 2: case 5: case 6: if(m_tracker.Track(this,point)){ //step1. cavas reset GetDocument()->m_cavasH = m_tracker.m_rect.bottom; GetDocument()->m_cavasW = m_tracker.m_rect.right; //step2. scroll or not CRect clientRect; GetClientRect(&clientRect); SetScrollSizes(MM_TEXT, CSize(m_tracker.m_rect.Width()+10, m_tracker.m_rect.Height()+10)); m_drawView->MoveWindow(m_tracker.m_rect.left, m_tracker.m_rect.top, m_tracker.m_rect.right,m_tracker.m_rect.bottom); GetDocument()->BackUp();//备份 Invalidate(); } }
    使用时容易出现的问题:如果在调用CRectTracker的Track方法之前调用了SetCapture函数,会发现Track方法失效。因为SetCapture方法会捕获鼠标事件,而Track则需要独立处理鼠标事件,两个函数争夺鼠标活动的处理权,并以Track的失败告终。
    3.1.4 内存泄露内存泄露问题发生的概率非常高,MFC的Debug功能对内存泄露的检测虽然算不上完美,但是基本够用了,使用F5启动调试,然后尽可能多的执行操作,关闭后在Debug窗口显示调试结构,如果有内存泄露,则会出现以下类型的信息:
    Detected memory leaks!Dumping objects ->afxtempl.h(370) : {1208} normal block at 0x00376880, 40 bytes long. Data: < . > BE 00 00 00 2E 00 00 00 AC 00 00 00 A1 00 00 00 E:\code\less01\HDraw\HDrawDoc.cpp(131) : {1202} client block at 0x00376770, subtype 0, 48 bytes long.a HStrokeLine object at $00376770, 48 bytes longafxtempl.h(370) : {960} normal block at 0x00376708, 40 bytes long. Data: < ~ > 92 00 00 00 1E 00 00 00 7E 00 00 00 A8 00 00 00 E:\code\less01\HDraw\HDrawDoc.cpp(131) : {954} client block at 0x003765B0, subtype 0, 48 bytes long.a HStrokeLine object at $003765B0, 48 bytes longafxtempl.h(370) : {723} normal block at 0x00376548, 40 bytes long. Data: <Q [ w > 51 00 00 00 5B 00 00 00 07 01 00 00 77 00 00 00 E:\code\less01\HDraw\HDrawDoc.cpp(131) : {717} client block at 0x00377768, subtype 0, 48 bytes long.a HStrokeLine object at $00377768, 48 bytes longafxtempl.h(370) : {422} normal block at 0x00377910, 40 bytes long. Data: << # Y > 3C 00 00 00 23 00 00 00 E4 00 00 00 59 00 00 00 E:\code\less01\HDraw\HDrawDoc.cpp(131) : {419} client block at 0x00377800, subtype 0, 48 bytes long.a HStrokeLine object at $00377800, 48 bytes longObject dump complete.
    双击相应的信息就能定位到未释放内存的申请地址,然后考虑应该在什么地方释放。
    0 评论 0 下载 2019-07-15 21:47:35 下载需要10点积分
  • 《WINDOWS黑客编程技术详解》配套资源下载

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

    书籍封面(反面)

    目录

    第1篇 用户篇

    第1章 开发环境

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

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

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

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

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

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

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

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

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

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

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

    第2篇 内核篇

    第12章 开发环境

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

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

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

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

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

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

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

    附录 函数一览表

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

    一、项目简介现今较火的抖音上有一个十分有趣的特效,其可以自动检测出人脸并且放置墨镜和烟卷,鉴于此,想自己实现动手实现以下该特效的制作。
    二、工作环境Python 3.6,opencv+Dlib,Windows操作系统,pycharm
    三、流程
    从摄像头获取视频流,并转换为一帧一帧的图像,然后将图像信息传递给opencv这个工具库处理,返回灰度图像。
    程序启动后,根据监听器信息,使用一个while循环,不断的加载视频图像,然后返回给opencv工具呈现图像信息。
    创建一个键盘事件监听,按下”d”键,则开始执行面部匹配,并进行面具加载(这个过程是动态的,你可以随时移动)。
    面部匹配使用Dlib中的人脸检测算法来查看是否有人脸存在。如果有,它将为每个人脸创建一个结束位置,眼镜和烟卷会移动到那里结束。
    然后我们需要缩放和旋转我们的眼镜以适合每个人的脸。我们将使用从Dlib的68点模型返回的点集来找到眼睛和嘴巴的中心,并为它们之间的空间旋转。
    在我们实时获取眼镜和烟卷的最终位置后,眼镜和烟卷从屏幕顶部进入,开始匹配你的眼镜和嘴巴
    假如没有人脸,程序会直接返回你的视频信息,不会有面具移动的效果。默认一个周期是4秒钟。然后你可以通过”d”键再次检测。
    退出程序按下q键。

    四、关键代码
    创建面具加载服务类DynamicStreamMaskService及其对应的初始化属性:
    读取摄像头视频流并转化为图象:
    实现人脸定位函数,及眼镜和烟卷的定位:
    接下来实现画图函数:
    创建启动函数start:

    五、运行结果运行后,首先会打开摄像头,然后按下键盘d键,就会看到墨镜和烟卷从屏幕上方出来,自动定位到眼睛和嘴巴。参考结果如下:图片经过马赛克处理了。
    注意代码中需要调用shape_predictor_68_face_landmarks.dat人脸库,需要下载,然后在代码调用处中指定路径即可。
    0 评论 0 下载 2019-07-11 14:00:18 下载需要15点积分
  • 基于移动网络通讯行为的用户风险识别

    一、实验内容参加“基于移动网络通讯行为的用户风险识别”大赛,本次大赛以模拟的语音通话、短信收发、网站及App访问记录等移动网络使用行为为基础,参赛队伍需要通过数据挖掘技术和机器学习算法,构建识别风险用户的预测模型,判别用户属于风险用户的可能性。从而为各行业提供风控保障,助力新时代大数字生态的健康有序发展。
    二、实验主要思想与方法本实验主要根据用户的通话记录、短信记录、网站和APP访问记录的信息,对用户进行分类和预测。在实验方法介绍分为三部分,第一部分为数据集训练与可视化,第二部分介绍特征提取的主要策略,在第三部分将介绍此次实验使用的LightGBM模型。
    2.1 数据集训练与可视化2.1.1 用户通话记录用户通话记录数据

    通话号码长度分布

    不同通话类型所占比例

    打入和打出电话所占比例

    打入和打出电话不同长度出现次数

    打入和打出电话号码不同头部出现次数

    每日活跃次数

    打入和打出情况下每天的活跃次数

    5种不同类型下每天的活跃次数

    通话号码不同长度出现次数

    已给风险用户的通话号码长度出现次数

    通话号码头部出现次数(仅截取主要部分)

    已给风险用户通话号码头部出现次数(仅截取主要部分)

    2.1.2 用户短信记录用户短信记录数据

    短信号码长度分布

    短信号码不同头部出现次数

    已给风险用户的短信号码头部出现次数

    短信号码不同长度出现次数

    已给风险用户的短信号码长度出现次数

    接收/发送短信情况下不同短信头部出现次数

    接收/发送短信情况下不同短信长度出现次数

    2.1.3 用户网站/App访问记录用户网站/App访问记录数据

    用户访问网站/APP分别占的比例

    两种类型每日活跃次数

    2.2 特征提取经过对可视化结果的分析,对特征进行如下处理:

    通话记录:

    voice_time:将end_time、start_time转为时间格式并计算通话时长(end_time-start_time);voice_date:取start_time的前两位
    短信记录:sms_date:取start_time的前两位。
    对已给特征使用常规的提取方法,如取:sum、mean、min、max、median等。
    将提取的特征与in_out、call_type、wa_type等进行结合。
    多变量进行结合,如:
    voice_feature[‘voice_cnt_peruniquecall’]= voice_feature[‘voice_opp_num_unique’]/voice_feature[‘voice_date_cnt’],求每天有通话的号码数量等。

    经过以上特征处理,最终得到如下特征:

    feature,importancesms_opp_head_max_in,92voice_len_mean_in,70sms_opp_len_mean_in,57voice_cnt_peruniquecall,56wa_type1_date_mean,54wa_dura_sum_percnt,50wa_type0_namecnt_perday,50sms_opp_head_min_in,49voice_head_mean_in,47sms_opp_head_mean_in,41sms_opp_len_mean,39voice_opp_head_min,39voice_headinunique_perday,37voice_head_mean_type1,36wa_type0_dura_perday,36wa_down_flow_median,35voice_time_median,35voice_date_mean_type1,34voice_timesum_peruniquecall,34voice_head_mean_out,33sms_start_time_mean,33voice_time_min,33voice_start_mean_in,33wa_type1_dura_perday,33voice_head_max_type1,32wa_namecnt_perday,32sms_callunique_perday,31voice_len_max_in,31voice_start_mean_out,30sms_calluniquein_mean_perday,30sms_calluniquein_perday,30sms_len_perday,30voice_headoutunique_perday,29voice_time_mean_out,29voice_len_mean_type1,29wa_type1_date_cnt,29wa_up_flow_median,28wa_type1_namecnt_perday,28voice_head_unique_in,28wa_type0_namedura_perday,28wa_type0_date_mean,27voice_headout_perday,27voice_timeoutmean_percall,26voice_headin_perday,26voice_opp_head_mean,26sms_callcnt_perday,26wa_visit_date_cnt,26wa_type1_namevicnt_perday,26wa_type0_dura_mean,25voice_opp_head_max,25voice_date_cnt_in,25wa_type1_namedura_perday,25wa_name_count_uinque,24wa_type0_visitcnt_mean,24voice_time_max,24sms_opp_num_count_in,23wa_type0_date_cnt,23voice_headmean_peruniquecall,23wa_type0_namevicnt_perday,23wa_type1_namevicntall_perday,23voice_time1mean_percall,23wa_visit_cnt_mean,23wa_type1_dura_mean,23sms_opp_head_max,22voice_opp_len_max,22wa_visit_dura_median,22sms_head_unique_perday,22voice_cntoutsum_perday,22voice_opp_unique_in,22wa_down_flow_mean,22wa_type1_downflow_mean,22sms_start_time_mean_in,21voice_time_mean_type1,21wa_visitdura_perday,21wa_up_flow_std,21voice_time_mean_in,21sms_opp_len_sum,21wa_namedura_perday,20sms_start_time_mean_out,20wa_type0_up_persec,20wa_type1_visitcnt_all,20wa_type0_visitcnt_perday,20voice_timeoutunique_percall,20voice_len_mean_out,20wa_up_flow_max,20wa_type0_flow_mean,19voice_cntoutmean_perday,19voice_timeoutsum_perday,19voice_head_peruniquecall,19wa_type1_upflow_mean,19sms_calluniquemeanout_perday,19sms_calluniquemeanin_perday,19wa_type1_visitcnt_perday,19voice_time_std,19wa_visitcnt_perday,19wa_type1_down_percnt,19wa_flow_sum_perday,19sms_opp_head_min,19wa_type0_downflow_all,18voice_cntinsum_perday,18wa_type1_flow_mean,18sms_calluniqueout_mean_perday,18voice_numcnt3mean_perday,18wa_type1_name_unique,18sms_opp_head_mean_out,17wa_down_flow_std,17voice_timemean_peruniquecall,17voice_lenmean_peruniquecall,17voice_cnt_perday,17wa_type0_name_unique,17sms_opp_head_mean,17voice_timeout_percall,16voice_opp_unique_out,16wa_type0_down_perday,16wa_type0_namevicntall_perday,16wa_flow,16sms_date_cnt_in,16wa_upflowcnt_perday,16voice_timeinunique_percall,16sms_opp_len_max,15wa_up_flow_mean,15wa_visit_dura_std,15sms_date_cnt,15wa_visit_dura_mean,15voice_numcnt1mean_perday,15voice_opp_len_mean,15voice_date_mean_type2,15voice_len_max_out,15wa_namevicnt_perday,15sms_date_mean_in,14voice_lensum_peruniquecall,14voice_timeoutsum_percall,14voice_time_sum_out,14wa_upflow_perday,14voice_timeoutmean_perday,14voice_time_mean_type3,14wa_type1_visitcnt_mean,14wa_type0_upflow_mean,14voice_time_sum_type3,13wa_downflowcnt_perday,13wa_type1_up_perday,13wa_flow_sum_percnt,13sms_date_cnt_out,13wa_down_flow_max,13voice_timemean_percall,13sms_calluniquecntin_perday,13voice_timein_percall,13voice_date_mean_out,12voice_time_sum_in,12voice_date_count_type1,12voice_time_sum_type1,12voice_date_count_type3,12wa_type1_up_persec,12voice_head_mean_type2,12voice_time1_percall,12voice_time2_percall,12voice_time3_percall,12voice_len_peruniquecall,12wa_type0_upflow_all,12wa_flow_sum_persecond,12wa_type0_down_percnt,12voice_opp_len_unique,11wa_type0_visitcnt_all,11wa_type0_up_perday,11sms_calluniqueout_perday,11voice_time3mean_percall,11wa_down_flow_sum,11voice_timeinmean_percall,11wa_visit_cnt_std,11voice_date_mean_type3,11voice_date_cnt_out,10wa_up_flow_sum,10wa_upflowdura_persec,10voice_time2mean_percall,10voice_cntinmean_perday,10voice_head_mean_type3,10sms_opp_head_min_out,10wa_visit_cnt_median,10wa_visit_cnt_sum,10voice_timeinsum_perday,10wa_type0_downflow_mean,9wa_type1_down_perday,9sms_opp_num_unique_in,9wa_visit_dura_sum,9sms_opp_num_count_unique,8voice_opp_count_all,8sms_calluniquecntout_perday,8voice_date_count_type2,8sms_date_mean_out,8voice_date_cnt,8voice_time_mean_type2,8voice_time_mean,8voice_opp_num_unique,8voice_numcnt1_perday,8voice_numcnt2mean_perday,8sms_opp_num_count_out,8wa_visit_cnt_max,8wa_type1_upflow_all,7voice_opp_count_out,7voice_numcnt2_perday,7wa_downflowdura_persec,7voice_headsum_peruniquecall,7wa_visit_dura_max,7voice_timeinsum_percall,7voice_date_mean_in,6wa_downflow_perday,6sms_opp_len_mean_out,6voice_time_sum,6sms_opp_num_count_all,6voice_time_percall,6voice_head_unique_out,6voice_num_cnt_type1,6voice_num_cnt_type3,6voice_opp_head_unique,5sms_opp_num_unique_out,5wa_type1_downflow_all,4voice_len_sum_out,4sms_opp_head_max_out,4voice_numcnt3_perday,4sms_opp_head_unique,3voice_timeinmean_perday,3voice_head_max_type3,3voice_time_sum_type2,2voice_head_max_type2,2voice_len_sum_in,2voice_opp_count_in,2voice_num_cnt_type2,0

    在特征工程中,删除了一些低分的特征,最终确定维数为228。
    2.3 LightGBMLightGBM与其他流行算法采用depth-wise的叶子生长策略不同,其使用的是带深度限制的leaf-wise的叶子生长策略。与depth-wise相比,leaf-wise算法收敛得更快。但leaf-wise的一个缺点就是:参数选择不当可能会产生比较深的决策树从而产生过拟合。因此LightGBM在Leaf-wise上增加了一个最大深度限制,在保证高效率的同时防止过拟合。
    在调参方面,由于LightGBM使用的是leaf-wise算法,因此调节使用的是num_leaves控制模型复杂度,大致设置为num_leaves=2^max_depth。另外,min_data_in_leaf参数用于处理过拟合的问题,设置大可以防止过拟合,但需要注意可能会导致欠拟合。
    由于特征过多,必须要考虑多过拟合的处理,所以设置较小的num_leavea和max_depth以避免过深的树,同时要设置合适的min_data_in_leaf避免过拟合或欠拟合。
    以下为参数设置:
    lgb_params = { 'boosting_type': 'gbdt', 'objective': 'binary', 'is_training_metric': False, 'learning_rate': 0.08, 'num_leaves':16, 'max_depth':6, 'min_data_in_leaf':40, 'verbosity':-1, 'is_training_metric':False, 'feature_fraction': 0.8, 'bagging_fraction': 0.8, 'bagging_freq':1, 'is_unbalance':True}三、实验结果通过特征提取和调整模型参数,提交结果前最终线下测试结果为0.809,线上测试结果为0.760,最终排名142/211。
    比赛结果截图:

    四、心得体会本次实训对我来说实际上是一次很大的考验。因为先前既没有python的基础,也没有上过数据挖掘理论课,所以一开始对相关的理论概念一无所知。
    在这样的情况下,我从网上的教程一步步开始学习,包括学习python的网站以及在同学推荐下看数据挖掘和机器学习的网课,慢慢摸索。尽管这样,在初赛阶段,写出来的代码和测试结果仍然惨不忍睹(线上测试分数只有0.39)。
    后来,在同学的帮助下(包括模型讲解和编码的指导)和参考了助教提供的baseline,逐渐把分数提高,最终有这样的成绩。
    可以说现在我依然对数据挖掘相关的理论知识一知半解,仅仅停留在照着葫芦画瓢的阶段。但至少在这个过程中我逐渐熟悉了python的使用,以及一些很实用的python库如numpy、pandas等,还算是有些收获。
    0 评论 0 下载 2019-06-25 18:08:34
  • 基于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 功能截图校园论坛主页

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

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

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

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

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

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

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

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

    点击右上角的“退出论坛”按钮,即退出登录状态,回到论坛主页。
    5 评论 155 下载 2018-10-05 22:38:31 下载需要16点积分
  • Palette-based Photo Recoloring论文算法重现

    一、使用方法直接打开index.html从相册中选择合适图片上传,程序将自动计算调色板。待计算完成,可点击调色板编辑变换的目标颜色。编辑完后,点击CONFIRM按钮开始重着色。
    二、算法实现2.1 调色板计算
    将RGB颜色空间均匀分成$16\times 16\times 16$个bins,统计图像中属于各个bin的颜色个数。每个bin的RGB空间均值为其代表色 (注: 原文为Lab空间均值,但求该均值较复杂,故使用了差别不大的RGB均值)。
    对bin进行聚类,使用的方法是改进的K-means算法。
    在聚类前,规定黑色为调色板颜色之一,这样可避免生成的调色板中有很多暗色。
    将聚类得到的调色板颜色按照Lab亮度升序排序。

    2.2 重着色
    单个的颜色变换可以看成颜色在L通道与a、b通道单独变换的组合。
    L通道的变换由对调色板亮度线性插值得到。
    ab通道的变换(左下图)由颜色在Lab空间内平移得到。其中对于超出Lab边界的情况做了特殊处理
    一组颜色变换可以看做是若干个单独颜色变换的组合(右下图),文章给出了使用径向基函数分配权重的方法:

    $$f(x)=\sum_i^k \omega _i(x) f_i(x)$$ $$\omega _i(x)=\sum_j^k\lambda_{ij}\phi(\parallel x-C_j\parallel)$$ $$\phi(r)=e^{-\frac{r^2}{2\sigma_r^2}}$$ <div style="text-align:center"> <img src="img/report/p1.png" height=200>   <img src="img/report/p1.png" height=200> </div>
    三、主要函数及说明


    函数
    位置
    说明




    Color.lab2rgb(InputColor)
    color.js
    将RGB颜色转换为Lab颜色($RGB\to XYZ\to Lab$)


    Color.rgb2lab(InputColor)
    color.js
    将Lab颜色转换为RGB颜色


    Color.labBoundary(pin, pout)
    color.js
    在Lab空间内,求颜色pin与pout连线与Lab边界的交点(二分查找)


    Palette.palette()
    palette.js
    统计图像颜色属于各个bin的颜色个数


    Palette.kmeansFirst()
    palette.js
    聚类,由统计好的bins数据得到调色板


    Palette.kmeans()
    palette.js
    调整上一步聚类得到的颜色,直到调色板不再变化


    Palette.colorTransformSingleL(l)
    palette.js
    根据调色板颜色亮度,确定新图像亮度的变化


    Palette.colorTransformSingleAB(ab1,ab2,L,x)
    palette.js
    在Lab空间某一亮度的层内,根据论文方法,由$ab1\to ab2$这一对颜色变换,对x做a、b通道的颜色变换


    Palette.colorTransform(colors1,colors2)
    palette.js
    综合几组颜色变换的结果,得到新的图片



    Gallery<div style="text-align:center"><img src="img/gallery/p1.png" height=400><img src="img/gallery/p2.png" height=400></div><div style="text-align:center"><img src="img/gallery/p3.png" height=400><img src="img/gallery/p4.png" height=400></div>---<div style="text-align:center"><img src="img/gallery/p5.png" height=400><img src="img/gallery/p6.png" height=400></div><div style="text-align:center"><img src="img/gallery/p7.png" height=400><img src="img/gallery/p8.png" height=400></div>---<div style="text-align:center"><img src="img/gallery/p9.png" height=400><img src="img/gallery/p10.png" height=400></div>
    0 评论 1 下载 2019-06-18 13:31:16
  • 基于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 系统展示登陆主界面

    系统管理员登录主界面

    查看管理员信息界面

    查看停车场信息界面

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

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

    查看出入信息界面

    添加停车记录信息界面

    查看停车记录备份界面

    手机端的查询界面

    手机端的显示界面
    3 评论 100 下载 2018-10-05 22:27:42 下载需要18点积分
  • 基于WIN32 API实现黄金矿工游戏单人版

    一、什么是设计文档游戏类型是什么?游戏有哪些功能?相关数学公式是什么?
    描述一个游戏的所有功能,这就是设计文档,也叫需求说明。真正的设计文档,并不是我写的这个样子,应该由策划来写(俗称“案子”)。我写的这篇,有流程图、有分类,条理清晰,基本上和真实代码完全对应,已经接近伪码了。
    二、游戏状态图
    三、游戏功能设计1.开屏

    显示内容:
    程序启动后,显示初始化图片,计时结束,进入菜单界面。
    逻辑处理:
    控制图片从左至右显示。

    2.菜单

    显示内容:
    显示菜单背景图片,显示“开始”按钮。
    逻辑处理:
    检测鼠标移动。当鼠标移动到按钮上,更改按钮图片。
    检测鼠标单击。当按下按钮后,初始化游戏数据,开始“地图加载”动画。

    3. 地图加载动画

    显示内容:
    显示背景图片,进度条,显示当前关卡、目标金钱数量。
    逻辑处理:
    控制进度条移动。

    4. 游戏中

    显示内容:
    显示人物图片,地面背景。
    显示当前金钱数量,目标金钱数量,剩余时间。
    显示叉子。
    显示金子、石头。
    显示炮的数量。
    显示爆炸动画。
    逻辑处理:
    控制叉子摆动,伸出,收回。
    检测按键“下”,按下后,叉子伸出。
    检测叉子是否碰到物品,碰到物品后,叉子收回,物品跟随叉子移动。
    当物品移动到地面位置,清除物品,增加金钱。
    胜负判断。
    道具使用:
    检测按键“上”,按下后,清除当前所抓物品,播放爆炸动画。
    判断玩家属性:如果有“体力”道具,增加叉子收回速度。
    判断玩家属性:如果有“魔法”道具,增加物品价值。

    5.游戏过关

    显示内容:
    显示过关图片。
    逻辑处理:
    计时结束,进入“购买道具”状态。

    6.购买道具

    显示内容:
    道具按钮:炮,体力,魔法。
    “下一关”按钮。
    逻辑处理:
    检测鼠标移动。当鼠标移动到按钮上,更改按钮图片。
    点击“炮”,增加炮的数量。
    点击“体力”,增加叉子收回速度。
    点击“魔法”,设置玩家属性:有魔法道具。
    点击“下一关”,加载地图,进入下一关游戏。

    7.游戏失败

    显示内容:
    显示失败图片。
    逻辑处理:
    计时结束,进入“菜单”状态。

    8.游戏通关

    显示内容:
    显示通关图片。
    逻辑处理:
    计时结束,进入“菜单”状态。

    四、叉子坐标系统叉子所在位置为坐标原点,所在位置垂线为X轴。向左摆动,旋转角度A大于0;向右摆动,旋转角度A小于0。其中,x,y是屏幕坐标系统。叉子坐标系统示意图:

    五、碰撞检测怎样检测叉子碰到了物品?在叉子端口处,设定一个圆形区域。如果这个圆与物品碰撞,则叉子碰到了物品。示意图如下:

    其中,圆心坐标A(50, 0) ,半径14。
    物品的检测范围也是圆形区域,示意图如下:

    六、地图数据物品属性表



    ID
    价值
    名称
    检测半径(像素)
    移动速度(像素)




    0
    500
    金子
    32
    4


    1
    150
    金子
    24
    12


    2
    50
    金子
    16
    20


    3
    15
    石头
    24
    12


    4
    5
    石头
    16
    20


    5
    600
    钻石
    16
    20



    地图物品数据
    目前只制作了3张地图。
    第一关地图,过关金钱数量:700



    ID
    横坐标
    纵坐标




    0
    50
    110


    1
    100
    270


    2
    200
    370


    1
    380
    370


    2
    480
    340


    0
    550
    150


    3
    190
    190


    4
    390
    260


    5
    120
    380



    第二关,过关金钱数量:1000



    ID
    横坐标
    纵坐标




    1
    50
    110


    4
    100
    270


    2
    200
    370


    3
    380
    370


    4
    480
    340


    5
    450
    400


    0
    550
    150


    1
    190
    190


    2
    390
    260



    第三关,过关金钱数量:2000



    ID
    横坐标
    纵坐标




    4
    50
    110


    1
    100
    270


    0
    200
    370


    0
    380
    370


    3
    480
    340


    4
    550
    150


    2
    190
    190


    4
    390
    260


    5
    460
    300



    游戏截图

    2 评论 69 下载 2018-10-04 21:41:13 下载需要6点积分
  • 基于Java的开心农场

    1 概述构思如下
    前端用户交互系统,包括用户注册和登录,农场概况、土地操作。作物操作等。用户注册时要有相应的用户名、密码、提示问题、答案,登陆后可以修改用户部分相关信息。农场主要信息包括金币数、经验值、好友排行,涉及到相关数据库的存储。对土地的操作主要是耕地、开垦,增加经验同时减少金币。作物的种植、除草、浇水、施肥,对应着相关数据库数据更新。金币的主要来源应该是以卖果实增加的金币。 后端包括后台数据库管理、进程调度。对于金币、经验、土地数、成熟时间等都应有详细记录,主体以时间的记录推进,以金币数和经验值数作为判断标准,进行相应的进程调度。
    1.1 需求规定1.1.1 对功能的规定


    功能
    输入
    处理
    输出




    登录;找回忘记密码
    在登录页面输入的用户名、密码;提示问题答案
    信息验证,并判断是否弹出农场界面;
    在登录页面提示登录结果;用户基本信息修改页面


    浏览农场
    单击农场按钮;对农场的拖动操作
    弹出农场界面
    弹出农场界面


    购买商品
    点击商店小图片进入商店界面;在商店页面选择的商品种类、输入的数量
    用户金币值减少,用户包裹商品种类、数量增加
    显示商品种类名称+商品数量,提示购买结果,显示用户金币减少


    播种
    在农场界面对土地进行的操作;种子种类名称+播种的土地编号
    在界面中替换图片
    在农场界面的土地上发生变化


    作物生长
    作物阶段生长时间+作物生长状态
    在界面中替换图片;计算作物生长时间、生长状态
    在农场界面的土地上发生变化


    拖动、翻地、收获、(不能放虫、放杂草)
    在农场界面对土地进行的操作;操作编号
    在界面中替换图片
    在农场界面的土地上发生变化


    收获果实,放仓库
    在农场界面对土地进行的操作
    在界面中替换图片;将商品种类及其数量加在数据库中;用户经验的变化
    在农场界面的土地上发生变化,仓库中果实的种类及其数量增加


    卖果实
    在仓库页面选择的商品种类、输入的数量
    将商品种类及其数量在数据库中减去;用户金钱的增加
    仓库中的商品种类及其数量减少


    开垦土地
    在农场界面对土地进行的操作
    在界面中替换图片
    在农场界面的土地上发生变化


    查看包裹;包裹内物品的使用
    单击包裹按钮;点击使用按钮
    弹出包裹显示种类,数量列表
    弹出包裹;对包裹内的物品进行操作


    分页显示好友信息
    点击好友面板可弹出好友列表界面
    经验排行,金钱排行,查询好友功能
    相应显示操作结果



    1.1.2 系统功能层次模块图
    1.1.2.1 子模块用户土地操作:包括翻地、播种、收获等;增加经验值
    2 系统功能设计2.1 登录注册功能本功能为登录注册功能,包括登录、注册。当个人忘记密码时,由其通过回答提示问题找回密码。
    2.1.1 登录子功能系统要求登录者提供帐号、密码,并进行身份确认及登录结果页面导向。如果登录者输入的账号和密码与数据库数据不一致,则提示该用户“您输入的账号或密码错误,请从新输入!”;登录成功后弹出用户农场界面。又或者该用户已经不记得密码,登录还设置一个找回密码的功能,用户可以修改自己的密码及部分基本信息,单击找回密码按钮则弹出用户早先在注册信息中设置的问题,如果用户输入的答案与注册信息中设置的答案不一致,则提示用户“您输入的答案错误,请从新输入!”;如果输入的答案正确,则弹出密码从新输入对话框,要求用户重新输入密码,并再次确认以及是否修改其他基本信息,用户提交后,经过验证将数据保存到数据库。
    2.2 用户功能农场界面会显示用户的游戏信息,如昵称、等级、金币等信息。同时农场会显示不同的图片按钮供用户进行操作,如点击商店图片进入商店购买物品、点击留言可以输入留言信息、点击仓库图片进入仓库查看作物等。
    2.2.1 土地操作子功能用户对自己土地的操作,主要包括拖动、翻地、播种收获、等。不能放虫、种杂草等等。如果进行翻地操作的土地上已经存在作物,这时对土地进行操作为有效操作,。当土地上没有作物时可以进行播种操作,当土地上存在作物时不可以进行播种操作。物已经成熟,可以进行收获、,当土地上的作物没有成熟,则不能进行收获。
    2.2.2 买卖商品子功能游戏中包含商店系统,用户可以从商店中用金币购买物品,也可以把自己仓库(仓库是用来存取用户作物收成的地方)中收获或偷取的果实、蔬菜等物品卖到商店获得金币。用户通过单击商店按钮进入商店界面,在商店中用户可以看到系统所提供的所有商品,其商品信息主要包括:商品名称、购买单价、限制购买等级等等;如果用户单击仓库按钮,则进入仓库界面,在仓库界面中,用户可以看到已经收获的或偷取好友的果实各自的种类、名称、数量、卖出单价及其所有果实的卖出收入。
    记录日志也是系统的功能,用户对好友的土地操作会由系统记录下来,并显示到用户的日志信息中。日志记录的主要内容应该包括:用户对土地的操作、用户的买卖行为以及好友对土地的操作等等。主要日志字段包含操作人、操作时间以及操作内容等。
    3 数据字典设计数据字典的主要目的是提供查阅对不了解的条目的解释。在数据字典中记录数据元素的下列信息:一般信息(名字,别名,描述等),定义(数据类型,长度,结构等),使用特点(值的范围,使用频率,使用方式—输入/输出/本地,条件值等),控制信息(来源,用户,使用它的程序,改变权等),分组信息(父结构,从属结构,物理位置—记录、文件和数据库等)。
    下面的例子是通过卡片来描述数据字典:

    4 数据库设计4.1 表设计4.1.1 TableName(表名的解释)userBasic 用户基本信息表



    字段名
    数据类型
    是否为空
    默认值
    备注




    userID
    Int
    Not null

    用户编号,主键,标识列,初始值为1,增量为1


    userName
    Nvarchar(30)
    Not null

    用户昵称


    Sex
    Bit
    Not null
    0
    用户性别


    Email
    Nvarchar(50)
    Null

    用户Email地址


    enrollTime
    Datetime
    Not null

    注册日期


    userIcoSrc
    Nvarchar(50)
    Not null

    用户头像地址


    userCode
    Nvarchar(12)
    Not null

    用户游戏帐号


    Pwd
    Nvarchar(50)
    Not null

    用户密码



    userInfo 用户游戏信息表



    字段名
    数据类型
    是否为空
    默认值
    备注




    infoID
    Int
    Not null

    信息编号,主键,标识列,初始值为1,增量为1


    userID
    Int
    Not null

    用户编号,外键,与userBasic表中userID字段关联


    Experience
    Int
    Not null
    0
    用户游戏经验值


    Coin
    Int
    Not null
    1000
    用户游戏金币值


    Grade
    Int
    Not null
    0
    用户游戏级别


    lastTime
    Datetime
    Not null

    用户最近一次登录游戏的时间



    cropBasic 作物基本信息表



    字段名
    数据类型
    是否为空
    默认值
    备注




    cropID
    Int
    Not null

    作物编号,主键,标识列,初始值为1,增量为1


    cropName
    Nvarchar(50)
    Not null

    作物名称


    Grade
    Int
    Not null
    0
    用户种植该作物所需达到的游戏级别


    seedPrice
    Int
    Not null
    0
    用户购买该作物种子的单价


    cropPrice
    Int
    Not null
    0
    作物果实卖出时的单价


    getExp
    Int
    Not null
    0
    用户收获果实所得的单位经验值


    hvtMax
    Int
    Not null

    作物可收获果实的最大值


    hvtMin
    Int
    Not null

    作物可收获果实的最小值


    hvtTime
    Int
    Not null

    作物从种子阶段成长到结果阶段所需的时间值


    cropIcoSrc
    Nvarchar(50)
    Not null

    作物图标地址


    Instroduce
    Nvarchar(50)
    Not null

    作物简单介绍



    cropGrowthInfo 作物生长信息表



    字段名
    数据类型
    是否为空
    默认值
    备注




    infoID
    Int
    Not null

    信息编号,主键,标识列,初始值为1,增量为1


    cropID
    Int
    Not null

    作物编号,外键,与cropBasic表中cropID字段关联


    germinateTime
    Int
    Not null

    作物发芽阶段所需的时间值


    litterTime
    Int
    Not null

    作物小叶阶段所需的时间值


    bigTime
    Int
    Not null

    作物大叶阶段所需的时间值


    flowerTime
    Int
    Not null

    作物开花阶段所需的时间值


    frutTime
    Int
    Not null

    作物结果阶段所需的时间值


    germinateIcoSrc
    Nvarchar(50)
    Not null

    作物发芽阶段的图片地址


    litterIcoSrc
    Nvarchar(50)
    Not null

    作物小叶阶段的图片地址


    bigIcoSrc
    Nvarchar(50)
    Not null

    作物大叶阶段的图片地址


    flowerIcoSrc
    Nvarchar(50)
    Not null

    作物开花阶段的图片地址


    frutIcoSrc
    Nvarchar(50)
    Not null

    作物结果阶段的图片地址



    harvestList 成果列表



    字段名
    数据类型
    是否为空
    默认值
    备注




    listID
    Int
    Not null

    列表编号,主键,标识列,初始值为1,增量为1


    userID
    Int
    Not null

    用户编号,外键,与userBasic表中userID字段关联


    cropID
    Int
    Not null

    作物编号,外键,与cropBasic表中cropID字段关联


    hvtTotal
    Int
    Not null
    0
    用户在游戏中摘取果实的累计值



    cropStoreInfo 果实存储列表



    字段名
    数据类型
    是否为空
    默认值
    备注




    storied
    Int
    Not null

    存储编号,主键,标识列,初始值为1,增量为1


    userID
    Int
    Not null

    用户编号,外键,与userBasic表中userID字段关联


    cropID
    Int
    Not null

    作物编号,外键,与cropBasic表中cropID字段关联


    storeNumber
    Int
    Not null
    0
    用户仓库中存储的果实数量



    seedStoreInfo 种子存储列表



    字段名
    数据类型
    是否为空
    默认值
    备注




    storied
    Int
    Not null

    存储编号,主键,标识列,初始值为1,增量为1


    userID
    Int
    Not null

    用户编号,外键,与userBasic表中userID字段关联


    cropID
    Int
    Not null

    作物编号,外键,与cropBasic表中cropID字段关联


    storeNumber
    Int
    Not null
    0
    用户包裹中存储的果实种子数量



    plantInfo 种植列表



    字段名
    数据类型
    是否为空
    默认值
    备注




    plantID
    Int
    Not null

    种植编号,主键,标识列,初始值为1,增量为1


    userID
    Int
    Not null

    用户编号,外键,与userBasic表中userID字段关联


    soilID
    Int
    Not null

    游戏中被开垦的土地编号


    isReclaim
    Bit
    Not null
    0
    记录游戏中该块土地是否已被开垦;0代表未开垦,1代表已开垦


    cropID
    Int
    Not null

    作物编号,外键,与cropBasic表中cropID字段关联


    startTime
    Datatime
    Not null

    作物种植开始时间


    State
    Int
    Not null
    0
    作物生长期间的状态值,包括无、正常、杂草、有虫、干旱、积水六种状态分别用0、1、2、3、4、5代表


    Degree
    Int
    Not null
    0
    作物处于上述状态中的程度值;程度分为五级(x=2、4、6、8、10)



    friendsList 好友列表



    字段名
    数据类型
    是否为空
    默认值
    备注




    listID
    Int
    Not null

    列表编号,主键,标识列,初始值为1,增量为1


    userID
    Int
    Not null

    用户编号,外键,与userBasic表中userID字段关联


    friendID
    Int
    Not null

    好友编号



    4.2 表之间的关联设计
    0 评论 3 下载 2019-06-03 18:15:43 下载需要10点积分
  • 基于Cocos Creator开发的打砖块游戏

    一、简介
    Cocos简而言之就是一个开发工具,详见官方网站TypeScript简而言之就是开发语言,是JavaScript的一个超集详解官网
    今天我们就来学习如何写一个打砖块的游戏,很简单的一个入门级小游戏。
    二、实现过程2.1 布局部分首先来一个整体的工程界面:

    一看就很简单吧,就几个元素+脚本(之所以分开写是为了便于查错)。
    再来一个游戏界面:很简洁有木有?当然啦,美观的工作需要各位小伙伴自行发挥啦。好了,下面进入正题…









    游戏中画面
    游戏结束画面



    首先,创建一个新的“世界”名字叫game。将场景设置为640X960

    然后,你在这个世界花了1亿买了一块地皮,当然得有地契“BG”(在Canvas下新增一个空节点将大小改为640X960,或者你自己选择一张背景,拖在Canvas下设置大小就ok了,至于其他的就默认)

    好了,有地皮了,你就可以“为所欲为”了。先添加砖块吧:

    将砖块拖到Canvas下,然后拖回Texture就成了预制体(很简单有木有)。
    至于大小什么的可以用代码控制。
    因为是砖块,要和小球碰撞,所以要加上物理碰撞。

    知识点

    cc.RigidBodyType.Static:静态刚体,零质量,零速度,即不会受到重力或速度影响,但是可以设置他的位置来进行移动
    cc.RigidBodyType.Dynamic:动态刚体,有质量,可以设置速度,会受到重力影响
    cc.RigidBodyType.Kinematic:运动刚体,零质量,可以设置速度,不会受到重力的影响,但是可以设置速度来进行移动
    cc.RigidBodyType.Animated:动画刚体,在上面已经提到过,从 Kinematic 衍生的类型,主要用于刚体与动画编辑结合使用

    这里我们选择:

    Type:Static(静止),在那等着小球来碰撞。然后给他添加一个包围盒PhysicsBoxCollider
    PhysicsBoxCollider 类型,摩擦力、弹性系数请自由发挥


    再来是我们的主角:

    同样的给他一个碰撞组件,Type: Dynamic;然后添加包围盒

    下面就是托盘,也是Static

    然后添加一个空节点,大小比BG小一些,他的作用其实就是“围墙”,避免小球或托盘飞出场景。同时添加碰撞组件和包围盒。

    然后还有个foot_line,这个就是监测小球掉下去游戏结束。

    还有个count ,后面再说这个计数。好了,罗里吧嗦的布局就完成了。
    2.2 代码部分我们首先建一个game的游戏脚本,用来初始化一些节点(其实所有的代码都可以放在一个脚本里,但是遇到复制的程序的时候不便于查错,所以该分开写的还是要分开写。

    首先呢,我们要加载一些节点变量。写代码的时候带上注释是好习惯哦!
    我们来一个创建砖块的方法吧:

    我采用的是中规中矩的生成方法,通过i和j控制行和列,然后在设置横向间距即可最后生成的结果为:(其他的样式,只要找到规律生成即可。)

    再来我们看onload方法:

    第二句先忽略。因为小球和砖块碰撞会产生效果,所以要开启物理碰撞(默认是关闭的)。托盘是根据手指移动的,所以要监听手指的移动:
    知识点

    我们这里用touch_move,接着需要
    //获取点击的坐标并转换成节点坐标系坐标let eposition = self.node.convertToNodeSpaceAR(event.getLocation());
    然后设置托盘的位置即可。托盘是左右移动的,所以还需要控制他不能移出屏幕
    //控制不能移除屏幕let minx = -self.node.width / 2 + this.wall.width / 2;//最小坐标let maxx = -minx;if (this.wall.x > maxx) { this.wall.x = maxx;}if (this.wall.x < minx) { this.wall.x = minx;}
    场景开始加载的时候我们需要快速的生成好砖块,所以这用了定时器使用计时器
    //自动生成砖块self.schedule(function () { self.createbrick(); }, 0.2, 1);
    第一个参数就是回调方法,第二个是时间间隔,每隔多少秒调用一次,第三个是执行次数,默认为0执行一次。
    好了,初始代码完了,简单吧(其实还有些新增的东西,如障碍物和多个小球的生成,这个我们就后面再讲)。
    然后我把脚本挂在Canvas上,并把相应的节点和组件拖进去。运行可以看到已经生成了砖块了吧。

    再来我们看主角:ball,同样的给他创建一个脚本ball并挂在小球上。

    这个脚本的作用主要是检测,碰撞检测。关于碰撞标签tag,按顺序给碰撞体添加即可。

    onBeginContact(contact:cc.PhysicsContact,selfCollider:cc.PhysicsCollider,otherCollider:cc.PhysicsCollider):void{}
    用碰撞的tag来区分与小球(self)碰撞的是哪个碰撞体。如果是托盘,那就给小球一个速度。如果是托盘没接住小球掉下去了,游戏结束我们就要跳转到结束界面(后面讲)。
    接着就是砖块了:

    这个呢主要就是加分用的。基本是完工了,很简单的有木有。
    再来看结束的场景:

    创建一个结束的场景Sence,同样的需要一个脚本overcount,脚本里需要处理的是:

    接收主场景传过来的分数并做展示,此处我直接把节点给传过来了
    重新开始按钮功能


    第一个,分数节点,我们创建一个label节点但是因为要是常驻节点所以它和Canvas平级。

    然后新建一个脚本挂在此节点上
    onLoad () { cc.game.addPersistRootNode(this.node);}
    声明它为常驻节点。
    Brick.ts:当小球和砖块碰撞的时候呢,分数+1,并且要销毁砖块。
    if (otherCollider.tag == 0) {//球和砖块碰撞 let gamecc = cc.director.getScene().getChildByName('count').getComponent(cc.Label); gamecc.string = String(Number(gamecc.string) + 1); this.node.destroy();
    拿到分数后,ball.ts:
    if(otherCollider.tag==5){//球和foot_line碰撞 cc.director.getPhysicsManager().enabled = false; cc.find('Canvas').destroyAllChildren(); cc.director.loadScene('over'); }
    如果小球掉下去了(托盘没接到),然后我们将小球的物理状态关掉,清除game场景下的所有节点并跳转到结束场景over。
    Over场景中有一个重新开始的按钮overcount.ts:
    需要做的就是获取分数并将分数复制给结束界面的Label,然后隐藏此常驻节点。
    let gamecount = cc.director.getScene().getChildByName('count').getComponent(cc.Label);gamecount.node.active = false;this.overcount.string = gamecount.string;self.again.on(cc.Node.EventType.TOUCH_START, function () { gamecount.string = String(0); cc.director.loadScene('game'); }, self)
    点击重新开始按钮,那么需要把分数置为0,当然还没完,前面我们是关闭了分数计数节点,现在需要打开Game.ts
    onLoad() { let self = this; //开启分数节点 cc.director.getScene().getChildByName('count').getComponent(cc.Label).node.active = true;
    好了,到这你就可以让你的程序跑起来了。
    2.3 新增功能障碍物
    障碍物很简单的,也是通过预制体,然后给它上面挂个label用来当小球碰到障碍物的时候,这个数值自减最后在总分数上加上这个分值。

    创建一个bar脚本并挂在此节点上,Bar.ts

    脚本的作用呢就是给障碍物一个常量,小球和障碍物每碰撞一次就减1,当小于1 的时候就给总发加上3分。
    好了,大概就是这些了,项目其实很简单。多练、多写ヾ(◍°∇°◍)ノ゙
    还有添加隐藏的小球,碰撞后生成多个小球!这个请小伙伴自行发挥了…
    1 评论 5 下载 2019-05-29 11:03:14 下载需要15点积分
  • 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处的程序如何运行。
    6 评论 53 下载 2018-11-24 17:12:53 下载需要15点积分
  • 基于c语言的一个很好玩的整人小游戏,让你不仅仅拘泥于小黑方框(适合新手)。

    这是一个很有意思的小游戏,是本人自己写的一个c语言程序,内容相对很简单,可以让你的电脑关机,同时还可以调一些本电脑中的图片音乐,体会到不一样的乐趣。再也不是死板的小黑框了。上传的压缩包含有所有内容,请自取。
    下面我来详解一下我的小程序:
    close()函数,因为是整人小程序,那么必不可少的是关机!
    int close() { printf("你终于是走到了这一步,还有五十秒重启,抽支烟冷静一下,等待重启吧!\n"); system("shutdown -r -t 50"); return 0;}
    system(“shutdown -r -t 50”);这一句是调用系统命令,相当于你从DOS命令框中输入命令,本程序的核心也正是围绕此处展开的。
    wanjiu()函数,是让人有复活的机会,具体指令参考源码。
    剩下的主要核心是switch语句,让用户不断的选择,以完成闯关。
    运行中的细节图:





    0 评论 1 下载 2019-05-25 16:52:14 下载需要5点积分
  • 基于c语言的表白神器,闪烁心。

    这个实例相对是比较简单的,核心的内容有以下几点:
    借助一个数组,将用户的输入信息赋值给数组,其中元素是“yes”则开始动画。
    画心代码如下:
    for (y = 1.5f; y > -1.5f; y-=0.1f)//画心形图案 { for (x = -1.5f; x < 1.5f; x += 0.05f) { z = x*x + y*y -1; f = z*z*z - x*x*y*y*y; putchar(f <= 0.0f ? "*********"[(int)(f*-8.0f)]:' '); } putchar('\n');}
    以9个*为一组,这样可以快速填充。
    system("color a");
    system函数,用于更改控制台的参数的函数,color a意为更改颜色;
    for(time=0;time<99999999;time++);
    每隔一段时间换一种颜色,实现颜色跳跃。
    0 评论 4 下载 2019-05-25 17:06:28 下载需要1点积分
  • 基于PyQT5、PocketSphinx的python语音识别小程序

    1.使用说明1.1 项目简介参照微软cotana的工作方式,编写自己的一个python语音助手程序,借助PyQt5和Pipy,创建可视化窗口,能实现基本的语音识别功能,同时根据几个特定的关键词执行简单的行动(如music,readme等)。
    1.2 项目功能要求
    实现语音识别,并将识别的内容在屏幕上打印出来
    设置几个命令关键词(music,readme),并实现相应的行动。若识别出的内容中包含设置的几个关键词,则程序执行相应的行动。
    设置两种识别模式:PocketSphinx中包含7个Recognizer API:程序中使用了两个API:recognize_sphinx和recognize_google。(两种识别模式可由用户自行选择,其中recognize_sphinx可直接在本地运行,但识别精度较低;recognize_google识别精度较高,但是使用recognize_google需要处于联网状态下且IP地址需要在境外,否则语音识别会出现错误)
    设置文本框:用户可直接在文本框中输入命令,其执行效力与语音输入等效

    2.程序设计与实现2.1 设计class Ui_MainWindow(object):
    Ui_Mainwindow类加载图形化用户界面,控制界面布局,类中包含各种Label,PushButton,MenuBar控件。
    class myWindow(QtWidgets.QMainWindow):
    mywindow类处理交互逻辑,类中包含各种执行函数,同时实现控件与函数的连接。
    2.2 主要函数实现声音监听与处理函数def listen(self): # Working with Microphones mic = sr.Recognizer() with sr.Microphone() as source: # use the default microphone as the audio source audio = mic.listen(source) # listen for the first phrase and extract it into audio data try: if self.isgoogle: content = mic.recognize_google(audio) else: content = mic.recognize_sphinx(audio) except sr.RequestError: self.ui.label.setText("Something was wrong! Try again......") COMMEND = ["music", "open"] commend_is_music = re.search(COMMEND[0].lower(), content.lower()) commend_is_file = re.search(COMMEND[1].lower(), content.lower()) if commend_is_music: self.ui.label.setText("you said: \" " + content + "\"") win32api.ShellExecute(0, 'open', 'D:\\网易云音乐\\CloudMusic\\cloudmusic.exe', '', '', 1) elif commend_is_file: self.ui.label.setText("you said: \"" + content + "\"") win32api.ShellExecute(0, 'open', 'D:\\Notpad++\\Notepad++\\notepad++.exe', '', '', 0) else: self.ui.label.setText("you said: \" " + content + "\"\nIt's not a valid command.")
    创建监听线程def listen_thread(self): self.ui.label.setText("I'm listening...... ") t1 = threading.Thread(target=self.listen) t1.setDaemon(True) t1.start()
    文本处理函数def text_changed(self): content = self.ui.textbox.text() print(content) COMMEND = ["music", "open"] commend_is_music = re.search(COMMEND[0].lower(), content.lower()) commend_is_file = re.search(COMMEND[1].lower(), content.lower()) if commend_is_music: self.ui.label.setText("you typed: \" " + content + "\"") win32api.ShellExecute(0, 'open', 'D:\\网易云音乐\\CloudMusic\\cloudmusic.exe', '', '', 1) elif commend_is_file: self.ui.label.setText("you typed: \"" + content + "\"") win32api.ShellExecute(0, 'open', 'D:\\Notpad++\\Notepad++\\notepad++.exe', '', '', 0) else: self.ui.label.setText("you typed: \" " + content + "\"\nIt's not a valid command.")
    创建文本处理线程def text_thread(self): t2 = threading.Thread(target=self.text_changed) t2.setDaemon(True) t2.start()
    连接各类控件与相应函数self.ui.recognize_btn.clicked.connect(self.listen_thread)#语音识别按钮连接监听线程self.ui.sphinx_bar.triggered.connect(self.sphinxbar_recognize)#sphinx模式触发self.ui.google_bar.triggered.connect(self.googlebar_recognize)#google模式触发self.ui.text_btn.clicked.connect(self.text_thread)#文本框输入确认按钮连接文本处理线程
    3.测试截图


    2 评论 5 下载 2019-05-11 16:44:19 下载需要6点积分
  • 基于Python实现的抓包分析软件

    一、简介这是一个学习模仿WireShark的抓包软件。可以的功能有:侦听、解析、构造数据包等。其中还包括扩展功能:流量监测和攻击检测(Land攻击,Ping of Death)。软件目前支持解析:IP、IPv6、ARP、TCP、UDP、ICMP、ICMPv6、SSDP、HTTP、TLS。
    二、主要功能
    侦听指定网卡或所有网卡,抓取流经网卡的数据包
    解析捕获的数据包每层的每个字段,查看数据包的详细内容
    可通过不同的需求设置了BPF过滤器,获取指定地址、端口或协议等相关条件的报文
    针对应用进行流量监测,监测结果实时在流量图显示,并可设置流量预警线,当流量超过预警线时自动报警
    提供了以饼状图的形式统计ARP、TCP、UDP、ICMP报文,以柱状图的形式统计IPv4、IPv6报文
    可将抓取到的数据包另存为pcap文件,并能通过打开一个pcap文件对其中的数据包进行解析
    可逐层逐字段构造数据包,实现自定义数据包发送

    三、主要模块
    数据报文采集模块:完成网络接口数据的捕获、解析,可以根据用户定义条件组合来进行捕获,如只监视采用TCP或UDP协议的数据包,也可以监视用户希望关注的相关IP地址的数据包,同时完成数据封包日志记录,提高了系统的灵活性。此外,对IP类型、ARP、TCP、UDP、ICMP的数量进行统计。
    应用流量监测模块:获取当前正在运行的应用进程,用户可选择一个应用进行流量监测,获取应用中流量信息,同时对一些常见的入侵攻击特征进行判断,如根据源目的地址是否相同判断Land攻击、IP头部长度是否过长判断ping拒绝服务攻击,并发出预警。
    报文伪造模块:可以自行构造Ether、IP、TCP、UDP、ICMP、ARP报文,并选择send()、sendp()、sr()、srl()、srloop()五种方式发送报文以实现简单攻击或对TCP/IP进行调试。
    界面显示模块:设计系统主窗口即数据报文采集界面、应用流量监测界面、报文伪造界面。并完成报文统计图的显示,流量图的显示。
    四、源代码结构
    img存放程序中使用的背景和图标。
    capture_core.py抓包程序的后台文件,主要用来生成数据包的摘要信息、分析数据包的结构、程序状态处理、下载速度和上传速度的更新等。
    flow_monitor.py流量监控程序的后台服务代码,实时更新速度、应用流量的过滤及摘要信息的生成、更新应用的网络连接等。
    forged_packet用于构造数据包并发送,可自定义数据包的每个字段,实现网络攻击或网络欺骗等功能。
    main_ui.py抓包程序的GUI代码,包括了快捷键的绑定以及可自定义字体和背景图片、已抓到数据包的摘要信息的展示、显示某个数据包的详细信息和十六进制数据等功能。
    main.py程序的入口。
    monitor_system.py流量监控的GUI代码,用于查看网络连接速度等。
    tools.py工具代码,用于获取网卡的NIC、格式的转换、网络连接速度的获取。
    data.json用于存放程序的配置信息。

    五、环境依赖
    Python 3.6
    Scapy 2.4

    Linux & Windows
    # pip install psutil scapy matplotlib pyqt5
    在Windows下,还需要
    # pip install wmi pywin32
    六、使用方法进入项目目录
    # cd WireWhale # python main.py
    七、部分功能介绍
    主界面主要包括5个部分:
    菜单栏
    文件:文件保存、打开,软件退出编辑:可自行设置主窗体字体捕获:捕获数据包的流程分析:两大拓展功能,应用流量监测和伪造数据包统计:报文统计帮助:功能介绍及版权声明
    工具栏
    界面初始时,根据程序运行状态转移图只有开始键可以响应设置了标志位:start_flag、pause_flag、stop_flag、save_flag,用于对程序中一些函数的使用添加限制。开始、暂停、停止、重新开始四个按钮全部按照下图逻辑设置在什么情况下可响应下面为部分状态截图
    起始状态运行状态暂停状态停止状态

    过滤器以及网卡选择
    页面初始化时网卡选择下拉框获取网卡信息进行显示,如下图所示,默认全选
    报文显示
    根据报文类型显示不同颜色,以进行明显的区别报文解析以树状结构显示,层次结构清晰明了抓包简略信息显示框定时滑到最底部
    状态栏(显示当前网卡、实时收发包速度、上传下载速度)

    数据统计模块:绘制图使用python下最流行的数据处理框架Matplotlib绘制要求的统计图。


    八、软件运行时部分截图8.1 主程序



    8.2 数据包伪造

    8.3 流量监控
    1 评论 1 下载 2019-05-17 17:02:38 下载需要11点积分
显示 0 到 15 ,共 15 条
eject