Using Converter class to use cv::undistort in Matlab

OpenCV function

void undistort(const Mat& src, Mat& dst, const Mat& cameraMatrix, const Mat& distCoeffs, const Mat& newCameraMatrix=Mat())

Matlab

dst=mexUndistort(src,cameraMatrix,distCoeffs)

MEX function source

#include "mex.h"
#include "matcv.h" 
void mexFunction(int nlhs, mxArray *plhs[],int nrhs, mxArray *prhs[])
{
    if(nrhs!=3)
    {
         mexErrMsgTxt("Need exactly 3 inputs src,  cameraMatrix,  and  distCoeffs.n");
    }
    
    cv::Mat Inputs[3];
    cv::Mat Output;
    
    for(int i=0;i<nrhs;i++)
    {
        Inputs[i]=Converter(prhs[i]);
    }
    
    cv::undistort(Inputs[0],Output,Inputs[1],Inputs[2]);
    
    plhs[0]=Converter(Output);
    
    return;
}


To compile you need opencv and boost. Mex command I used with Matlab R2011a and VC9 goes like this:

mex mexUndistort.cpp matcv.cpp -IC:OpenCV2.3buildinclude -ID:NinadToolsboost_1_47_0 -LC:OpenCV2.3buildx64vc9lib -lopencv_core230 -lopencv_imgproc230   -v COMPFLAGS=”/c /Zp8 /GR /W3 /EHs /D_CRT_SECURE_NO_DEPRECATE /D_SCL_SECURE_NO_DEPRECATE /DMATLAB_MEX_FILE /nologo /MD”

Using OpenCV functions in Matlab

I wrote Converter class few days back to simplify creating MEX files for OpenCV functions. Example of using the class follows in the next post.

Source for matcv.h

#ifndef __MATCV__H__
#define __MATCV__H__

#include "mex.h"
#include "matrix.h"
#include <cstring>
#include "opencvcv.h"
#include "opencvhighgui.h"
#include "boostbimap.hpp"

class Converter{
public:
 Converter(mxArray* src, bool Interleve=true);
 Converter(cv::Mat& src);

 operator cv::Mat();
 operator mxArray*();

private:
 enum{MATLAB_MXARRAY,OPENCV_MAT} _id;
 bool _interleve;

 cv::Mat opencv;
 mxArray* matlab;

 typedef boost::bimap<mxClassID,unsigned char> bmtype;
 bmtype _idmap;
 void _initidmap();

 void _matlab2opencv();
 void _opencv2matlab();
};

#endif //__MATCV__H__

Source for matcv.cpp

#include "matcv.h"

Converter::Converter(mxArray* src,bool Interleve):matlab(src),_id(MATLAB_MXARRAY),_interleve(Interleve){
    _initidmap();
}
Converter::Converter(cv::Mat& src):opencv(src),_id(OPENCV_MAT){
    _initidmap();
}

Converter::operator cv::Mat()
{
    if(_id==OPENCV_MAT)
        return(opencv);
    _matlab2opencv();
    return(opencv);
}

Converter::operator mxArray*()
{
    if(_id==MATLAB_MXARRAY)
        return(matlab);
    _opencv2matlab();
    return(matlab);
}

void Converter::_initidmap()
{
    _idmap.insert(bmtype::value_type(mxINT8_CLASS,CV_8S));
    _idmap.insert(bmtype::value_type(mxUINT8_CLASS,CV_8U));
    _idmap.insert(bmtype::value_type(mxINT16_CLASS,CV_16S));
    _idmap.insert(bmtype::value_type(mxUINT16_CLASS,CV_16U));
    _idmap.insert(bmtype::value_type(mxINT32_CLASS,CV_32S));
    _idmap.insert(bmtype::value_type(mxSINGLE_CLASS,CV_32F));
    _idmap.insert(bmtype::value_type(mxDOUBLE_CLASS,CV_64F));
}

