3.6 图像的剪切、合成与识别
实例111 图像的剪切
这是一个可以启发思维的实例
实例位置:光盘\mingrisoft\03\111
实例说明
几乎所有的图像编辑软件都提供了图像的剪切功能,用户可以根据需要剪切图像的某一部分进行处理,本实例也实现了这一功能,运行程序,单击“剪切”按钮,将截取源图像的某一区域到目标图像中,如图3.33所示。
图3.33 图像的剪切
技术要点
实现图像的剪切可以使用CRgn类创建一个剪切的区域,然后利用目标设备上下文CDC选中该剪切的区域,在该区域绘制图像,最后在源设备上下文中将剪切的区域设置为白色的背景。CRgn封装了Windows图像设备接口的区域对象,该区域可以是椭圆形或多边形。用户可以通过该类为CDC定义一个剪切的区域。CRgn类的主要方法如下:
(1)CreateRectRgn方法。该方法用于创建一个矩形区域。语法如下:
BOOL CreateRectRgn( int x1, int y1, int x2, int y2 );
参数说明:
● x1、y1:标识矩形区域的左上角坐标。
● x2、y2:标识矩形区域的右下角坐标。
(2)CreateEllipticRgn方法。该方法用于创建一个椭圆形的区域。语法如下:
BOOL CreateEllipticRgn( int x1, int y1, int x2, int y2 );
参数说明:
● x1、y1:标识椭圆的外接矩形的左上角坐标。
● x2、y2:标识椭圆的外接矩形的右下角坐标。
(3)CreatePolygonRgn方法。该方法用于创建一个多边形的区域。语法如下:
BOOL CreatePolygonRgn( LPPOINT lpPoints, int nCount, int nMode );
参数说明:
● lpPoints:是CPoint类型数组指针,用于标识多边形的顶点坐标。
● nCount:标识lpPoints数组中元素的数量。
● nMode:标识区域的填充模式。
(4)CombineRgn方法。该方法用于组合两个区域。语法如下:
int CombineRgn( CRgn* pRgn1, CRgn* pRgn2, int nCombineMode );
参数说明:
● pRgn1、pRgn2:标识组合区域的两个指针。
● nCombineMode:确定区域的组合模式,可选值如下。
■ RGN_AND:使用两个区域的重叠部分。
■ RGN_COPY:使用第一个区域。
■ RGN_DIFF:创建由第一区域组成的区域,而不包含第二个区域。
■ RGN_OR:使用两个区域的共同区域。
■ RGN_XOR:使用两个区域的非重叠部分。
实现过程
(1)新建一个基于对话框的应用程序。
(2)在对话框中添加图片控件和按钮控件。
(3)主要程序代码如下:
void CCutImageDlg::OnOK() { CBitmap m_bitmap; HBITMAP m_hbitmap = m_sourceimage.GetBitmap(); //附加位图句柄 m_bitmap.Attach(m_hbitmap); CDC* m_dc = m_cutimage.GetDC(); CRgn m_rgn; BITMAP m_bitinfo; m_bitmap.GetBitmap(&m_bitinfo); //创建一个剪切区域 m_rgn.CreateEllipticRgn(150,1,m_bitinfo.bmWidth-1,m_bitinfo.bmHeight-1); CDC* m_sourcedc = m_sourceimage.GetDC(); //选中剪切区域 m_dc->SelectClipRgn(&m_rgn,RGN_COPY ); m_dc->BitBlt(0,0,m_bitinfo.bmWidth,m_bitinfo.bmHeight,m_sourcedc,0,0,SRCCOPY); //绘制剪切图像 m_sourcedc->SelectClipRgn(&m_rgn,RGN_COPY ); m_sourcedc->BitBlt(0,0,m_bitinfo.bmWidth,m_bitinfo.bmHeight, m_dc,0,0,WHITENESS);//绘制源图像 m_bitmap.Detach(); }
举一反三
根据本实例,读者可以:
开发具有合成图层功能的图像处理软件。
实例112 图像的合成
本实例是一个提高效率、人性化的程序
实例位置:光盘\mingrisoft\03\112
实例说明
在许多图像处理软件中,都提供了图像的合成功能。例如在PhotoShop中,用户可以利用图层技术合成图像。本实例实现了图像的合成技术,效果如图3.34所示。
图3.34 图像的合成
技术要点
本实例中实现图像合成是采用设备上下文CDC类的BitBlt方法实现的。CDC类提供了多个绘制图像的方法,常用的主要有BitBlt和StretchBlt两个方法,下面分别介绍这两个方法。
(1)BitBlt方法。该方法将位图从源设备区域复制到目标设备区域。语法如下:
BOOL BitBlt( int x, int y, int nWidth, int nHeight, CDC* pSrcDC, int xSrc, int ySrc, DWORD dwRop );
参数说明:
● x、y:标识目标区域的左上角坐标。
● nWidth、nHeight:确定复制位图的宽度和高度。
● pSrcDC:标识源设备上下文指针。
● xSrc、ySrc:标识源位图的左上角坐标。
● dwRop:确定复制模式,通常为SRCCOPY。
(2)StretchBlt方法。该方法将位图从源设备区域复制到目标设备区域。与BitBlt方法不同的是,StretchBlt方法在必要的时候会延长或压缩位图区域以适合目标区域。语法如下:
BOOL StretchBlt( int x, int y, int nWidth, int nHeight, CDC* pSrcDC, int xSrc, int ySrc, int nSrcWidth, int nSrcHeight, DWORD dwRop );
参数说明:
● x、y:标识目标区域的左上角坐标。
● nWidth、nHeight:确定复制位图的宽度和高度。
● pSrcDC:标识源设备上下文指针。
● xSrc、ySrc:标识源位图的左上角坐标。
● nSrcWidth、nSrcHeight:标识源位图的宽度和高度。
● dwRop:确定复制模式,通常为SRCCOPY。
实现过程
(1)新建一个基于对话框的应用程序。
(2)向对话框中添加Button和Picture控件。
(3)主要程序代码如下:
void CCombineImageDlg::OnOK() { //获取背景图片设备上下文 CDC* m_grounddc = m_back.GetDC(); //获取子图片设备上下文 CDC* m_babydc = m_baby.GetDC(); CBitmap m_bitmap; //位图对象 BITMAP m_bitinfo; //位图信息 int m_height,m_width; m_bitmap.Detach(); m_bitmap.Attach((HBITMAP)m_baby.GetBitmap()); //获取位图大小 m_bitmap.GetObject(sizeof(m_bitinfo),&m_bitinfo); m_width=m_bitinfo.bmWidth; //图像宽度 m_height=m_bitinfo.bmHeight; //图像高度 //在背景图片的指定区域绘制图像 m_grounddc->BitBlt(130,100,m_width,m_height,m_babydc,0,0,SRCCOPY); //将句柄与位图对象分离 m_bitmap.Detach(); }
举一反三
根据本实例,读者可以:
实现图像的剪切。
实例113 获取鼠标任意位置的颜色值
本实例可以方便操作、提高效率
实例位置:光盘\mingrisoft\03\113
实例说明
在Windows的画图程序中,打开“编辑颜色”窗口时,当鼠标在颜色区域移动时,右方的显示区域和下方的编辑框中会显示相应的颜色和颜色值。本实例实现了该功能,效果如图3.35所示。
图3.35 获取鼠标任意位置的颜色值
技术要点
获取某一点的颜色很容易,只要得到当前鼠标下的设备上下文CDC类就可以了,因为调用CDC类的GetPixel方法可以获取某一点的颜色值。但是,通过颜色值如何获得红、绿、蓝三原色的值呢?Visual C++提供了3个宏,用于获取某一颜色的红、绿、蓝三原色,这3个宏分别如下所示。
(1)GetRValue宏。该宏用于获取指定颜色的红颜色值。语法如下:
BYTE GetRValue(DWORD rgb );
参数说明:
● rgb:标识一个颜色值。
● 返回值:指定颜色的红色值。
(2)GetGValue宏。该宏用于获取指定颜色的绿颜色值。语法如下:
BYTE GetGValue(DWORD rgb );
参数说明:
● rgb:标识一个颜色值。
● 返回值:指定颜色的绿色值。
(3)GetBValue宏。该宏用于获取指定颜色的蓝色值。语法如下:
BYTE GetBValue(DWORD rgb );
参数说明:
● rgb:标识一个颜色值。
● 返回值:指定颜色的蓝色值。
在MFC应用程序中,颜色值通常采用COLORREF类型,在设置颜色值时,可以利用RGB宏将红、绿、蓝三原色组合为一个COLORREF类型颜色值。例如获取红颜色,代码如下:
RGB(255,0,0); //获取红颜色
实现过程
(1)新建一个基于对话框的应用程序。
(2)在对话框中添加静态文本控件、图片控件和编辑框控件。
(3)主要程序代码如下:
void CFetchColorDlg::OnMouseMove(UINT nFlags, CPoint point) { CDialog::OnMouseMove(nFlags, point); CWnd* temp = ChildWindowFromPoint(point); if (temp) { CDC* m_dc = temp->GetDC(); COLORREF m_color; this->MapWindowPoints(temp,&point,1); //获取颜色值 m_color = m_dc->GetPixel(point); int r,g,b; r = GetRValue(m_color); g = GetGValue(m_color); b = GetBValue(m_color); //在编辑框中显示颜色值 CString str; str.Format("%i",r); m_red.SetWindowText(str); str.Format("%i",g); m_green.SetWindowText(str); str.Format("%i",b); m_blue.SetWindowText(str); //使用当前颜色填充区域 CRect m_rect; m_test.GetClientRect(m_rect); CDC* dc = m_test.GetDC(); CBrush m_brush(RGB(r,g,b)); dc->FillRect(m_rect,&m_brush); } }
举一反三
根据本实例,读者可以:
设计颜色拾取器。
实例114 提取图片中的对象
这是一个可以提高分析能力的实例
实例位置:光盘\mingrisoft\03\114
实例说明
在图像识别领域中,图像提取是最基础也是最重要的一个环节。如果前期图像提取错误,无论识别技术多么高超,最终也不会识别出正确的对象。本实例将实现从图像中提取人物轮廓,效果如图3.36所示。
图3.36 提取图片中的对象
技术要点
要从图像中提取某个对象,首先需要观察提取对象的特征。例如本实例中提取的对象,轮廓是由黑色的像素构成,因此只要将黑色像素提取出来,则对象的轮廓也就描述出来了。设备上下文CDC类提供了GetPixel方法和SetPixel方法,用于获取或设置某个点的颜色。下面详细介绍这两个方法。
(1)GetPixel方法。该方法用于获取某一点的颜色值。语法如下:
COLORREF GetPixel( int x, int y ) const; COLORREF GetPixel( POINT point ) const;
参数说明:
● x、y、point:标识坐标点。
● 返回值:坐标点的颜色值。
(2)SetPixel方法。该方法用于设置某一点的颜色值。语法如下:
COLORREF SetPixel( int x, int y, COLORREF crColor ); COLORREF SetPixel( POINT point, COLORREF crColor );
参数说明:
● x、y、point:标识坐标点。
● crColor:标识设置的颜色值。
● 返回值:坐标点实际显示的颜色值。
实现过程
(1)新建一个基于对话框的应用程序。
(2)在对话框中添加图片控件和按钮控件。
(3)主要程序代码如下:
void CFetchObjectDlg::OnFetch() { CDC* dc = m_image.GetDC(); CDC* m_dc = m_demo.GetDC(); CRect m_rect; m_image.GetClientRect(&m_rect); //获得控件的客户区域 int x,y; for(x=0;x<m_rect.right;x++) //根据宽度循环 for(y=0;y<m_rect.bottom;y++) //根据高度循环 { COLORREF m_color; m_color=dc->GetPixel(x,y); //获得当前颜色 //判断是否为要获取的颜色 if ((m_color ==RGB(255,255,255))||(m_color==RGB(0,0,0))|| (m_color == RGB(252,197,30))) { m_dc->SetPixel(x,y,m_color); } } }
举一反三
根据本实例,读者可以:
开发简易的图像识别程序。
实例115 手写数字识别
本实例是一个提高效率的程序
实例位置:光盘\mingrisoft\03\115
实例说明
如今许多软件都提供了文字识别功能。例如在一些手机的应用软件中,用户能够通过手写文字实现短信发送,其中就涉及了文字的识别技术。本实例实现了手写数字的识别,采用联机字符识别技术,效果如图3.37所示。
图3.37 手写数字识别
技术要点
手写数字识别的难度在于其形状极多。对于规范的手写数字,可以采用模板匹配的方法。但是,由于每个人的字体不尽相同,导致数字或大或小、或胖或瘦,如图3.38所示,采用模板匹配就行不通了。
图3.38 手写数字
在图3.38中,虽然“2”的写法不同,但人眼一下就能识别出它们是“2”。分析原因,这两个字都是向右、向左下、向右的书写顺序。它们的特征在于笔顺相同。本实例就是以数字的笔顺为特征区别手写数字的。
实现过程
(1)新建一个基于对话框的应用程序。
(2)向对话框中添加图片控件和按钮控件。
(3)主要程序代码如下:
void CRegFigureDlg::OnReg() { m_curpen = 0; if(m_Figure.Direction[0]==down) //判断1 if(m_Figure.Direction[1]==none) //只有一笔记录 { if ( m_Figure.DotCount == 1) { MessageBox("1"); for(int i=0;i<16;i++) //重新初始化m_Figure { m_Figure.Direction[i]= none; } m_Figure.DotCount = 0; m_Panel.Invalidate(); //更新图片控件 return; } } if(m_Figure.Direction[0]==right) //判断7 if (m_Figure.Direction[1]==down) if (m_Figure.Direction[2]==none) { if ( m_Figure.DotCount == 1) { MessageBox("7"); for(int i=0;i<16;i++) //重新初始化m_Figure { m_Figure.Direction[i]=none; } m_Figure.DotCount=0; m_Panel.Invalidate(); //更新图片控件 return; } } if(m_Figure.Direction[0]==right) //判断2 if (m_Figure.Direction[1]==down) { if (m_Figure.DotCount == 1) { if (m_Figure.Direction[2]==left) { if(m_Figure.Direction[3]==right) if ( m_Figure.Direction[4]==none) { MessageBox("2"); for(int i=0;i<16;i++) //重新初始化m_Figure { m_Figure.Direction[i]=none; } m_Figure.DotCount=0; m_Panel.Invalidate(); //更新图片控件 return; } } else if (m_Figure.Direction[2]==right) { if(m_Figure.Direction[3]==none) { MessageBox("2"); for(int i=0;i<16;i++) //重新初始化m_Figure { m_Figure.Direction[i]=none; } m_Figure.DotCount = 0; m_Panel.Invalidate(); //更新图片控件 return; } } } } if(m_Figure.Direction[0]==right) //判断3 if (m_Figure.Direction[1]==down) if (m_Figure.DotCount==1) { if (m_Figure.Direction[2]==left) if(m_Figure.Direction[3]==right) { MessageBox("3"); } for(int i=0;i<16;i++) //重新初始化m_Figure { m_Figure.Direction[i]=none; } m_Figure.DotCount=0; m_Panel.Invalidate(); //更新图片控件 return; } if(m_Figure.Direction[0]==down) //判断4 if (m_Figure.Direction[1]==right) if (m_Figure.DotCount==2) { if(m_Figure.Direction[2]==down) if(m_Figure.Direction[3]==none) { for(int i=0;i<16;i++) //重新初始化m_Figure { m_Figure.Direction[i]= none; } m_Figure.DotCount = 0; m_Panel.Invalidate(); //更新图片控件 MessageBox("4"); return; } } if(m_Figure.Direction[0]==down) //判断5 if (m_Figure.Direction[1]==right) if (m_Figure.DotCount==2) if(m_Figure.Direction[2]==down) if(m_Figure.Direction[3]==left) if(m_Figure.Direction[4]==right) if (m_Figure.Direction[5]==none) { MessageBox("5"); for(int i=0;i<16;i++) //重新初始化m_Figure { m_Figure.Direction[i]= none; } m_Figure.DotCount = 0; m_Panel.Invalidate(); //更新图片控件 return; } if(m_Figure.DotCount==1) //判断6 if (m_Figure.Direction[1]==down) if (m_Figure.Direction[2]==right) if(m_Figure.Direction[3]==up) if(m_Figure.Direction[4]==left) { MessageBox("6"); for(int i=0;i<16;i++) //重新初始化m_Figure { m_Figure.Direction[i]= none; } m_Figure.DotCount = 0; m_Panel.Invalidate(); //更新图片控件 return; } if(m_Figure.DotCount==1) //判断8 { if ( m_Figure.Direction[0]==left) { if (m_Figure.Direction[1]==down) if(m_Figure.Direction[2]==left) if(m_Figure.Direction[3]==up) if(m_Figure.Direction[4]==right) if(m_Figure.Direction[5]==up) { MessageBox("8"); for(int i=0;i<16;i++) //重新初始化m_Figure { m_Figure.Direction[i]= none; } m_Figure.DotCount = 0; m_Panel.Invalidate(); //更新图片控件 return; } } if(m_Figure.Direction[0]==down) //第2种判断数字8的方式 { if (m_Figure.Direction[1]==right) if(m_Figure.Direction[2]==down) if(m_Figure.Direction[3]==right) if(m_Figure.Direction[4]==up) if(m_Figure.Direction[5]==left) { MessageBox("8"); for(int i=0;i<16;i++) //重新初始化m_Figure { m_Figure.Direction[i]= none; } m_Figure.DotCount = 0; m_Panel.Invalidate(); //更新图片控件 return; } } if(m_Figure.Direction[0]==left) //第3种判断数字8的方式 { if (m_Figure.Direction[1]==down) if(m_Figure.Direction[2]==right) if(m_Figure.Direction[3]==down) if(m_Figure.Direction[4]==left) if(m_Figure.Direction[5]==up) { MessageBox("8"); for(int i=0;i<16;i++) //重新初始化m_Figure { m_Figure.Direction[i]= none; } m_Figure.DotCount = 0; m_Panel.Invalidate(); //更新图片控件 return; } } } if(m_Figure.DotCount==1) //判断9 { if ( m_Figure.Direction[0]==left) { if (m_Figure.Direction[1]==down ) if(m_Figure.Direction[2]==right) { MessageBox("9"); for(int i=0;i<16;i++) //重新初始化m_Figure { m_Figure.Direction[i]=none; } m_Figure.DotCount = 0; m_Panel.Invalidate(); //更新图片控件 return; } } if(m_Figure.Direction[0]==right) //第2种判断9的方式 if ( m_Figure.Direction[1]==up) if(m_Figure.Direction[2]==left) if ( m_Figure.Direction[3]==down) { MessageBox("9"); for(int i=0;i<16;i++) //重新初始化m_Figure { m_Figure.Direction[i]=none; } m_Figure.DotCount=0; m_Panel.Invalidate(); //更新图片控件 return; } } for(int i=0;i<16;i++) //如果没有匹配的模板重新初始化m_Figure { m_Figure.Direction[i]=none; } m_Figure.DotCount = 0; m_Panel.Invalidate(); //更新图片控件 }
举一反三
根据本实例,读者可以:
实现手写汉字的识别。