MFC 获取摄像头 实时显示并图像处理(光斑自动对焦)

第一次写CSDN论文,给自己的毕设做个总结

对于我这个常年用C语言的渣渣,突然接触需要C++的MFC和Opencv,就变成毕设困难户了,好在有CSDN上大神们的帮助和同班同学热心的指导,总算是上手了mfc和Opencv。

废话不多说,直接进入正题。

PART 1  :   MFC界面的组件认识

在VS2013中新建好mfc项目,给这个项目取个动人的 名字“HuaQ_mfc”,然后在dialog中找到了对应的

:英文直译,按钮;

:英文直译,静态的字,也就是是说,这玩意加到上面去后不能再程序中就行修改;

:这个组件是用来放摄像头捕获的图像

:可编辑,在这里用来放所需显示的(清晰度 and 重心位置坐标);

还有好多的组件,因为用不到所以没有多做介绍。

PART 2 :  mfc组件的参数修改

对于button(按钮)

摄像头部分:        1.打开摄像头   2.关闭摄像头 3.图像处理自动对焦 

串口部分(作为上位机和下位机通讯):    1.打开串口  2.关闭串口 3. X轴移动 4. Y轴移动 5.Z轴移动 

分别右键所对应的button,单击属性,只需要修改两个参数即可(1.Caption-在窗口上显示的name 2.ID-每个组件的only_name)

dialog  -------  Caption:光斑自动对焦系统   ID:IDD_HUAQ_MFC_DIALOG

打开摄像头(button) ------- Caption:打开摄像头   ID:IDC_OPEN_CAMERA

关闭摄像头 (button)------- Caption:关闭摄像头   ID:IDC_CLOSE_CAMERA

打开串口(button) -------    Caption:打开串口   ID:IDC_OPEN_PORT

关闭串口(button) -------    Caption:关闭串口   ID:IDC_CLOSE_PORT

图像处理自动对焦(button) ---------Caption:图像处理自动对焦   ID:IDC_IMAGE_DEAL

X,Y,Z轴三个移动的button,因为是做自动对焦,手动用到的不多,并未对其实的控制进行编程。

Caption什么的,不装逼,用中文,I love China ,how about you ?

ID:中文直译

对于static Text  :我弄了两个 

first --- Caption :清晰度  (ID不需要改)            second ------ Caption : 重心坐标值   (ID不需要改)

在static Text后面放上 对应的 Edit Control,用来显示数值的变化。

对于Edit Control :

清晰度所对应 --------- Caption: 不用改    ID:IDC_Definition_Edit

重心坐标值对应  ------ Caption: 不用改    ID:IDC_Radius_Edit(这里并没有用坐标的翻译,因为最初自己做的时候是判断图像半径的大小变化,但是后面又引入了其他类型的图像,用最小二乘元拟合法做失败了,最终使用能够一统江山的算法——重心坐标)

PART 3 :   摄像头的捕获显示

所做的一切,都是在opencv环境配置好,且在X64上运行

添加头文件

#include "stdafx.h"
#include "opencv_mfc_2.h"
#include "opencv_mfc_2Dlg.h"
#include "afxdialogex.h"
#include "opencv2/opencv.hpp"
#include "CvvImage.h"

添加命名空间

using namespace std;  
using namespace cv;

添加全局变量

CvCapture* Capture;
CvCapture* m_Video;
CRect rect;
CDC *pDC;
HDC hDC;
CWnd *pwnd;VideoCapture cap;
Mat frame;   //定义Mat变量,用来存储每一帧

以上做好后,找到之前的dialog,右键“打开摄像头”这个button

选择“添加事件处理程序”

然后按下编辑代码即可

相应的,对另外的button也这么做,一个Button,可以看成一个中断程序,按一下,进去了,代码跑一跑。

而我们要做的就是在这个button中,添加处理函数。

老铁,双击     “打开摄像头”这个button

进入这个button的代码区,添加如下代码:

 /*****************************************       打开摄像头            ********************************************************/