void  Converter::_opencv2matlab()
{
    //Is the data type supported?
    bmtype::right_map::const_iterator itr=_idmap.right.find(opencv.depth());
    
    //if not then
    if(itr==_idmap.right.end())
    {
        mexErrMsgTxt("OpenCV2Matlab:Unsupported data type.");
    }
    
    //Find the matlab data type
    mxClassID type=itr->second;
    
    //We support max 3 dimensions
    mwSignedIndex dims[3];
    dims[0]=opencv.rows;
    dims[1]=opencv.cols;
    dims[2]=opencv.channels();
    
    //if number of channels is 1, its a 2D array
    if(dims[0]>0 && dims[1]>0 && dims[2]==1)
    {
        //Create the array to be returned
        matlab=mxCreateNumericArray(2,dims,type,mxREAL);
        //Create opencv header for the matlab data
        cv::Mat tmp=cv::Mat(dims[1],dims[0],CV_MAKETYPE(opencv.depth(),1),mxGetData(matlab),0);
        //Transpose the opencv data to get row major data for matlab
        tmp=opencv.t();
        
        const mwSize* size=mxGetDimensions(matlab);
        mxAssert((opencv.rows==size[0])&(opencv.cols==size[1]),"OpenCV2Matlab:Conversion mismatch");
    }
    else
    {
        //Create the array to be returned
        matlab=mxCreateNumericArray(3,dims,type,mxREAL);
        
        //Seperate the channels
        std::vector<cv::Mat> chans(dims[2]);
        //cv::split(opencv,&chans[0]);
        cv::split(opencv,chans);
        
        //Create opencv header as a "flat" image for the matlab data
        cv::Mat tmp=cv::Mat(dims[1]*dims[2],dims[0],CV_MAKETYPE(opencv.depth(),1),mxGetData(matlab),0);
        
        for(int i=0;i<dims[2];i++)
        {
            //transpose the opencv channels image to row major matlab data
            cv::Mat tmp2=chans[i].t();
            //Copy the data to the flat matlab data
            tmp2.copyTo(tmp.rowRange(i*dims[1],(i+1)*dims[1]));
        } 
        
        const mwSize* size=mxGetDimensions(matlab);
        mxAssert((opencv.rows==size[0])&(opencv.cols==size[1])&(opencv.channels()==size[2]),"OpenCV2Matlab:Conversion mismatch");
    }   
}

void  Converter::_matlab2opencv()
{
    //find the corresponding corresponding opencv data type
    bmtype::left_map::const_iterator itr=_idmap.left.find(mxGetClassID(matlab));
    
    //No corresponding type?
    if(itr==_idmap.left.end()| mxIsComplex(matlab) | mxIsSparse(matlab))
    {
        std::string msg="Matlab2OpenCV:Unsupported data type:"+(itr==_idmap.left.end()?std::string(mxGetClassName(matlab)):"")
        +(mxIsComplex(matlab)?" Complex":"")+(mxIsSparse(matlab)?" Sparse":".");
        mexErrMsgTxt(msg.c_str());
    }
    
    unsigned char type=itr->second;
    
    //Get number of dimensions
    const mwSize dims=mxGetNumberOfDimensions(matlab);
    
    //Cannot handle more that 3 dimensions
    if(dims>3)
    {
        std::ostringstream o;
        o<<"Matlab2OpenCV:Supports upto 3 dimensions. You supplied "<<dims<<".";
        mexErrMsgTxt(o.str().c_str());
    }
    
    //Get actual dimensions
    const mwSize* size=mxGetDimensions(matlab);
    
    //Check if 2 or 3 dimentions
    if(dims==2)
    {
        //Create header for row major matlab data
        const cv::Mat donotmodify=cv::Mat(size[1],size[0],CV_MAKETYPE(type,1),mxGetData(matlab),0);
        
        //Transpose the data so that it is column major for opencv.
        opencv=donotmodify.t();
        mxAssert((opencv.rows==size[0])&(opencv.cols==size[1]),"Matlab2OpenCV:Conversion mismatch");
    }
    else
    {
        //Create header for the "flat" matlab data
        const cv::Mat donotmodify=cv::Mat(size[1]*size[2],size[0],CV_MAKETYPE(type,1),mxGetData(matlab),0);
        
        //Transpose the flat data
        cv::Mat flat=donotmodify.t();
        
        //Create vector of channels to pass to opencv merge operataion
        std::vector<cv::Mat> chans(size[2]);
        
        for(int i=0;i<size[2];i++)
        {
            chans[i]=flat.colRange(i*size[1],(i+1)*size[1]);
        }
        cv::merge(chans,opencv); 
        mxAssert((opencv.rows==size[0])&(opencv.cols==size[1])&(opencv.channels()==size[2]),"Matlab2OpenCV:Conversion mismatch");
    }
}

Writing 3D range grid data to PLY file

I have been working on fixing 3D scanner capable of scanning entire human body. I needed to export the scanner data to Scanalyze to figure out what was wrong with the scanner. I could not find something easy and quick which I could use to export the range grid data to the PLY file format that Scanalyze understands. Final result of few hours of web search, reverse engineering and trial and error follows:

function cloud2ply(X,Y,Z,outfile)
% Writes range grid data to a PLY file
% X,Y,Z: 2D matrix describing x,y and z depths
%outfile: path/name of the output ply flie

%Ignores all zeros
M=(X(:)==0)&(Y(:)==0)&(Z(:)==0);

fid=fopen(outfile,'w');
fprintf(fid,'plyn');
fprintf(fid,'format ascii 1.0n');
fprintf(fid,'obj_info num_cols %dn',size(X,1));
fprintf(fid,'obj_info num_rows %dn',size(X,2));
fprintf(fid,'element vertex %dn',sum(~M));
fprintf(fid,'property float xn');
fprintf(fid,'property float yn');
fprintf(fid,'property float zn');
fprintf(fid,'element range_grid %dn',numel(X));
fprintf(fid,'property list uchar int vertex_indicesn');
fprintf(fid,'end_headern');

