第七章 KinectV2结合MFC显示和处理图像数据(下)

第七章  KinectV2结合MFC显示和处理图像数据(下)

首先声明一下,本系统所使用的开发环境版本是计算机系统Windows 10、Visual Studio 2013、Opencv3.0和Kinect SDK v2.0。这些都可以在百度上找到,download下来安装一下即可。

一、在MFC中如何显示OpenCV的图像Mat

前段时间一直在学习opencv,但学习过程中写的例子都是基于控制台的。今天打算把之前写的一些例子都移植到MFC中,基本上就是复制以前的代码,唯一的区别在于在控制台中,显示图像是先创建一个窗口,然后在窗口中显示图像,然而在MFC中,一般是将图像显示在一个picture控件中,这样就得用到CvvImage类啦,但是这里有一个问题,那就是Opencv2.2以上版本不再包含CvvImage类了,这样的话我们就不能使用这个类了。

解决方案有:

方案一:本人亲测有效,在网上找了一些资料。我们可以自己建立一个CvvImage.h和一个CvvImage.cpp的文件,添加到工程中。这样我们在工程中包含上这个CvvImage.h的头文件,就可以正常的按照以前的方式使用CvvImage类将图像绘制到MFC控件中了。

这里给出这两个文件的代码:

<h3>/*头文件 CvvImage.h*/  </h3>
#pragma once
#ifndef CVVIMAGE_CLASS_DEF
#define CVVIMAGE_CLASS_DEF  

#include "cv.h"
#include "highgui.h"  

/* CvvImage class definition */
class  CvvImage
{
public:
   CvvImage();
   virtual ~CvvImage();
   /* Create image (BGR or grayscale) */
   virtual bool  Create( int width, int height, int bits_per_pixel, int image_origin = 0 );
   /* Load image from specified file */
   virtual bool  Load( const char* filename, int desired_color = 1 );
   /* Load rectangle from the file */
   virtual bool  LoadRect( const char* filename,
      int desired_color, CvRect r );
#if defined WIN32 || defined _WIN32
   virtual bool  LoadRect( const char* filename,
      int desired_color, RECT r )
   {
      return LoadRect( filename, desired_color,
         cvRect( r.left, r.top, r.right - r.left, r.bottom - r.top ));
   }
#endif
   /* Save entire image to specified file. */
   virtual bool  Save( const char* filename );
   /* Get copy of input image ROI */
   virtual void  CopyOf( CvvImage& image, int desired_color = -1 );
   virtual void  CopyOf( IplImage* img, int desired_color = -1 );
   IplImage* GetImage() { return m_img; };
   virtual void  Destroy(void);
   /* width and height of ROI */
   int Width() { return !m_img ? 0 : !m_img->roi ? m_img->width : m_img->roi->width; };
   int Height() { return !m_img ? 0 : !m_img->roi ? m_img->height : m_img->roi->height;};
   int Bpp() { return m_img ? (m_img->depth & 255)*m_img->nChannels : 0; };
   virtual void  Fill( int color );
   /* draw to highgui window */
   virtual void  Show( const char* window );  

#if defined WIN32 || defined _WIN32
   /* draw part of image to the specified DC */
   virtual void  Show( HDC dc, int x, int y, int width, int height,
      int from_x = 0, int from_y = 0 );
   /* draw the current image ROI to the specified rectangle of the destination DC */
   virtual void  DrawToHDC( HDC hDCDst, RECT* pDstRect );
#endif
protected:
   IplImage*  m_img;
};
typedef CvvImage CImage;
#endif 
/*源文件 CvvImage.cpp*/  