/*********************由于利用mfc显示摄像头需要用到CvvImage,所以我们需要首先将图片转到IplImage格式******************************/
/*****************************  再将图片转到CvvImage格式,然后将其显示到picture控件上   ********************************************************/void Copencv_mfc_2Dlg::OnClickedOpenCamera()
{ // TODO: Add your control notification handler code here  //Mat frame;   //定义Mat变量,用来存储每一帧cap.open(0);   //VideoCapture cap;(已经在前面做了全局变量)    cap.open(“1.avi”);       这是第一种种方法//VideoCapture cap(“1.avi”);                                            这是第二种方法  //读取摄像头  ——  这就是第一个,未能成功运行的代码中的方法//CvCapture *capture = cvCreateCameraCapture(0);cap >> frame;                     //读取当前帧方法一      Function:   读取一帧//cap.read(frame); //读取当前帧方法二CDC* pDC = GetDlgItem(IDC_CAMERA_SHOW)->GetDC();  //获取IDC_CAMERA_SHOW这个控件的设备环境,然后就可以对这个控件进行图形方面的操作了。HDC hDC = pDC->GetSafeHdc();   //获取显示控件的句柄 IplImage img = frame;CvvImage cimg;cimg.CopyOf(&img);            //复制该帧图像CRect rect;   //矩形类,用于记录一个矩形GetDlgItem(IDC_CAMERA_SHOW)->GetClientRect(&rect);   //GetClientRect是得到窗口句柄的用户坐标。 获取控件的坐标范围cimg.DrawToHDC(hDC, &rect);   //显示到设备的矩形框内ReleaseDC(pDC);SetTimer(1, 10, NULL);
}

这里也是需要重点说的,首先我们利用opencv的库函数打开摄像头并且获取到mat格式的图片,但是由于利用mfc显示摄像头需要用到CvvImage,所以我们需要首先将图片转到IplImage格式,再将图片转到CvvImage格式,然后将其显示到picture控件上。在控制台程序中,我们可以很简单的通过for(;;)的空循环来不停的实现获取摄像头的每一帧,但是我发现这么做在MFC里面是不可行的。一个是因为MFC是用户界面程序,如果这么写的话,所有的界面都会卡住,而且这么写的话其他的功能按钮就失去作用了。这里为了实现获取摄像头的每一帧,我们要通过设定一个时间事件,让每隔一定时间,比如20ms,就调用一个函数,通过这个时间调用来获取摄像头的帧。

添加onTimer函数    ,右键dialog

选择类向导


选中后 选择 “添加处理程序”

/**********************************************定时器部分******************************************************/void Copencv_mfc_2Dlg::OnTimer(UINT_PTR nIDEvent)
{// TODO: Add your message handler code here and/or call default  //  Mat frame;//cap.open(0);  cap >> frame;   //读取当前帧CDC* pDC = GetDlgItem(IDC_CAMERA_SHOW)->GetDC();     //根据ID获得窗口指针再获取与该窗口关联的上下文指针HDC hDC = pDC->GetSafeHdc();      // 获取设备上下文句柄 IplImage img = frame;CvvImage cimg;cimg.CopyOf(&img);    //复制该帧图像CRect rect;   // 矩形类GetDlgItem(IDC_CAMERA_SHOW)->GetClientRect(&rect);cimg.DrawToHDC(hDC, &rect);   //显示到设备的矩形框内ReleaseDC(pDC);/********************************图像清晰度的判断**********************************/Mat imageGrey;cvtColor(frame, imageGrey, CV_RGB2GRAY);Mat imageSobel;Sobel(imageGrey, imageSobel, CV_16U, 1, 1);//图像的平均灰度  double meanValue = 0.0;meanValue = mean(imageSobel)[0];//double to string  stringstream meanValueStream;string meanValueString;meanValueStream << meanValue;meanValueStream >> meanValueString;//meanValueString = "Articulation(Sobel Method): " + meanValueString;image_definition = meanValue;UpdateData(FALSE);CDialogEx::OnTimer(nIDEvent);  //这是,把当前函数没有被处理的消息id,用默认的处理函数来处理。类似与 switch里的最后哪个default
}

这段代码是从最初的版本上拷下来的,在定时器函数中同时进行图像的清晰度的判断。

其中会涉及变量的显示-----也就是将程序中的变量在对应的Edit Control中显示出,或者是将Edit Control的变量发送给代码区

右键对应的Edit Control 


选择 “添加变量”


类别改成“Value”,变量类型“it is up to you ”变量名自定义,单击 “完成”。

关闭摄像头,这里采取了比较巧妙的办法,我们只是将时间函数停掉,并且关闭摄像头就ok了

          //***************************************     关闭摄像头        ****************************************************//
void Copencv_mfc_2Dlg::OnClickedCloseCamera()
{// TODO: Add your control notification handler code here  KillTimer(1);            //这里关闭摄像头的方式  ,只是将时间函数停掉cap.release();
}

PART 4  :图像处理的算法

首先是预处理:图像中值滤波,亮度增强,对比度变换,形态学变换,阈值二值化.........


实现3种清晰度评价方法,分别是Tenengrad梯度方法、Laplacian梯度方法和方差方法