fprintf(fid,'%f %f %fn',[X(~M) Y(~M) Z(~M)]');
fprintf(fid,'%d %dn',[~M (~M).*cumsum(~M)]');

fclose(fid);

Running Japanese application on windows 7

I have English(US) as my locale on my computer. I have this program which fails to run if locale is anything but Japanese. So only solution I thought I had was switching locale of the system. But that is inconvenient. After a little search, I found Applocale utility.

http://www.microsoft.com/download/en/details.aspx?displaylang=en&id=13209

But it wont install on Windows 7 as is. You need to start the installation from elevated command prompt.

http://www.emreakkas.com/windows-tips/how-to-install-applocale-on-windows-7

Runtime enum-to-type mapping

Both Matlab array mxArray and OpenCV’s Mat can hold data any basic type.  I want to write code which essentially can handle any data type. My current solution is to write template based code for data processing.  I call this code with a switch/case statement. I wanted to avoid rewriting this switch/case for each function that I write.  I was hoping that there was a better way of doing this. My friend posted this for me on stackoverflow.

mxClassID category = mxGetClassID(prhs[0]);
switch (category) {
      case mxINT8_CLASS: computecost<signed char>(T,offT,Offset,CostMatrix); break;
      case mxUINT8_CLASS: computecost<unsigned char>(T,offT,Offset,CostMatrix); break;
      case mxINT16_CLASS: computecost<signed short>(T,offT,Offset,CostMatrix); break;
      case mxUINT16_CLASS: computecost<unsigned short>(T,offT,Offset,CostMatrix); break;
      case mxINT32_CLASS: computecost<signed int>(T,offT,Offset,CostMatrix); break;
      case mxSINGLE_CLASS: computecost<float>(T,offT,Offset,CostMatrix); break;
      case mxDOUBLE_CLASS: computecost<double>(T,offT,Offset,CostMatrix); break;
      default:
}

http://stackoverflow.com/q/7356740 

The details I missed on the stackoverflow question were I plan to call the this function template only at once. However, I have multiple functions which will need this switch case structure. This makes the function table based methods useless and I don’t really want to use macros. So, it seems like there is now way of getting rid of the switch/case.

Using cv::Mat at()

How does one access (i,j) th location and k th channels of a 2D cv::Mat. Here k is not constant at runtime. I was thinking cv::Mat at<T>(i,j,k) will do it. But it does not. So how do I do this?


double x=Data.at&lt;double&gt;(i,j*Data.channels()+k);

Fixing the problem

Steps followed
  1. Check what Visual studio Matlab was compiled with. Browsed to C:Program FilesMATLABR2011abinwin64, I see boost dlls with vc90 in their names, so I am guessing Visual studio 2008 AKA VC9 was used. Just to be absolutely sure, I used Dependency Walker and opened libmex.dll in it. It shows VC9 runtimes (msvc*90.dll) in dependency as well.
  2. Get OpenCV compiled with same Visual Studio Version. I already have  this located at C:OpenCV2.3buildx64vc9.
  3. Configured Matlab mex compiler to use VC9. Done.
  4. Compile test.cpp with mex and run it. 
  • Debug mode first: mex -g test.cpp -IC:OpenCV2.3buildinclude  -lopencv_core230d  -LC:OpenCV2.3buildx64vc9lib
  • The compiling/linking is is ok.
  • Tried running the “test”.
  • ??? Unexpected Standard exception from MEX file.
    What() is:c:Usersvpworkocvopencvmodulescoresrcconvert.cpp:265: error: (-215) mv && n > 0
  • Debug mode with SECURE_SCL=1: Leads to crash of Matlab!
  • Release mode:  mex test.cpp -IC:OpenCV2.3buildinclude  -lopencv_core230  -LC:OpenCV2.3buildx64vc9lib
  • Leads to crash of Matlab!
  • Release mode with SECURE_SCL=1: mex test.cpp -IC:OpenCV2.3buildinclude  -lopencv_core230  -LC:OpenCV2.3buildx64vc9lib -v COMPFLAGS=”/c /Zp8 /GR /W3 /EHs /D_CRT_SECURE_NO_DEPRECATE /D_SCL_SECURE_NO_DEPRECATE /DMATLAB_MEX_FILE /nologo /MD”
  • No crash. Now to more thorough testing!

The problem!

I tried compiling following piece of code with “mex”.

Matlab version: R2011a

C++ Compiler: Visual Studio 2010

//test.cpp
#include &quot;mex.h&quot;
#include &lt;vector&gt;
#include &lt;opencvcv.h&gt;
void mexFunction(int nlhs, mxArray *plhs[],int nrhs, mxArray *prhs[])
{
    int size[]={100,100,3};
    const std::vector&lt;cv::Mat&gt; chans(size[2],cv::Mat(size[0],size[1],CV_64F));
    cv::Mat opencv;
    cv::merge(chans,opencv);
    return ;
}

This compiles fine. But crashes Matlab, if you run “test” in Matlab. In debug mode (“mex” with -g), leads to an assertions failure !