#include "StdAfx.h"
#include "CvvImage.h"
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
CV_INLINE RECT NormalizeRect( RECT r );
CV_INLINE RECT NormalizeRect( RECT r )
{
   int t;
   if( r.left > r.right )
   {
      t = r.left;
      r.left = r.right;
      r.right = t;
   }
   if( r.top > r.bottom )
   {
      t = r.top;
      r.top = r.bottom;
      r.bottom = t;
   }  

   return r;
}
CV_INLINE CvRect RectToCvRect( RECT sr );
CV_INLINE CvRect RectToCvRect( RECT sr )
{
   sr = NormalizeRect( sr );
   return cvRect( sr.left, sr.top, sr.right - sr.left, sr.bottom - sr.top );
}
CV_INLINE RECT CvRectToRect( CvRect sr );
CV_INLINE RECT CvRectToRect( CvRect sr )
{
   RECT dr;
   dr.left = sr.x;
   dr.top = sr.y;
   dr.right = sr.x + sr.width;
   dr.bottom = sr.y + sr.height;  

   return dr;
}
CV_INLINE IplROI RectToROI( RECT r );
CV_INLINE IplROI RectToROI( RECT r )
{
   IplROI roi;
   r = NormalizeRect( r );
   roi.xOffset = r.left;
   roi.yOffset = r.top;
   roi.width = r.right - r.left;
   roi.height = r.bottom - r.top;
   roi.coi = 0;  

   return roi;
}
void  FillBitmapInfo( BITMAPINFO* bmi, int width, int height, int bpp, int origin )
{
   assert( bmi && width >= 0 && height >= 0 && (bpp == 8 || bpp == 24 || bpp == 32));  

   BITMAPINFOHEADER* bmih = &(bmi->bmiHeader);  

   memset( bmih, 0, sizeof(*bmih));
   bmih->biSize = sizeof(BITMAPINFOHEADER);
   bmih->biWidth = width;
   bmih->biHeight = origin ? abs(height) : -abs(height);
   bmih->biPlanes = 1;
   bmih->biBitCount = (unsigned short)bpp;
   bmih->biCompression = BI_RGB;
   if( bpp == 8 )
   {
      RGBQUAD* palette = bmi->bmiColors;
      int i;
      for( i = 0; i < 256; i++ )
      {
         palette[i].rgbBlue = palette[i].rgbGreen = palette[i].rgbRed = (BYTE)i;
         palette[i].rgbReserved = 0;
      }
   }
}
CvvImage::CvvImage()
{
   m_img = 0;
}
void CvvImage::Destroy()
{
   cvReleaseImage( &m_img );
}
CvvImage::~CvvImage()
{
   Destroy();
}
bool  CvvImage::Create( int w, int h, int bpp, int origin )
{
   const unsigned max_img_size = 10000;  

   if( (bpp != 8 && bpp != 24 && bpp != 32) ||
      (unsigned)w >=  max_img_size || (unsigned)h >= max_img_size ||
      (origin != IPL_ORIGIN_TL && origin != IPL_ORIGIN_BL))
   {
      assert(0); // most probably, it is a programming error
      return false;
   }
   if( !m_img || Bpp() != bpp || m_img->width != w || m_img->height != h )
   {
      if( m_img && m_img->nSize == sizeof(IplImage))
         Destroy();
      /* prepare IPL header */
      m_img = cvCreateImage( cvSize( w, h ), IPL_DEPTH_8U, bpp/8 );
   }
   if( m_img )
      m_img->origin = origin == 0 ? IPL_ORIGIN_TL : IPL_ORIGIN_BL;
   return m_img != 0;
}
void  CvvImage::CopyOf( CvvImage& image, int desired_color )
{
   IplImage* img = image.GetImage();
   if( img )
   {
      CopyOf( img, desired_color );
   }
}  

#define HG_IS_IMAGE(img)                                                  \
   ((img) != 0 && ((const IplImage*)(img))->nSize == sizeof(IplImage) && \
   ((IplImage*)img)->imageData != 0)  