Tenengrad梯度方法

Tenengrad梯度方法利用Sobel算子分别计算水平和垂直方向的梯度,同一场景下梯度值越高,图像越清晰。以下是具体实现,这里衡量的指标是经过Sobel算子处理后的图像的平均灰度值,值越大,代表图像越清晰。


  1. #include   
  2. #include   
  3.   
  4. using namespace std;  
  5. using namespace cv;  
  6.   
  7. int main()  
  8. {  
  9.     Mat imageSource = imread("2.jpg");  
  10.     Mat imageGrey;  
  11.   
  12.     cvtColor(imageSource, imageGrey, CV_RGB2GRAY);  
  13.     Mat imageSobel;  
  14.     Sobel(imageGrey, imageSobel, CV_16U, 1, 1);  
  15.   
  16.     //图像的平均灰度  
  17.     double meanValue = 0.0;  
  18.     meanValue = mean(imageSobel)[0];  
  19.   
  20.     //double to string  
  21.     stringstream meanValueStream;  
  22.     string meanValueString;  
  23.     meanValueStream << meanValue;  
  24.     meanValueStream >> meanValueString;  
  25.     meanValueString = "Articulation(Sobel Method): " + meanValueString;  
  26.     putText(imageSource, meanValueString, Point(20, 50), CV_FONT_HERSHEY_COMPLEX, 0.8, Scalar(255, 255, 25), 2);  
  27.     imshow("Articulation", imageSource);  
  28.     waitKey();  
  29. }  

Laplacian梯度方法:

Laplacian梯度是另一种求图像梯度的方法,在上例的OpenCV代码中直接替换Sobel算子即可

  1. #include   
  2. #include   
  3.   
  4. using namespace std;  
  5. using namespace cv;  
  6.   
  7. int main()  
  8. {  
  9.     Mat imageSource = imread("1.jpg");  
  10.     Mat imageGrey;  
  11.   
  12.     cvtColor(imageSource, imageGrey, CV_RGB2GRAY);  
  13.     Mat imageSobel;  
  14.   
  15.     Laplacian(imageGrey, imageSobel, CV_16U);  
  16.     //Sobel(imageGrey, imageSobel, CV_16U, 1, 1);  
  17.   
  18.     //图像的平均灰度  
  19.     double meanValue = 0.0;  
  20.     meanValue = mean(imageSobel)[0];  
  21.   
  22.     //double to string  
  23.     stringstream meanValueStream;  
  24.     string meanValueString;  
  25.     meanValueStream << meanValue;  
  26.     meanValueStream >> meanValueString;  
  27.     meanValueString = "Articulation(Laplacian Method): " + meanValueString;  
  28.     putText(imageSource, meanValueString, Point(20, 50), CV_FONT_HERSHEY_COMPLEX, 0.8, Scalar(255, 255, 25), 2);  
  29.     imshow("Articulation", imageSource);  
  30.     waitKey();  
  31. }  

方差方法:

方差是概率论中用来考察一组离散数据和其期望(即数据的均值)之间的离散(偏离)成都的度量方法。方差较大,表示这一组数据之间的偏差就较大,组内的数据有的较大,有的较小,分布不均衡;方差较小,表示这一组数据之间的偏差较小,组内的数据之间分布平均,大小相近。


对焦清晰的图像相比对焦模糊的图像,它的数据之间的灰度差异应该更大,即它的方差应该较大,可以通过图像灰度数据的方差来衡量图像的清晰度,方差越大,表示清晰度越好

  1. #include   
  2. #include   
  3.   
  4. using namespace std;  
  5. using namespace cv;  
  6.   
  7. int main()  
  8. {  
  9.     Mat imageSource = imread("2.jpg");  
  10.     Mat imageGrey;  
  11.   
  12.     cvtColor(imageSource, imageGrey, CV_RGB2GRAY);  
  13.     Mat meanValueImage;  
  14.     Mat meanStdValueImage;  
  15.   
  16.     //求灰度图像的标准差  
  17.     meanStdDev(imageGrey, meanValueImage, meanStdValueImage);  
  18.     double meanValue = 0.0;  
  19.     meanValue = meanStdValueImage.at<double>(0, 0);  
  20.   
  21.     //double to string  
  22.     stringstream meanValueStream;  
  23.     string meanValueString;  
  24.     meanValueStream << meanValue*meanValue;  
  25.     meanValueStream >> meanValueString;  
  26.     meanValueString = "Articulation(Variance Method): " + meanValueString;  
  27.   
  28.     putText(imageSource, meanValueString, Point(20, 50), CV_FONT_HERSHEY_COMPLEX, 0.8, Scalar(255, 255, 25), 2);  
  29.     imshow("Articulation", imageSource);  
  30.     waitKey();  
  31. }  

