本次介绍的是基于张正友标定法的基础并结合MFC的界面化操作来实现的单目相机标定。界面如下:
程序实在VS2013+opencv2.4.9平台上编写的,由于这个版本的opencv没有CvvImage类,所以简单提供如下:
#pragma once
#ifndef CVVIMAGE_CLASS_DEF
#define CVVIMAGE_CLASS_DEF
#include "opencv.hpp"
/* 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
#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) );
}
拍摄图片部分的程序如下:
void CmyCameraCalibrationDlg::OnBnClickedOpen()
{
// TODO: 在此添加控件通知处理程序代码
// TODO: 在此添加控件通知处理程序代码
Capture = cvCreateCameraCapture(0); //
if (Capture==0)
{
MessageBox(_T("无法连接摄像头!!!"));
return;
}
frame = cvQueryFrame(Capture); //从摄像头或者文件中抓取并返回一帧
pDC = GetDlgItem(IDC_STATIC_PIC1)->GetDC();//GetDlgItem(IDC_PIC_STATIC)意思为获取显示控件的句柄(句柄就是指针),获取显示控件的DC
GetDlgItem(IDC_STATIC_PIC1)->GetClientRect(&rect);
hDC = pDC->GetSafeHdc();//获取显示控件的句柄
CvvImage m_CvvImage;
m_CvvImage.CopyOf(frame, 1); //复制该帧图像
m_CvvImage.DrawToHDC(hDC, &rect); //显示到设备的矩形框内
ReleaseDC(pDC);
SetTimer(1, 25, NULL); //定时器,定时时间和帧率一致
}
void CmyCameraCalibrationDlg::OnTimer(UINT_PTR nIDEvent)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
pDC = GetDlgItem(IDC_STATIC_PIC1)->GetDC();//GetDlgItem(IDC_PIC_STATIC)意思为获取显示控件的句柄(句柄就是指针),获取显示控件的DC
GetDlgItem(IDC_STATIC_PIC1)->GetClientRect(&rect);
hDC = pDC->GetSafeHdc();//获取显示控件的句柄
frame = cvQueryFrame(Capture); //从摄像头或者文件中抓取并返回一帧
CvvImage m_CvvImage;
m_CvvImage.CopyOf(frame, 1); //复制该帧图像
m_CvvImage.DrawToHDC(hDC, &rect); //显示到设备的矩形框内
CDialogEx::OnTimer(nIDEvent);
}
void CmyCameraCalibrationDlg::OnBnClickedClose()
{
// TODO: 在此添加控件通知处理程序代码
if (!Capture)
{
MessageBox(_T("没有打开摄像头!!!"));
return;
}
cvReleaseCapture(&Capture);
}
void CmyCameraCalibrationDlg::OnBnClickedClatch()
{
// TODO: 在此添加控件通知处理程序代码
if (!Capture)
{
MessageBox(_T("没有打开摄像头!!!"));
return;
}
//cvReleaseCapture(&Capture);
pDC = GetDlgItem(IDC_STATIC_PIC2)->GetDC();//GetDlgItem(IDC_PIC_STATIC)意思为获取显示控件的句柄(句柄就是指针),获取显示控件的DC
GetDlgItem(IDC_STATIC_PIC2)->GetClientRect(&rect);
hDC = pDC->GetSafeHdc();//获取显示控件的句柄
//frame = cvLoadImage("D:\\Documents\\Visual Studio 2013\\Projects\\beijing.jpg"); //图片读取路径可以自己设定
frame = cvQueryFrame(Capture); //从摄像头或者文件中抓取并返回一帧
Mat image(frame,true);
sprintf_s(name,"%s%d%s", "..\\pic\\",num++,".jpg");
imwrite(name,image);
CvvImage m_CvvImage;
m_CvvImage.CopyOf(frame, 1); //复制该帧图像
m_CvvImage.DrawToHDC(hDC, &rect); //显示到设备的矩形框内
ReleaseDC(pDC);
}
相机标定部分利用的是张正友标定法,根据界面上提供的棋盘格宽和高编辑框,可以去自定义自己需要的尺寸,标定程序如下:
void CmyCameraCalibrationDlg::OnBnClickedCalibration()
{
// TODO: 在此添加控件通知处理程序代码
// TODO: 在此添加控件通知处理程序代码
UpdateData(true);
ifstream fin("calibdata.txt");
ofstream fout("calibresult.txt");
ofstream fout1("chesscorners.txt");
//cout << "=====读取棋盘格图像=====" << endl;
//cout << "======提取角点信息======" << endl;
m_run += "=====读取棋盘格图像=====\r\n";
m_run += "======提取角点信息======\r\n";
//--------------定义变量-------------------
Size image_size;
int wid = GetDlgItemInt(IDC_EDIT2);
int hei = GetDlgItemInt(IDC_EDIT3);
Size board_size = Size(wid,hei); //标定板上每行、列的角点数
vector<Point2f> image_point_buf; //缓存每幅图像上检测到的角点
vector<vector<Point2f>> image_point_seq; //保存检测到的所有角点
string filename;
//char name[100];
int image_count = 0;
while(getline(fin,filename))
{
image_count++;
Mat img = imread(filename);
if(!img.data){
//cout << "读取图片错误" << endl;
MessageBox(_T("读取图片错误"));
exit(1);
}
if(image_count == 1){ //读入第一幅图像时获取图像信息
image_size.width = img.cols;
image_size.height = img.rows;
//cout << "image_size.width = " << image_size.width << endl;
//cout << "image_size.height = " << image_size.height << endl;
CString s1,s2;
s1.Format(_T("%d\r\n"),image_size.width);
s2.Format(_T("%d\r\n"),image_size.height);
m_run += "image_size.width = ";
m_run += s1;
m_run += "image_size.height = ";
m_run += s2;
}
if(0 == findChessboardCorners(img,board_size,image_point_buf))
{
//cout << "can not find chessboard corners!\n"; //没找到角点
MessageBox(_T("can not find chessboard corners!"));
exit(1);
}
else
{
Mat view_gray;
cvtColor(img,view_gray,CV_RGB2GRAY);
//--------亚像素精确化------------
find4QuadCornerSubpix(view_gray,image_point_buf,Size(5,5));
//保存角点
image_point_seq.push_back(image_point_buf);
//在图像上显示角点位置
drawChessboardCorners(view_gray,board_size,image_point_buf,false); //用于在图片中标记角点
pDC = GetDlgItem(IDC_STATIC_PIC3)->GetDC();//GetDlgItem(IDC_PIC_STATIC)意思为获取显示控件的句柄(句柄就是指针),获取显示控件的DC
GetDlgItem(IDC_STATIC_PIC3)->GetClientRect(&rect);
hDC = pDC->GetSafeHdc();//获取显示控件的句柄
IplImage imgTmp = view_gray;
IplImage *input = cvCloneImage(&imgTmp);
CvvImage m_CvvImage;
m_CvvImage.CopyOf(input, 1); //复制该帧图像
m_CvvImage.DrawToHDC(hDC, &rect); //显示到设备的矩形框内
ReleaseDC(pDC);
}
}
int total = image_point_seq.size();
//cout << "total = " << total << endl;
CString s3;
s3.Format(_T("%d\r\n"),"total = ",total);
m_run += "total = ";
m_run += s3;
int CornerNum = board_size.width*board_size.height;
for(int i = 0;i < total;i++)
{
fout1 << "--->第" << i+1 << "图片数据 --->" << endl;
for(int j = 0;j < CornerNum;j++){
fout1 << "--->第" << j+1 << "个角点的横坐标:" << image_point_seq[i][j].x << endl;
fout1 << "--->第" << j+1 << "个角点的纵坐标:" << image_point_seq[i][j].y << endl;
fout1 << endl;
}
fout1 << endl;
}
//cout << "角点提取完成" << endl;
//GetDlgItem(IDC_EDIT1)->SetWindowText(_T("角点提取完成"));
m_run += "角点提取完成\r\n";
//---------------------------------------以下是摄像机标定------------------------------------
//cout << "开始标定......." << endl;
m_run += "开始标定.......\r\n";
//---------------棋盘三维信息----------------
Size square_size = Size(10,10); //实际测量得到的标定板上每个棋盘格的大小
vector<vector<Point3f>> object_points; //保存标定板上角点的三维坐标
//---------------内外参数--------------------
Mat cameraMatrix = Mat(3,3,CV_32FC1,Scalar::all(0)); //相机内参矩阵
vector<int> point_counts; //每幅图像中角点的数量
Mat distCoeffs = Mat(1,5,CV_32FC1,Scalar::all(0)); //相机的5个畸变系数:k1,k2,p1,p2,k3
vector<Mat> tvecsMat; //每幅图像的旋转向量
vector<Mat> rvecsMat; //每幅图像的平移变量
//---------------初始化标定板上的角点的三维坐标----------------------
int i,j,t;
for(t=0;t<image_count;t++)
{
vector<Point3f> tempPointSet;
for(i=0;i<board_size.height;i++)
{
for(j=0;j<board_size.width;j++){
Point3f realPoint;
//假定标定板放在世界坐标系中的z=0的平面上
realPoint.x = i*square_size.width;
realPoint.y = j*square_size.height;
realPoint.z = 0;
tempPointSet.push_back(realPoint);
}
}
object_points.push_back(tempPointSet);
}
//初始化每幅图像中的角点数量,假定每幅图像都可以看到完整的标定板
for(i=0;i<image_count;i++)
point_counts.push_back(board_size.width*board_size.height);
//----------------------------------------开始标定-------------------------------------------------
calibrateCamera(object_points,image_point_seq,image_size,cameraMatrix,distCoeffs,rvecsMat,tvecsMat,0);
//cout << "标定完成" << endl;
//Dlg1 += "标定完成\r\n";
//对标定结果进行评价
//cout << "开始评价标定结果......" << endl;
m_run += "开始评价标定结果......\r\n";
double total_err = 0.0; //所有图像的平均误差总和
double err = 0.0; //每幅图像的平均误差
vector<Point2f> image_points2; //保存重新计算得到的投影点
//cout << "每幅图像的标定误差:" << endl;
//Dlg1 += "每幅图像的标定误差:\r\n";
fout << "每幅图像的标定误差:" << endl;
for(i=0;i<image_count;i++)
{
vector<Point3f> tempPointSet = object_points[i];
//通过得到的摄像机内外参数对空间的三维点进行重新投影计算,得到新的投影点
projectPoints(tempPointSet,rvecsMat[i],tvecsMat[i],cameraMatrix,distCoeffs,image_points2);
//计算新旧投影点之间的误差
vector<Point2f> tempImagePoint = image_point_seq[i];
Mat tempImagePointMat = Mat(1,tempImagePoint.size(),CV_32FC2);
Mat image_points2Mat = Mat(1,image_points2.size(),CV_32FC2);
for(int j = 0;j < tempImagePoint.size();j++)
{
image_points2Mat.at<Vec2f>(0,j) = Vec2f(image_points2[j].x, image_points2[j].y);
tempImagePointMat.at<Vec2f>(0,j) = Vec2f(tempImagePoint[j].x, tempImagePoint[j].y);
}
err = norm(image_points2Mat, tempImagePointMat, NORM_L2);
total_err += err/= point_counts[i];
//std::cout<<"第"<<i+1<<"幅图像的平均误差:"<<err<<"像素"<<endl;
fout<<"第"<<i+1<<"幅图像的平均误差:"<<err<<"像素"<<endl;
}
//std::cout<<"总体平均误差:"<<total_err/image_count<<"像素"<<endl;
CString s5;
s5.Format(_T("%f"),total_err/image_count);
m_run += "总体平均误差:";
m_run += s5;
m_run += "像素\r\n";
fout<<"总体平均误差:"<<total_err/image_count<<"像素"<<endl<<endl;
//std::cout<<"评价完成!"<<endl;
m_run += "评价完成!\r\n";
//保存标定结果
//std::cout<<"开始保存定标结果………………"<<endl;
m_run += "开始保存定标结果………………\r\n";
Mat rotation_matrix = Mat(3,3,CV_32FC1, Scalar::all(0)); /* 保存每幅图像的旋转矩阵 */
fout<<"相机内参数矩阵:"<<endl;
fout<<cameraMatrix<<endl<<endl;
fout<<"畸变系数:\n";
fout<<distCoeffs<<endl<<endl<<endl;
for (int i=0; i<image_count; i++)
{
fout<<"第"<<i+1<<"幅图像的旋转向量:"<<endl;
fout<<tvecsMat[i]<<endl;
/* 将旋转向量转换为相对应的旋转矩阵 */
Rodrigues(tvecsMat[i],rotation_matrix);
fout<<"第"<<i+1<<"幅图像的旋转矩阵:"<<endl;
fout<<rotation_matrix<<endl;
fout<<"第"<<i+1<<"幅图像的平移向量:"<<endl;
fout<<rvecsMat[i]<<endl<<endl;
}
//std::cout<<"完成保存"<<endl;
m_run += "完成保存\r\n";
fout<<endl;
/************************************************************************
显示定标结果
*************************************************************************/
Mat mapx = Mat(image_size,CV_32FC1);
Mat mapy = Mat(image_size,CV_32FC1);
Mat R = Mat::eye(3,3,CV_32F);
//std::cout<<"保存矫正图像"<<endl;
m_run += "保存矫正图像\r\n";
string imageFileName;
std::stringstream StrStm;
for (int i = 0 ; i != image_count ; i++)
{
//std::cout<<"Frame #"<<i+1<<"..."<<endl;
initUndistortRectifyMap(cameraMatrix,distCoeffs,R,cameraMatrix,image_size,CV_32FC1,mapx,mapy);
StrStm.clear();
imageFileName.clear();
string filePath="..\\pic\\chess";
StrStm<<i+1;
StrStm>>imageFileName;
filePath+=imageFileName;
filePath+=".bmp";
Mat imageSource = imread(filePath);
if(!imageSource.data)
MessageBox(_T("未读取图像"));
Mat newimage = imageSource.clone();
//另一种不需要转换矩阵的方式
//undistortPoints(imageSource,newimage,cameraMatrix,distCoeffs);
remap(imageSource,newimage,mapx, mapy, INTER_LINEAR);
StrStm.clear();
filePath.clear();
StrStm<<"..\\pic_d\\";
StrStm<<i+1;
StrStm>>imageFileName;
imageFileName += "_d.jpg";
imwrite(imageFileName,newimage);
}
//std::cout<<"保存结束"<<endl;
m_run += "矫正图像保存完成\r\n";
UpdateData(false);
}
最后提供下查看矫正过后的图片的程序:
int pic_num = 1;
void CmyCameraCalibrationDlg::OnBnClickedNext()
{
// TODO: 在此添加控件通知处理程序代码
SetDlgItemText(IDC_NEXT,_T("下一张"));
Mat pic_d;
if(pic_num > 14)
pic_num = 1;
else
{
char str_name[100];
sprintf_s(str_name,"%s%d%s","..\\pic_d\\",pic_num++,"_d.jpg");
pic_d = imread(str_name);
if(!pic_d.data)
MessageBox(_T("未读取图像"));
pDC = GetDlgItem(IDC_STATIC_PIC4)->GetDC();//GetDlgItem(IDC_PIC_STATIC)意思为获取显示控件的句柄(句柄就是指针),获取显示控件的DC
GetDlgItem(IDC_STATIC_PIC4)->GetClientRect(&rect);
hDC = pDC->GetSafeHdc();//获取显示控件的句柄
IplImage imgTmp = pic_d;
IplImage *input = cvCloneImage(&imgTmp);
CvvImage m_CvvImage;
m_CvvImage.CopyOf(input, 1); //复制该帧图像
m_CvvImage.DrawToHDC(hDC, &rect); //显示到设备的矩形框内
ReleaseDC(pDC);
}
}
下面附上工程代码链接:http://download.csdn.net/download/qq_20127603/10137150