void  CvvImage::CopyOf( IplImage* img, int desired_color )
{
   if( HG_IS_IMAGE(img) )
   {
      int color = desired_color;
      CvSize size = cvGetSize( img );
      if( color < 0 )
         color = img->nChannels > 1;
      if( Create( size.width, size.height,
         (!color ? 1 : img->nChannels > 1 ? img->nChannels : 3)*8,
         img->origin ))
      {
         cvConvertImage( img, m_img, 0 );
      }
   }
}
bool  CvvImage::Load( const char* filename, int desired_color )
{
   IplImage* img = cvLoadImage( filename, desired_color );
   if( !img )
      return false;  

   CopyOf( img, desired_color );
   cvReleaseImage( &img );  

   return true;
}
bool  CvvImage::LoadRect( const char* filename,
                   int desired_color, CvRect r )
{
   if( r.width < 0 || r.height < 0 ) return false;  

   IplImage* img = cvLoadImage( filename, desired_color );
   if( !img )
      return false;
   if( r.width == 0 || r.height == 0 )
   {
      r.width = img->width;
      r.height = img->height;
      r.x = r.y = 0;
   }
   if( r.x > img->width || r.y > img->height ||
      r.x + r.width < 0 || r.y + r.height < 0 )
   {
      cvReleaseImage( &img );
      return false;
   }
   /* truncate r to source image */
   if( r.x < 0 )
   {
      r.width += r.x;
      r.x = 0;
   }
   if( r.y < 0 )
   {
      r.height += r.y;
      r.y = 0;
   }
   if( r.x + r.width > img->width )
      r.width = img->width - r.x;  

   if( r.y + r.height > img->height )
      r.height = img->height - r.y;
   cvSetImageROI( img, r );
   CopyOf( img, desired_color );
   cvReleaseImage( &img );
   return true;
}  

bool  CvvImage::Save( const char* filename )
{
   if( !m_img )
      return false;
   cvSaveImage( filename, m_img );
   return true;
}
void  CvvImage::Show( const char* window )
{
   if( m_img )
      cvShowImage( window, m_img );
}
void  CvvImage::Show( HDC dc, int x, int y, int w, int h, int from_x, int from_y )
{
   if( m_img && m_img->depth == IPL_DEPTH_8U )
   {
      uchar buffer[sizeof(BITMAPINFOHEADER) + 1024];
      BITMAPINFO* bmi = (BITMAPINFO*)buffer;
      int bmp_w = m_img->width, bmp_h = m_img->height;
      FillBitmapInfo( bmi, bmp_w, bmp_h, Bpp(), m_img->origin );
      from_x = MIN( MAX( from_x, 0 ), bmp_w - 1 );
      from_y = MIN( MAX( from_y, 0 ), bmp_h - 1 );
      int sw = MAX( MIN( bmp_w - from_x, w ), 0 );
      int sh = MAX( MIN( bmp_h - from_y, h ), 0 );
      SetDIBitsToDevice(
         dc, x, y, sw, sh, from_x, from_y, from_y, sh,
         m_img->imageData + from_y*m_img->widthStep,
         bmi, DIB_RGB_COLORS );
   }
}
void  CvvImage::DrawToHDC( HDC hDCDst, RECT* pDstRect )
{
   if( pDstRect && m_img && m_img->depth == IPL_DEPTH_8U && m_img->imageData )
   {
      uchar buffer[sizeof(BITMAPINFOHEADER) + 1024];
      BITMAPINFO* bmi = (BITMAPINFO*)buffer;
      int bmp_w = m_img->width, bmp_h = m_img->height;
      CvRect roi = cvGetImageROI( m_img );
      CvRect dst = RectToCvRect( *pDstRect );
      if( roi.width == dst.width && roi.height == dst.height )
      {
         Show( hDCDst, dst.x, dst.y, dst.width, dst.height, roi.x, roi.y );
         return;
      }
      if( roi.width > dst.width )
      {
         SetStretchBltMode(
            hDCDst,           // handle to device context
            HALFTONE );
      }
      else
      {
         SetStretchBltMode(
            hDCDst,           // handle to device context
            COLORONCOLOR );
      }
      FillBitmapInfo( bmi, bmp_w, bmp_h, Bpp(), m_img->origin );
      ::StretchDIBits(
         hDCDst,
         dst.x, dst.y, dst.width, dst.height,
         roi.x, roi.y, roi.width, roi.height,
         m_img->imageData, bmi, DIB_RGB_COLORS, SRCCOPY );
   }
}
void  CvvImage::Fill( int color )
{
   cvSet( m_img, cvScalar(color&255,(color>>8)&255,(color>>16)&255,(color>>24)&255) );
}  

网上也有其他解决方案,这里就不细说了,给出链接可以自己去了解一下

方案二:

http://blog.csdn.net/dcrmg/article/details/51913160

方案三:

http://lh2078.blog.163.com/blog/static/5681137220131311492631/

二、对深度图像的实时处理

