2.5结构光成像基础实现(相位计算,形状重建)

结构光成像是一种主动光学三维测量技术,通过投射已知图案的光束到物体表面,利用摄像头捕捉变形后的图案,通过几何关系计算物体表面的三维信息。其核心是通过光条纹的变形反推物体表面形状。
投影系统通常使用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对话框项目

  1. 在Visual Studio中创建"MFC应用程序"项目
  2. 选择"基于对话框"的应用类型
  3. 命名为"FringeProcessingDemo"

2. 设计对话框界面

在资源视图中编辑主对话框(IDD_FRINGEPROCESSINGDEMO_DIALOG):

  1. 添加按钮:

    • IDC_BTN_LOAD_IMAGES: “加载条纹图像”
    • IDC_BTN_PROCESS: “处理图像”
    • IDC_BTN_SAVE: “保存结果”
  2. 添加静态文本和编辑框:

    • IDC_STATIC_IMAGE1: “图像1路径”
    • IDC_EDIT_PATH1: 用于显示第一张图像路径
    • IDC_STATIC_IMAGE2: “图像2路径”
    • IDC_EDIT_PATH2: 用于显示第二张图像路径
  3. 添加图片控件(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. 使用说明

  1. 点击"加载条纹图像"按钮选择两张条纹图像
  2. 点击"处理图像"按钮进行相位计算和形状重建
  3. 处理完成后,结果会显示在对应的图片控件中
  4. 点击"保存结果"可以保存处理后的图像

9. 实际应用注意事项

  1. 标定参数:实际应用中需要根据系统标定参数将相位转换为真实的三维坐标
  2. 多步相移:完整的条纹分析通常需要4步或更多相移图像
  3. 性能优化:对于大图像或实时处理,可以考虑使用GPU加速
  4. 错误处理:添加更多的错误检查和异常处理
  5. 用户界面:可以添加进度条显示处理进度
  6. 图像显示:可以直接用BITMAPINFO类进行图像显示,不用CImage类,显示速度会提升很多

这个MFC应用程序实现了条纹图像加载、相位计算和形状重建的基本功能,可以作为更复杂条纹投影系统的基础框架。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值