结构光成像是一种主动光学三维测量技术,通过投射已知图案的光束到物体表面,利用摄像头捕捉变形后的图案,通过几何关系计算物体表面的三维信息。其核心是通过光条纹的变形反推物体表面形状。
投影系统通常使用DLP或激光干涉生成条纹,常见图案包括正弦条纹、二进制编码条纹或随机散斑。相机系统需与投影仪进行严格标定,建立两者间的几何对应关系。
相位测量是核心算法之一,通过多幅相移图案计算绝对相位: ϕ(x,y)=arctan(∑n=1NInsin(2πn/N)∑n=1NIncos(2πn/N)) \phi(x,y) = \arctan\left(\frac{\sum_{n=1}^{N} I_n \sin(2\pi n/N)}{\sum_{n=1}^{N} I_n \cos(2\pi n/N)}\right) ϕ(x,y)=arctan(∑n=1NIncos(2πn/N)∑n=1NInsin(2πn/N))
基于MFC实现条纹图像处理和形状重建
下面我将创建一个完整的MFC对话框应用程序,实现加载两张条纹图像、相位计算和形状重建功能。
1. 创建MFC对话框项目
- 在Visual Studio中创建"MFC应用程序"项目
- 选择"基于对话框"的应用类型
- 命名为"FringeProcessingDemo"
2. 设计对话框界面
在资源视图中编辑主对话框(IDD_FRINGEPROCESSINGDEMO_DIALOG):
-
添加按钮:
- IDC_BTN_LOAD_IMAGES: “加载条纹图像”
- IDC_BTN_PROCESS: “处理图像”
- IDC_BTN_SAVE: “保存结果”
-
添加静态文本和编辑框:
- IDC_STATIC_IMAGE1: “图像1路径”
- IDC_EDIT_PATH1: 用于显示第一张图像路径
- IDC_STATIC_IMAGE2: “图像2路径”
- IDC_EDIT_PATH2: 用于显示第二张图像路径
-
添加图片控件(Picture Control):
- IDC_STATIC_PHASE: 显示相位图
- IDC_STATIC_SHAPE: 显示形状图
3. 添加事件处理程序
为按钮添加事件处理函数:
// FringeProcessingDemoDlg.h
class CFringeProcessingDemoDlg : public CDialogEx
{
// ...
private:
CString m_strImagePath1;
CString m_strImagePath2;
Mat m_phaseImage;
Mat m_shapeImage;
afx_msg void OnBnClickedBtnLoadImages();
afx_msg void OnBnClickedBtnProcess();
afx_msg void OnBnClickedBtnSave();
// ...
};
4. 实现图像加载功能
// FringeProcessingDemoDlg.cpp
void CFringeProcessingDemoDlg::OnBnClickedBtnLoadImages()
{
// 设置文件过滤器
CString strFilter = _T("图像文件 (*.bmp;*.jpg;*.png)|*.bmp;*.jpg;*.png|所有文件 (*.*)|*.*||");
// 创建文件对话框
CFileDialog dlg1(TRUE, NULL, NULL, OFN_FILEMUSTEXIST, strFilter, this);
CFileDialog dlg2(TRUE, NULL, NULL, OFN_FILEMUSTEXIST, strFilter, this);
// 打开第一个图像
if(dlg1.DoModal() == IDOK)
{
m_strImagePath1 = dlg1.GetPathName();
SetDlgItemText(IDC_EDIT_PATH1, m_strImagePath1);
// 显示图像1
std::string strpath = CT2A(m_strImagePath1.GetString());
Mat img1 = imread(strpath, IMREAD_GRAYSCALE);
if(!img1.empty())
{
CImage cimg1;
cimg1.Create(img1.cols, img1.rows, 24);
BitmapToCImage(img1, cimg1);
CWnd* pWnd1 = GetDlgItem(IDC_STATIC_PHASE);
CDC* pDC1 = pWnd1->GetDC();
CRect rect1;
pWnd1->GetClientRect(&rect1);
cimg1.StretchBlt(pDC1->m_hDC, rect1, SRCCOPY);
ReleaseDC(pDC1);
}
}
// 打开第二个图像
if(dlg2.DoModal() == IDOK)
{
m_strImagePath2 = dlg2.GetPathName();
SetDlgItemText(IDC_EDIT_PATH2, m_strImagePath2);
// 显示图像2
std::string strpath2 = CT2A(m_strImagePath2.GetString());
Mat img2 = imread(strpath2, IMREAD_GRAYSCALE);
if(!img2.empty())
{
CImage cimg2;
cimg2.Create(img2.cols, img2.rows, 24);
BitmapToCImage(img2, cimg2);
CWnd* pWnd2 = GetDlgItem(IDC_STATIC_SHAPE);
CDC* pDC2 = pWnd2->GetDC();
CRect rect2;
pWnd2->GetClientRect(&rect2);
cimg2.StretchBlt(pDC2->m_hDC, rect2, SRCCOPY);
ReleaseDC(pDC2);
}
}
}
// 辅助函数:Mat转CImage
void CFringeProcessingDemoDlg::BitmapToCImage(const Mat& mat, CImage& cimg)
{
if(mat.type() == CV_8UC1)
{
// 灰度图
for(int y = 0; y < mat.rows; y++)
{
for(int x = 0; x < mat.cols; x++)
{
BYTE val = mat.at<BYTE>(y, x);
cimg.SetPixel(x, y, RGB(val, val, val));
}
}
}
else if(mat.type() == CV_8UC3)
{
// 彩色图
for(int y = 0; y < mat.rows; y++)
{
for(int x = 0; x < mat.cols; x++)
{
Vec3b pixel = mat.at<Vec3b>(y, x);
cimg.SetPixel(x, y, RGB(pixel[2], pixel[1], pixel[0]));
}
}
}
}
5. 实现图像处理和形状重建
void CFringeProcessingDemoDlg::OnBnClickedBtnProcess()
{
// 加载两张条纹图像
std::string strpath1 = CT2A(m_strImagePath1.GetString());
Mat img1 = imread(strpath1, IMREAD_GRAYSCALE);
std::string strpath2 = CT2A(m_strImagePath2.GetString());
Mat img2 = imread(strpath2, IMREAD_GRAYSCALE);
if(img1.empty() || img2.empty())
{
AfxMessageBox(_T("请先加载两张条纹图像!"));
return;
}
// 转换为浮点型
Mat fimg1, fimg2;
img1.convertTo(fimg1, CV_32F);
img2.convertTo(fimg2, CV_32F);
// 1. 计算相位差
Mat phaseDiff;
phase(fimg1 - fimg2, fimg1 + fimg2, phaseDiff, false);
// 2. 相位解包裹
Mat unwrappedPhase = unwrapPhase(phaseDiff);
// 3. 转换为形状信息
Mat shape = phaseToShape(unwrappedPhase);
// 保存结果
m_phaseImage = phaseDiff;
m_shapeImage = shape;
// 显示结果
ShowResultImage(m_phaseImage, IDC_STATIC_PHASE);
ShowResultImage(m_shapeImage, IDC_STATIC_SHAPE);
AfxMessageBox(_T("图像处理完成!"));
}
// 相位解包裹
Mat CFringeProcessingDemoDlg::unwrapPhase(const Mat& wrappedPhase)
{
Mat unwrapped = Mat::zeros(wrappedPhase.size(), CV_32F);
float* prevRow = nullptr;
for(int y = 0; y < wrappedPhase.rows; y++)
{
const float* wpRow = wrappedPhase.ptr<float>(y);
float* upRow = unwrapped.ptr<float>(y);
for(int x = 0; x < wrappedPhase.cols; x++)
{
if(x == 0 && y == 0)
{
upRow[x] = wpRow[x];
}
else if(x == 0)
{
float prev = prevRow[x];
float current = wpRow[x];
float diff = current - prev;
// 处理相位跳变
if(diff > CV_PI) diff -= 2*CV_PI;
else if(diff < -CV_PI) diff += 2*CV_PI;
upRow[x] = prev + diff;
}
else
{
float prev = upRow[x-1];
float current = wpRow[x];
float diff = current - prev;
// 处理相位跳变
if(diff > CV_PI) diff -= 2*CV_PI;
else if(diff < -CV_PI) diff += 2*CV_PI;
upRow[x] = prev + diff;
}
}
prevRow = upRow;
}
return unwrapped;
}
// 相位到形状转换
Mat CFringeProcessingDemoDlg::phaseToShape(const Mat& unwrappedPhase)
{
// 这里简化处理,实际应用中需要根据系统标定参数转换
Mat shape;
normalize(unwrappedPhase, shape, 0, 255, NORM_MINMAX, CV_8U);
// 伪彩色显示
Mat colorShape;
applyColorMap(shape, colorShape, COLORMAP_JET);
return colorShape;
}
// 显示结果图像
void CFringeProcessingDemoDlg::ShowResultImage(const Mat& img, int nID)
{
if(img.empty()) return;
CImage cimg;
if(img.channels() == 1)
{
cimg.Create(img.cols, img.rows, 24);
BitmapToCImage(img, cimg);
}
else
{
cimg.Create(img.cols, img.rows, 24);
for(int y = 0; y < img.rows; y++)
{
for(int x = 0; x < img.cols; x++)
{
Vec3b pixel = img.at<Vec3b>(y, x);
cimg.SetPixel(x, y, RGB(pixel[2], pixel[1], pixel[0]));
}
}
}
CWnd* pWnd = GetDlgItem(nID);
CDC* pDC = pWnd->GetDC();
CRect rect;
pWnd->GetClientRect(&rect);
cimg.StretchBlt(pDC->m_hDC, rect, SRCCOPY);
ReleaseDC(pDC);
}
6. 实现结果保存功能
void CFringeProcessingDemoDlg::OnBnClickedBtnSave()
{
if(m_phaseImage.empty() || m_shapeImage.empty())
{
AfxMessageBox(_T("没有可保存的结果!"));
return;
}
// 设置保存文件对话框
CString strFilter = _T("PNG图像 (*.png)|*.png|JPEG图像 (*.jpg)|*.jpg|BMP图像 (*.bmp)|*.bmp||");
CFileDialog dlg(FALSE, _T("png"), _T("phase_result"),
OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, strFilter, this);
if(dlg.DoModal() == IDOK)
{
CString path = dlg.GetPathName();
CString ext = dlg.GetFileExt();
// 保存相位图
CString phasePath = path.Left(path.GetLength() - ext.GetLength() - 1) + "_phase." + ext;
std::string strphasepath = CT2A(phasePath.GetString());
imwrite(strphasepath, m_phaseImage);
// 保存形状图
CString shapePath = path.Left(path.GetLength() - ext.GetLength() - 1) + "_shape." + ext;
std::string strshapepath = CT2A(shapePath.GetString());
imwrite(strshapepath, m_shapeImage);
AfxMessageBox(_T("结果保存成功!"));
}
}
7. 完整对话框类实现
// FringeProcessingDemoDlg.h
#pragma once
#include <opencv2/opencv.hpp>
#include "afxdialogex.h"
using namespace cv;
class CFringeProcessingDemoDlg : public CDialogEx
{
public:
CFringeProcessingDemoDlg(CWnd* pParent = nullptr);
#ifdef AFX_DESIGN_TIME
enum { IDD = IDD_FRINGEPROCESSINGDEMO_DIALOG };
#endif
protected:
virtual void DoDataExchange(CDataExchange* pDX);
protected:
HICON m_hIcon;
virtual BOOL OnInitDialog();
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
DECLARE_MESSAGE_MAP()
public:
CString m_strImagePath1;
CString m_strImagePath2;
Mat m_phaseImage;
Mat m_shapeImage;
void BitmapToCImage(const Mat& mat, CImage& cimg);
Mat unwrapPhase(const Mat& wrappedPhase);
Mat phaseToShape(const Mat& unwrappedPhase);
void ShowResultImage(const Mat& img, int nID);
afx_msg void OnBnClickedBtnLoadImages();
afx_msg void OnBnClickedBtnProcess();
afx_msg void OnBnClickedBtnSave();
};
8. 使用说明
- 点击"加载条纹图像"按钮选择两张条纹图像
- 点击"处理图像"按钮进行相位计算和形状重建
- 处理完成后,结果会显示在对应的图片控件中
- 点击"保存结果"可以保存处理后的图像
9. 实际应用注意事项
- 标定参数:实际应用中需要根据系统标定参数将相位转换为真实的三维坐标
- 多步相移:完整的条纹分析通常需要4步或更多相移图像
- 性能优化:对于大图像或实时处理,可以考虑使用GPU加速
- 错误处理:添加更多的错误检查和异常处理
- 用户界面:可以添加进度条显示处理进度
- 图像显示:可以直接用BITMAPINFO类进行图像显示,不用CImage类,显示速度会提升很多
这个MFC应用程序实现了条纹图像加载、相位计算和形状重建的基本功能,可以作为更复杂条纹投影系统的基础框架。