在第二章里面可以看到Kinect获取的深度图像,就是一个灰度图,用灰度等级表示距离信息,但很多时候看的不是很明显,而且对于复杂环境边框有很多黑色的噪音,不利于准确和直观判断距离信息。可以对深度进行上色处理,就是用不同的颜色表示不同的距离信息,这样就可以一目了然了。这里涉及到颜色空间的转换问题,就是RGB空间和HSV空间的转换。

(1)下面的代码展示了如何将之前的深度位数据取反获取更好的深度影像数据,把16微数据变成8位,并且将8位的数据对Kinect深度范围的最大值(4500mm)取模,这样就过滤掉了大于4500mm和小于0的,把这些设置成黑色,这些黑色位置的数据是不准确的。

Mat temp(nHeight, nWidth, CV_16UC1);    //建立图像矩阵
pDepthFrame->CopyFrameDataToArray(nHeight * nWidth, (UINT16 *)temp.data); //先把数据存入16位的图像矩阵中
temp.convertTo(depthImg, CV_8UC1, 255.0 / 4500);   //再把16位转换为8位

(2) 经过处理,图像的表现力提高了一些,但是如果能够将16位的灰度级用32位彩色表示效果会更好。当 RGB值一样时,就会呈现出灰色。灰度值的范围是0~255,0为黑色,255为白色,之间的颜色为灰色。现在将灰色值以RGB模式展现出来。代码如下:

void CBodyBasics::ProcessDepth(const UINT16* pBuffer, int nWidth, int nHeight, USHORT nMinDepth, USHORT nMaxDepth)
{
// Make sure we've received valid data
if (m_pDepthRGBX && pBuffer && (nWidth == cDepthWidth) && (nHeight == cDepthHeight))
{
RGBQUAD* pRGBX = m_pDepthRGBX;

// end pixel is start + width*height - 1
const UINT16* pBufferEnd = pBuffer + (nWidth * nHeight);

while (pBuffer < pBufferEnd)
{
USHORT depth = *pBuffer;
//深度值除以4500(深度探测的最大值),然后乘以255,这样就可以将深度数据转换到0至255之间
//采用颜色模式显示灰度较之前采用灰度模式显示能够显示更多的细节信息
BYTE intensity = static_cast<BYTE>((depth >= nMinDepth) && (depth <= nMaxDepth) ? (depth * 255 / 4500) : 0);
pRGBX->rgbRed = intensity;
pRGBX->rgbGreen = intensity;
pRGBX->rgbBlue = intensity;

++pRGBX;
++pBuffer;
}

// Draw the data with OpenCV
Mat DepthImage(nHeight, nWidth, CV_8UC4, m_pDepthRGBX);
Mat showImg= DepthImage.clone();
imshow("【<1>图像效果增强】", showImg);
}
}

上面的代码中,将彩色影像的格式改为了Bgr32位,这意味每一个像素占用32位(4个字节)。每一个R,G,B分别占8位,剩余8位留用。这种模式限制了RGB的取值为0-255,所以需要将深度值转换到这一个范围内。除此之外,我们还设置了最小最大的探测范围,这个和之前的一样,任何不在范围内的都设置为白色。将深度值除以4500(深度探测的最大值),然后乘以255,这样就可以将深度数据转换到0至255之间了。运行后效果如图所示,可以看出,采用颜色模式显示灰度较之前采用灰度模式显示能够显示更多的细节信息。

(3)深度数据的上色(RGB)显示

将深度数据值转化到0-255并用RGB模式进行显示可以起到增强图像的效果,能够从图像上直观的看出更多的深度细节信息。还有另外一种简单,效果也不错的方法,那就是将深度数据值转换为色调和饱和度并用图像予以显示。下面的代码展示了这一实现:

//处理深度数据,RGB显示,上色
void CBodyBasics::ProcessrgbDepth(const UINT16* pBuffer, int nWidth, int nHeight, USHORT nMinDepth, USHORT nMaxDepth)
{
// Make sure we've received valid data
if (m_pDepthRGBX && pBuffer && (nWidth == cDepthWidth) && (nHeight == cDepthHeight))
{
RGBQUAD* pRGBX = m_pDepthRGBX;

// end pixel is start + nWidth*nHeight - 1
const UINT16* pBufferEnd = pBuffer + (nWidth * nHeight);

while (pBuffer < pBufferEnd)
{
USHORT depth= *pBuffer;
int hue;
BYTE  intensity[3]; 

if (depth < nMinDepth || depth > nMaxDepth)
{
pRGBX->rgbRed = 0x00;
pRGBX->rgbGreen = 0x00;
pRGBX->rgbBlue = 0x00;
}
else
{
hue = ((360 * depth / 0xFFF) + nMinDepth);
ConvertHslToRgb(hue, 100, 100, intensity);

pRGBX->rgbRed = intensity[0];
pRGBX->rgbGreen = intensity[1];
pRGBX->rgbBlue = intensity[2];
}
++pRGBX;
++pBuffer;
}

// Draw the data with OpenCV
Mat DepthImage(nHeight, nWidth, CV_8UC4, m_pDepthRGBX);
 Mat showImg = DepthImage.clone();
imshow("【<8>深度图上色】", showImg);
}
}

以上代码中使用了ConvertHslToRgb这一函数,该函数的作用是进行两个颜色空间的转换,就是将H(Hue色调)S(Saturation饱和度)L(Light亮度)颜色空间转换到RGB颜色空间的函数。参考http://www.cnblogs.com/yangecnu/archive/2012/04/04/KinectSDK_Depth_Image_Processing_Part1.html

转化的代码如下:

//颜色空间转换
void CBodyBasics::ConvertHslToRgb(int hue, double saturation, double lightness, byte rgb[])
{
//离摄像头近的呈蓝色,然后由近至远颜色从蓝色变为紫色,最远的呈红色
double red = 0.0;
double green = 0.0;
double blue = 0.0;
hue = hue % 360;
saturation = saturation / 100.0;
lightness = lightness / 100.0;

if (saturation == 0.0)
{
red = lightness;
green = lightness;
blue = lightness;
}
else
{
double huePrime = hue / 60.0;
int x = (int)huePrime;
double xPrime = huePrime - (double)x;
double L0 = lightness * (1.0 - saturation);
double L1 = lightness * (1.0 - (saturation * xPrime));
double L2 = lightness * (1.0 - (saturation * (1.0 - xPrime)));

switch (x)
{
case 0:
red = lightness;
green = L2;
blue = L0;
break;
case 1:
red = L1;
green = lightness;
blue = L0;
break;
case 2:
red = L0;
green = lightness;
blue = L2;
break;
case 3:
red = L0;
green = L1;
blue = lightness;
break;
case 4:
red = L2;
green = L0;
blue = lightness;
break;
case 5:
red = lightness;
green = L0;
blue = L1;
break;
}
}

rgb[0] = (byte)(255.0 * red);
rgb[1] = (byte)(255.0 * green);
rgb[2] = (byte)(255.0 * blue);
}

运行程序,会得到如下图结果,离摄像头近的呈蓝色,然后由近至远颜色从蓝色变为紫色,最远的呈红色。图中,我手上托着截图用的键盘,所以可以看到,床离摄像头最近,呈蓝色,键盘比人体里摄像头更近,呈谈蓝色,人体各部分里摄像头的距离也不一样,胸、腹、头部离摄像头更近。后面的墙离摄像头最远,呈橙色至红色。

(4)利用opencv的库函数对原始深度图做一些常见处理

在系统中我加了一些常见的图像处理方法,大概有如下几种方法:

case 1:
ProcessDepth(pBuffer, nWidth, nHeight, nDepthMinReliableDistance, nDepthMaxReliableDistance);
break;
case 2:
createTrackbar("参数值:", "【<2>高斯滤波】", &g_nGaussianBlurValue, 40, on_GaussianBlur);	//创建滑动条
on_GaussianBlur(g_nGaussianBlurValue, 0);	//调用回调函数,	break;	//下同
case 3:
{
createTrackbar("参数值:", "【<3>双边滤波】", &g_nBilateralFilterValue, 50, on_BilateralFilter);
on_BilateralFilter(g_nBilateralFilterValue, 0);
break;
}
case 4:
createTrackbar("参数值:", "【<4>Canny边缘检测】", &g_cannyLowThreshold, 120, on_Canny);
on_Canny(g_cannyLowThreshold, 0);
break;

