对于我这个常年用C语言的渣渣,突然接触需要C++的MFC和Opencv,就变成毕设困难户了,好在有CSDN上大神们的帮助和同班同学热心的指导,总算是上手了mfc和Opencv。
废话不多说,直接进入正题。
在VS2013中新建好mfc项目,给这个项目取个动人的 名字“HuaQ_mfc”,然后在dialog中找到了对应的
:英文直译,按钮;
:英文直译,静态的字,也就是是说,这玩意加到上面去后不能再程序中就行修改;
:这个组件是用来放摄像头捕获的图像
:可编辑,在这里用来放所需显示的(清晰度 and 重心位置坐标);
还有好多的组件,因为用不到所以没有多做介绍。
对于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(这里并没有用坐标的翻译,因为最初自己做的时候是判断图像半径的大小变化,但是后面又引入了其他类型的图像,用最小二乘元拟合法做失败了,最终使用能够一统江山的算法——重心坐标)
所做的一切,都是在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的代码区,添加如下代码:
/***************************************** 打开摄像头 ********************************************************/
/*********************由于利用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,就调用一个函数,通过这个时间调用来获取摄像头的帧。
选择类向导
选中后 选择 “添加处理程序”
/**********************************************定时器部分******************************************************/
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();
}
首先是预处理:图像中值滤波,亮度增强,对比度变换,形态学变换,阈值二值化.........
实现3种清晰度评价方法,分别是Tenengrad梯度方法、Laplacian梯度方法和方差方法
Tenengrad梯度方法利用Sobel算子分别计算水平和垂直方向的梯度,同一场景下梯度值越高,图像越清晰。以下是具体实现,这里衡量的指标是经过Sobel算子处理后的图像的平均灰度值,值越大,代表图像越清晰。
Laplacian梯度是另一种求图像梯度的方法,在上例的OpenCV代码中直接替换Sobel算子即可
方差是概率论中用来考察一组离散数据和其期望(即数据的均值)之间的离散(偏离)成都的度量方法。方差较大,表示这一组数据之间的偏差就较大,组内的数据有的较大,有的较小,分布不均衡;方差较小,表示这一组数据之间的偏差较小,组内的数据之间分布平均,大小相近。
对焦清晰的图像相比对焦模糊的图像,它的数据之间的灰度差异应该更大,即它的方差应该较大,可以通过图像灰度数据的方差来衡量图像的清晰度,方差越大,表示清晰度越好。
自己在用以上三种算法进行清晰度评测时候发现,方差算法应该是最垃圾的,因为他受噪声的干扰大
对于清晰度的判断,算法很多,将所有算法进行试验然后统计结果,我竟然发现不同形状的光斑得用不同的算法。
#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;
}
else
cout << "图像加载成功..." << 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);
}
这部分应该是最好做的,BUT,我的导师竟然告诉我,做两个exe,一个发指令到内存去,然后另外一个exe去调用内存区的指令,控制运动控制器。
WTC? 我的单片机又要吃灰了?
剩下的就是整合了,等全部弄好了,会把工程发到CSDN上,骗一波积分,嘻嘻!!!!!