自己在用以上三种算法进行清晰度评测时候发现,方差算法应该是最垃圾的,因为他受噪声的干扰大

对于清晰度的判断,算法很多,将所有算法进行试验然后统计结果,我竟然发现不同形状的光斑得用不同的算法。

然后就是重心坐标的计算
#include   
#include   
#include   
#include   using namespace cv;
using namespace std;
Mat src;
int thresh = 30;
Mat src_gray;
Mat src_zzlvbo;
Mat src_lightup;int max_thresh = 255;double alpha; /**< 控制对比度 */
int beta;  /**< 控制亮度 */int main()
{src = imread("E:\\opencv_demo\\light_definition\\s+10.jpg", CV_LOAD_IMAGE_COLOR);    //注意路径得换成自己的  //---将灰度化后的图像进行中值滤波-------------////medianBlur(src, src, 5);//Mat new_image = Mat::zeros(src.size(), src.type());//cout << " Basic Linear Transforms " << endl;//cout << "-------------------------" << endl;//cout << " *Enter the alpha value [1.0-3.0]: ";//cin >> alpha;//cout << " *Enter the beta value [0-100]: ";//cin >> beta;/ 执行变换 new_image(i,j) = alpha    * image(i,j) + beta//for (int y = 0; y < src.rows; y++)//{//	for (int x = 0; x < src.cols; x++)//	{//		for (int c = 0; c < 3; c++)//		{//			new_image.at(y, x)[c] = saturate_cast(alpha * (src.at(y, x)[c]) + beta);//		}//	}//}/ 创建显示窗口//namedWindow("Original Image", 1);//namedWindow("New Image", 1);/ 显示图像//imshow("Original Image", src);//imshow("New Image", new_image);cvtColor(src, src_gray, CV_BGR2GRAY);//灰度化     //GaussianBlur(src, src, Size(3, 3), 0.1, 0, BORDER_DEFAULT);//blur(src_gray, src_gray, Size(3, 3)); //滤波      //将灰度化后的图像进行中值滤波medianBlur(src_gray, src_gray, 9);namedWindow("灰度图", WINDOW_AUTOSIZE);imshow("灰度图", src_gray);//二值化threshold(src_gray, src_gray, 140, 255, CV_THRESH_BINARY);namedWindow("二值化", CV_WINDOW_AUTOSIZE);imshow("二值化", src_gray);//moveWindow("image", 20, 20);   //功能是改变指定窗口的位置和大小//输入图像  //输出图像  //定义操作:MORPH_OPEN为开操作,MORPH_CLOSE为闭操作  //单元大小,这里是3*3的8位单元  //开闭操作位置  //开闭操作次数  //Mat dst;//形态学处理后的图像morphologyEx(src_gray, src_gray, MORPH_OPEN, Mat(3, 3, CV_8U), Point(-1, -1), 1);//形态学处理//namedWindow("形态学", CV_WINDOW_AUTOSIZE);//imshow("形态学", src_gray);
//	moveWindow("形态学", 20, 20);   //功能是改变指定窗口的位置和大小//定义Canny边缘检测图像       Mat canny_output;vector > contours;vector hierarchy;//利用canny算法检测边缘       Canny(src_gray, canny_output, thresh, thresh * 3, 3);namedWindow("canny", CV_WINDOW_AUTOSIZE);imshow("canny", canny_output);moveWindow("canny", 550, 20);   //功能是改变指定窗口的位置和大小//查找轮廓    findContours(canny_output, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0));//计算轮廓矩       vector mu(contours.size());for (int i = 0; i < contours.size(); i++){mu[i] = moments(contours[i], false);}//计算轮廓的质心     vector mc(contours.size());for (int i = 0; i < contours.size(); i++){mc[i] = Point2d(mu[i].m10 / mu[i].m00, mu[i].m01 / mu[i].m00);}//画轮廓及其质心并显示      Mat drawing = Mat::zeros(canny_output.size(), CV_8UC3);for (int i = 0; i < contours.size(); i++){Scalar color = Scalar(255, 0, 0);drawContours(drawing, contours, i, color, 2, 8, hierarchy, 0, Point());circle(drawing, mc[i], 5, Scalar(0, 0, 255), -1, 8, 0);rectangle(drawing, boundingRect(contours.at(i)), cvScalar(0, 255, 0));char tam[100];sprintf(tam, "(%0.0f,%0.0f)", mc[i].x, mc[i].y);putText(drawing, tam, Point(mc[i].x, mc[i].y), FONT_HERSHEY_SIMPLEX, 0.4, cvScalar(255, 0, 255), 1);}namedWindow("Contours", CV_WINDOW_AUTOSIZE);imshow("Contours", drawing);moveWindow("Contours", 1100, 20);waitKey(0);src.release();src_gray.release();return 0;
}