case 5:
createTrackbar("参数值:", "【<5>Sobel边缘检测】", &g_sobelKernelSize, 3, on_Sobel);
on_Sobel(g_sobelKernelSize, 0);
break;
case 6:
createTrackbar("参数值:", "【<6>二值化】", &g_TwoThreshold, 255, on_Twozation);
on_Twozation(g_TwoThreshold, 0);
break;
case 7:
on_AdaptiveThreshold();
break;
case 8:
createTrackbar("腐蚀/膨胀", "【<8>腐蚀和膨胀】", &g_nTrackbarNumer, 1, on_TrackbarNumChange);
createTrackbar("参数值", "【<8>腐蚀和膨胀】", &g_nStructElementSize, 21, on_ElementSizeChange);
on_TrackbarNumChange(g_nTrackbarNumer, 0);
on_ElementSizeChange(g_nStructElementSize,0);
break;
case 9:
ProcessrgbDepth(pBuffer, nWidth, nHeight, nDepthMinReliableDistance, nDepthMaxReliableDistance);//上色
break;

回调函数如下:

static void on_GaussianBlur(int, void*)
{
//高斯滤波操作
 GaussianBlur(depthimg, d_dstImage, Size(g_nGaussianBlurValue * 2 + 1, g_nGaussianBlurValue * 2 + 1), 0, 0);
//显示窗口
 imshow("【<2>高斯滤波】", d_dstImage);
}
 //双边滤波函数
 static void on_BilateralFilter(int, void *)
 {
 bilateralFilter(depthimg, d_dstImage, g_nBilateralFilterValue, g_nBilateralFilterValue * 2, g_nBilateralFilterValue / 2);
 imshow("【<3>双边滤波】", d_dstImage);
 }
 //canny算子
 void on_Canny(int, void*)
 {
 Mat g_cannyDetectedEdges;	//Canny边缘检测相关变量
 // 先使用 3x3内核来降噪
 blur(depthimg, g_cannyDetectedEdges, Size(3, 3));

 // 运行我们的Canny算子
 Canny(g_cannyDetectedEdges, g_cannyDetectedEdges, g_cannyLowThreshold, g_cannyLowThreshold * 3, 3);

 //先将g_dstImage内的所有元素设置为0
 d_dstImage = Scalar::all(0);

 //使用Canny算子输出的边缘图g_cannyDetectedEdges作为掩码,来将原图g_srcImage拷到目标图g_dstImage中
 depthimg.copyTo(d_dstImage, g_cannyDetectedEdges);

 //显示效果图
 imshow("【<4>Canny边缘检测】", d_dstImage);
 }

 //Sobel算子
 void on_Sobel(int, void*)
 {
 Mat g_sobelGradient_X, g_sobelGradient_Y;	//Sobel边缘检测相关变量
 Mat g_sobelAbsGradient_X, g_sobelAbsGradient_Y;
 // 求 X方向梯度
 Sobel(depthimg, g_sobelGradient_X, CV_16S, 1, 0, (2 * g_sobelKernelSize + 1), 1, 1, BORDER_DEFAULT);
 convertScaleAbs(g_sobelGradient_X, g_sobelAbsGradient_X);//计算绝对值,并将结果转换成8位  

 // 求Y方向梯度
 Sobel(depthimg, g_sobelGradient_Y, CV_16S, 0, 1, (2 * g_sobelKernelSize + 1), 1, 1, BORDER_DEFAULT);
 convertScaleAbs(g_sobelGradient_Y, g_sobelAbsGradient_Y);//计算绝对值,并将结果转换成8位  

 // 合并梯度
 addWeighted(g_sobelAbsGradient_X, 0.5, g_sobelAbsGradient_Y, 0.5, 0, d_dstImage);

 //显示效果图
 imshow("【<5>Sobel边缘检测】", d_dstImage);

 }

 //二值化处理
 static void on_Twozation(int, void*)
 {
 // 转为二值图
 cvThreshold(&IplImage(depthimg), &IplImage(d_dstImage), g_TwoThreshold, 255, CV_THRESH_BINARY);
 imshow("【<6>二值化】", d_dstImage);
 //cvReleaseImage((IplImage**)&t_dstImage);
 }

 static void on_AdaptiveThreshold()
 {
 // 转为二值图
 cvAdaptiveThreshold(&IplImage(depthimg), &IplImage(d_dstImage), 255, CV_ADAPTIVE_THRESH_MEAN_C, CV_THRESH_BINARY, 3, 5);
 imshow("【<7>自适应二值化】", d_dstImage);
 }

 //            描述:进行自定义的腐蚀和膨胀操作
 void Process()
 {
 //获取自定义核
 Mat element = getStructuringElement(MORPH_RECT, Size(2 * g_nStructElementSize + 1, 2 * g_nStructElementSize + 1), Point(g_nStructElementSize, g_nStructElementSize));

 //进行腐蚀或膨胀操作
 if (g_nTrackbarNumer == 0) {
 erode(depthimg, d_dstImage, element);
 }
 else{
 dilate(depthimg, d_dstImage, element);
 }

 //显示效果图
 imshow("【<8>腐蚀和膨胀】", d_dstImage);
 }

 //            描述:腐蚀和膨胀之间切换开关的回调函数
 void on_TrackbarNumChange(int, void *)
 {
 //腐蚀和膨胀之间效果已经切换,回调函数体内需调用一次Process函数,使改变后的效果立即生效并显示出来
 Process();
 }

 //            描述:腐蚀和膨胀操作内核改变时的回调函数
 void on_ElementSizeChange(int, void *)
 {
 //内核尺寸已改变,回调函数体内需调用一次Process函数,使改变后的效果立即生效并显示出来
 Process();
 }

这些回调函数都有详细注释,用起来都非常的简单,有什么问题欢迎留言一起讨论交流。这里主要涉及到常见的opencv库函数的使用方法。这里顺便给大家推荐几个学习opencv的好博客。

(1)浅墨大神:http://blog.csdn.net/zhmxy555/article/category/1923021

这个比较新,主要是针对opencv2.4.9和3.0的

(2)MoreWindows大神:

http://blog.csdn.net/MoreWindows/article/category/1291764

这个比较早,opencv版本是2.3.1,

(3)http://blog.csdn.net/thefutureisour/article/category/1126906

。。。。

还有很多其他优秀的学习网站,内容其实差不多,都是讲一些常见函数的使用方法,毕竟opencv主要就是一些图像处理相关的库函数,学会使用这些函数可以了,当然了,如果你比较厉害,也可以研究一下opencv的源代码,反正也是开源的,只是要用cmake工具重新编译一下opencv,就可以看到所以的源码,好像是67万多行吧,有点多是吧,多看看人家那些优秀的代码是很有益的。

好了,这节内容差不多就是这样,主要内容是

1.如何在MFC的picture控件中显示,还是沿用opencv早期版本的做法

2.对kinect获取的深度图像作进一步的处理,让显示效果更好和更直观,主要有对原始图的16位灰度级显示成32位,并且用前面24位分别表示rgb分量,这样可以显示更多的细节信息,还有深度图的彩色显示,以及opencv里面的常见图像处理的基本方法。

下一篇文章见~

超跑开起来,要先制定一个小目标,比如先买辆兰博基尼限量款~哈哈哈

时间: 2024-10-15 13:29:03

第七章 KinectV2结合MFC显示和处理图像数据(下)的相关文章

[转]MFC子线程更改图像数据后更新主窗口图像显示方法

程序思路是由外部的输入输出控制卡发出采集图像信号,之后相机采集图像得到图像数据指针,接收图像数据指针创建成图像最后显示到MFC对话框应用程序的Picture Control控件上,同时,为了标定相机位置,在主对话框类CMyDlg的OnPaint函数中有对Picture Control的绘图操作(不改变图像数据,进行画线,画矩形等操作). 设计时考虑到I/O卡何时发出采集信号或者相机何时得到图像数据指针是不确定的(不使用OnTime),同时考虑到I/O卡和相机的回调函数与主程序之间的数据交换会更加

SPRING IN ACTION 第4版笔记-第七章Advanced Spring MVC-006- 如何保持重定向的request数据(用model、占位符、RedirectAttributes)

一.redirect为什么会丢数据? when a handler method completes, any model data specified in the method is copied into the request as request attributes, and the request is forwarded to the view for rendering. Because it’s the same request that’s handled by both

《深入理解计算机系统》第七章 链接 读书笔记