对于光斑的变化,我还想到了用光斑的面积和轮廓长度的变化来做吗,不过效果很差,当然, 附上代码:


这段代码可以进行轮廓长度的计算,但是最后的结果是显示多段轮廓,需要进行轮廓筛选,然后相加这段代码中包含了图像质心的计算,还有图像矩,轮廓绘制#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "cv.h"                             //  OpenCV 文件头
#include "highgui.h"
#include "cvaux.h"
#include "cxcore.h"using namespace std;
using namespace cv;//定义全局变量
Mat srcImage, grayImage;
int thresh = 200;   //边缘检测的阈值,改变此阈值对边缘检测的结果哟普很大的影响,最初设定值为100
const int threshMaxValue = 255;
RNG rng(12345);//声明回调函数
void thresh_callback(int, void*);int main()
{//将彩色图像读入srcImage = imread("E:\\opencv_demo\\light_definition\\1.jpg");//判断文件是否加载成功if (!srcImage.data){cout << "图像加载失败...";return -1;}elsecout << "图像加载成功..." << endl << endl;namedWindow("原图像", WINDOW_AUTOSIZE);imshow("原图像", srcImage);//图像转化为灰度图并平滑cvtColor(srcImage, grayImage, COLOR_BGR2GRAY);// blur(grayImage, grayImage, Size(3, 3));//将灰度化后的图像进行中值滤波medianBlur(grayImage, grayImage, 7);namedWindow("灰度图", WINDOW_AUTOSIZE);imshow("灰度图", grayImage);//二值化threshold(grayImage, grayImage, 120, 255, CV_THRESH_BINARY);//创建轨迹条createTrackbar("Thresh:", "二值图", &thresh, threshMaxValue, thresh_callback);thresh_callback(thresh, 0);waitKey(0);return 0;
}void thresh_callback(int, void*)
{Mat canny_output;vector>contours;vectorhierarchy;//canny边缘检测Canny(grayImage, canny_output, thresh, thresh * 2, 3);//轮廓提取findContours(canny_output, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));//计算图像矩vectormu(contours.size());for (int i = 0; i < contours.size(); i++){mu[i] = moments(contours[i], false);}//计算图像的质心vectormc(contours.size());for (int i = 0; i < contours.size(); i++){mc[i] = Point2f(mu[i].m10 / mu[i].m00, mu[i].m01 / mu[i].m00);}//绘制轮廓Mat drawing = Mat::zeros(canny_output.size(), CV_8UC3);for (int i = 0; i < contours.size(); i++){Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));drawContours(drawing, contours, i, color, 2, 8, hierarchy, 0, Point());circle(drawing, mc[i], 4, color, -1, 8, 0);}namedWindow("轮廓图", WINDOW_AUTOSIZE);imshow("轮廓图", drawing);//用moments矩集计算轮廓面积并与opencv函数计算结果进行比较printf("\t Info: Area and Contour Length \n");for (int i = 0; i < contours.size(); i++){printf("* Contour[%d] - Area(M_00)=%.2f-Area OpenCV:%.2f - Length:%.2f\n", i, mu[i].m00, contourArea(contours[i]), arcLength(contours[i], true));Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));drawContours(drawing, contours, i, color, 2, 8, hierarchy, 0, Point());circle(drawing, mc[i], 4, color, -1, 8, 0);}double lengthSum = 0;double areaSum = 0;char lengthFlag = 0;printf("\t Info: the sum of Area and Length is :\n");for (int i = 0; i < contours.size(); i++){if (arcLength(contours[i], true) > 120){lengthSum = lengthSum + arcLength(contours[i], true);areaSum = areaSum + contourArea(contours[i]);}}printf("Area OpenCV Sum : %.2f - Length Sum : %.2f\n", areaSum, lengthSum);
}

PART 5 : 串口控制下位机

这部分应该是最好做的BUT,我的导师竟然告诉我,做两个exe,一个发指令到内存去,然后另外一个exe去调用内存区的指令,控制运动控制器。

WTC?  我的单片机又要吃灰了?


剩下的就是整合了,等全部弄好了,会把工程发到CSDN上,骗一波积分,嘻嘻!!!!!




本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部