第七章链接 链接:将各种代码和数据部分收集起来并组合成为一个单一文件的过程,这个文件可被加载.连接可执行于编译时.加载时.运行时.由叫链接器的程序执行. 链接器使得分离编译成为可能.使得可以把 大程序分解成小模块,利于管理. 理解链接器将帮助你构筑大程型序:避免一些危险的编程错误:帮助你理解语言的作用域规则是如何实现的:帮助你理解其他重要的系统概念:使你能够利用共享库. 7.1 编译器驱动程序 驱动程序的工作:1.运行C预处理器,将C源程序(.c)翻译成一个ASCⅡ码中间文件(.i):2.运行C

《深入理解计算机系统》第七章学习笔记(初稿)

第七章 链接 链接是将各种代码和数据部分收集起来并组合成为一个单一文件的过程,这个文件可被加载(或拷贝)到存储器并执行.链接可以执行于编译时,也就是在源代码被翻译成机器代码时:也可以执行于加载时,也就是在程序被加载器加载到存储器并执行时:甚至执行于运行时,由应用程序来执行.在早期的计算机系统中,链接是手动执行的.在现代系统中,链接是由叫链接器的自动执行的. 理解链接器将帮助构造大型程序 理解链接器将帮助避免一些危险的编程错误 理解链接器将帮助语言的作用域规则是如何实现的 理解链接器将帮助其他重要

《Linux内核设计与实现》第七章读书笔记

第七章 链接 链接是将各种代码和数据部分收集起来并组合成为一个单一文件的过程,这个文件可被加载(或拷贝)到存储器并执行. 链接可以执行于编译时,也就是在源代码被翻译成机器代码时:也可以执行于加载时,也就是在程序被加载器加载到存储器并执行时:甚至执行于运行时,由应用程序来执行. 在早期的计算机系统中,链接是手动执行的.在现代系统中,链接是由叫链接器的自动执行的. 7.1 编译器驱动程序 大多数编译系统提供编译驱动程序,它代表用户在需要时调用语言预处理器.编译器.汇编器和链接器. 例子: 函数mai

《深入理解计算机系统》第七章 链接

<深入理解计算机系统>第七章 链接 链接是将各种代码和数据部分收集起来并组合成为一个单一文件的过程,这个文件可被加载(货被拷贝)到存储器并执行. 链接的时机 编译时,也就是在源代码被翻译成机器代码时 加载时,也就是在程序被加载器加载到存储器并执行时 运行时,由应用程序执行 链接器使分离编译称为可能. 链接是将各种代码和数据部分收集起来并组合成为一个单一文件的过程,这个文件可被加载(或拷贝)到存储器并执行. 链接可以执行于编译时,也就是在源代码被翻译成机器代码时:也可以执行于加载时,也就是在程序

CSS3秘笈:第七章

第七章  margin.padding和border 1.盒模型:四个属性: (1)padding:内容与其边框线之间的空间. (2)border:盒子周围的直线 (3)background-color:用来填充边框内部空间的,包括padding区域. (4)margin:一个标签和另一个标签之间的间隔. 2.padding和margin的区别:padding是在内容和边框之间增加空间.避免内容被硬塞在方框里面,它还包括背景区域,因此padding占用的空间也可以为空白的内容.而margin则是

使用JQuery快速高效制作网页交互特效第二章到第七章

第二章 JavaScript对象 浏览器对象模型(BOM)是JavaScript的组成之一,window对象是整个BOM的核心 window对象的常用方法 prompt():显示可提示用户输入的对话框 alert():显示一个带有提示信息和一个"确定"的按钮的警示对话框 confirm():显示一个滴啊有提示信息,"确定"和"取消"按钮的对话框 close():关闭浏览器窗口 open():打开一个新的浏览器窗口,加载给定URL制定的文档 set

第七章、epub文件处理 -- 解析 .xhtml文件 (一)

第七章.epub文件处理 -- 解析 .xhtml文件 (一) 本章将介绍代码如何利用ZLTextPlainModel类来分别处理.xhtml文件中的文本信息与标签信息. 本章涉及的核心类是ZLTextPlainModel类.ZLTextWritablePlainModel类.CachedCharStorage类.XHTMLTagAction接口实现类 .xhtml文件中包含着两种信息:文本信息与标签信息.我们需要先正确解析出标签信息代表的结构,才能正确得将文本信息显示在屏幕上. 举个例子:(这