0 背景
这里以显示Matlab变量struct
中的图片程序代码为例,示范如何将其转换为C++代码,并在C++代码中显示图片。由于在Matlab的代码转换中是不支持转换输出图片文件的函数(例如imshow
函数),于是只能返回uint8
格式的图片数据((8位无符号整数,即1个字节,以此方式存储的图像称作8位图像,相比较默认matlab数据类型双精度浮点double(64位,8个字节),可以节省很大一部分存储空间),然后自己写图片显示函数。
难点就在于显示转换后的C++代码中返回的uint8
格式的图片。
下图为Matlab变量struct
中的信息(包含多个cell):
每个cell中包含400个112923维度的矩阵(即400张三通道的图片数据【高112(行),宽92(列)】):
下图为uint8
格式的图片数据:
下图为在Matlab中显示的图片数据:
下图为C++改写后显示彩色图片(也就是我们最终实现的结果):
⚠️:这里预先转换的前提是,需要在转换机器上安装Matlab和opencv(用于图片显示),具体的安装方法在以前的博文中有详细讲解。
1 Matlab有用代码提取
下面的代码为Mtalab的原始代码:
function varargout = untitled(varargin)
gui_Singleton = 1;
gui_State = struct('gui_Name', mfilename, ...
'gui_Singleton', gui_Singleton, ...
'gui_OpeningFcn', @untitled_OpeningFcn, ...
'gui_OutputFcn', @untitled_OutputFcn, ...
'gui_LayoutFcn', [] , ...
'gui_Callback', []);
if nargin && ischar(varargin{1})
gui_State.gui_Callback = str2func(varargin{1});
end
if nargout
[varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:});
else
gui_mainfcn(gui_State, varargin{:});
end
function untitled_OpeningFcn(hObject, eventdata, handles, varargin)
set(handles.Dimension,'String',''); % 属性纬度
set(handles.Numbers,'String',''); % 对象个数
set(handles.Clusters,'String',''); % 训练准确率
set(handles.RecNum,'String',''); % 识别对象
handles.output = hObject;
guidata(hObject, handles);
function varargout = untitled_OutputFcn(hObject, eventdata, handles)
varargout{1} = handles.output;
% 载入数据
function Load_Callback(hObject, eventdata, handles)
% 数据载入控制
global Myfig;
global Convfig;
global TrFig;
load('Fig.mat');
load('Oliver.mat');
bar = waitbar(0,'读取数据中...');
for i=1:100
str=['训练中...',num2str(100*i/100),'%']; % 百分比形式显示处理进程,不需要删掉这行代码就行
waitbar(i/100,bar,str) % 更新进度条bar,配合bar使用
end
close(bar);
Myfig = gRGB;
Convfig = gRGB1;
TrFig = gRGB2;
% data1 = Data1;
set(handles.Dimension,'String',num2str(size(FigData,2))); % 属性维度
set(handles.Numbers,'String',num2str(size(FigData,1))); % 对象个数
set(handles.Clusters,'String',[num2str(85.50),'%']);
function Exit_Callback(hObject, eventdata, handles)
delete(handles.figure1);
close all;
function Iteration_Callback(hObject, eventdata, handles)
function Dimension_Callback(hObject, eventdata, handles)
function Dimension_CreateFcn(hObject, eventdata, handles)
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
set(hObject,'BackgroundColor','white');
end
function Numbers_Callback(hObject, eventdata, handles)
function Numbers_CreateFcn(hObject, eventdata, handles)
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
set(hObject,'BackgroundColor','white');
end
function Clusters_Callback(hObject, eventdata, handles)
function Clusters_CreateFcn(hObject, eventdata, handles)
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
set(hObject,'BackgroundColor','white');
end
% 展示图片
function Perform_Callback(hObject, eventdata, handles)
cla reset
global Myfig;
global Convfig;
global TrFig;
Cope = get(handles.RecNum,'String');
if isempty(Cope)
pause(0.25);
h=msgbox('请先选择识别对象(1-400)');
uiwait(h,5);
else
TotG=uint8(Convfig{str2num(Cope)}); % 原始模型
axes(handles.Net);
imshow(TotG)
hold on
axes(handles.ObjFunction);
TotG1=uint8(Myfig{str2num(Cope)}); % 改进模型
imshow(TotG1)
axes(handles.axes4);
TotG2=uint8(TrFig{str2num(Cope)}); % 待检测模型
imshow(TotG2)
hold off
end
function RecNum_Callback(hObject, eventdata, handles)
function RecNum_CreateFcn(hObject, eventdata, handles)
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
set(hObject,'BackgroundColor','white');
end
提取有用的代码并写可以转换的函数后的结果如下:
function [TotG, TotG1, TotG2]=t2(Cope)
oliver = coder.load('Oliver.mat');
Myfig = oliver.gRGB;
Convfig = oliver.gRGB1;
TrFig = oliver.gRGB2;
TotG=uint8(Convfig{Cope});
TotG1=uint8(Myfig{Cope});
TotG2=uint8(TrFig{Cope});
2 转换为C++
使用上一篇博文中提到的转换方法转换后,得到如下代码C++代码(由于我用CLion运行C++程序,而CLion使用CMake进行C++编译运行,所以添加了cmake-build-debug
文件夹):
Matlab转换的成C++代码是无法直接运行,我们需要添加一些额外的Matlab库文件(例如mat.h
、matrix.h
、mex.h
、tmwtypes.h
、rtwtypes.h
),这些文件是从Matlab的库中得到的,在Mac上的地址为/Applications/MATLAB_R2022a.app/extern/include/
):
我们进入example/main.cpp
文件中,这里是Matlab转换时自动写好的示例代码。其中最有用的函数部分的代码如下:
static double argInit_real_T()
{
return 0.0;
}
static void main_dealImage()
{
static unsigned char TotG[30912];
static unsigned char TotG1[30912];
static unsigned char TotG2[30912];
// Initialize function 'dealImage' input arguments.
// Call the entry-point 'dealImage'.
dealImage(argInit_real_T(), TotG, TotG1, TotG2);
}
如果我们使用open cv
中的Mat构造函数接收unchar
参数,那么显示出来的图像将只会是丢掉颜色信息的灰度图像,不会有彩色的图像(CV_8UC3:带有3个通道的8位无符号整数矩阵/图像;CV_8UC1:带有1个通道的8位无符号整数矩阵/图像)。
uchar* data = TotG;
//matlab中原矩阵为:112行,92列
cv::Mat src(92, 112, CV_8UC1, data);//rect_height, rect_width
imshow("image", src);
cv::waitKey(0);
⚠️注意:这里传入Mat中的行、列信息和Matlab原数据相反的原因是因为,在MATLAB的二位数组(矩阵)的数据存放顺序默认为列优先(从第一列自上向下存放和访问,再第二列…),而c/c++数据存放顺序默认为行优先。
我们使用如下代码打印输入的数据:
uchar* data = TotG2;
int row = 112;//行
int col = 92;//列
for (int k = 0; k < 3; ++k) {
for (int i = 0; i < col; ++i) {
for (int j = 0; j < row; ++j) {
std::cout<<(double)data[k*col*row + i*row + j]<<" ";
}std::cout<<std::endl;
}std::cout<<std::endl<<std::endl;
}
数据对比:
matlab(1通道):
C++(打印出来的1通道):
matlab(3通道):
C++(打印出来的3通道):
CV_8UC1
格式的图片显示:CV_8UC3
格式的图片显示:
出现这样问题的原因是因为:
- 在opencv中图像的三通道是BGR(历史原因:早期在相机制造商和软件提供商中很流行),而在Matlab中图像的三通道是RGB,也就是调整接收到的三通道数据的顺序,才能使用
CV_8UC3
格式来显示图片。不然直接使用就会导致图像信息错乱,而使用CV_8UC1
格式,虽然规避了图像信息错位的问题,但是丢失了另外两个通道的颜色信息,矩阵的行列信息也是相反,导致只能显示灰度图像。
我们使用如下方法进行三通道数据的处理(行列转置,三通道顺序调整,思路类似于下图):
cv::Mat array2cv(uchar* array){
//int D = mxGetNumberOfDimensions(pic); // 维度 mxArray* pic
int M = 112;// mxGetM(pic); // 行(高)
int N = 92;//mxGetN(pic) / D; // 列(宽)
uchar* picReal = array;
cv::Mat cvPic(M, N, CV_8UC3);
for (int i = 0; i < M; i++)//行
{
uchar* pData = cvPic.ptr<uchar>(i);
for (int j = 0; j < N; j++)//列
{
pData[3 * j + 0] = picReal[2 * M * N + j * M + i];
pData[3 * j + 1] = picReal[1 * M * N + j * M + i];
pData[3 * j + 2] = picReal[0 * M * N + j * M + i];
}
}
return cvPic;
}
然后使用如下代码进行数据显示:
cv::Mat src(112, 92, CV_8UC3, data);//rect_height, rect_width
cv::imshow("image", array2cv(data));
cv::waitKey(0);
3 使用纯C++实现mat数据中的图片显示
g1.cpp
文件内容如下:
#include<opencv2/opencv.hpp>
#include <opencv2/core/core.hpp> //核心模块
#include <opencv2/highgui/highgui.hpp> //GUI用户界面
#include <opencv2/imgproc/imgproc.hpp> // 图像处理
//using namespace cv;
#include "mat.h"//Matlab中的库
cv::Mat array2cv(mxArray* pic){
int D = mxGetNumberOfDimensions(pic); // 维度 mxArray* pic
int M = mxGetM(pic);//112 // 行(高)
int N = mxGetN(pic) / D;//92 // 列(宽)
uchar* picReal = (uchar*)mxGetPr(pic);;
cv::Mat cvPic(M, N, CV_8UC3);//rect_height, rect_width
for (int i = 0; i < M; i++)
{
uchar* pData = cvPic.ptr<uchar>(i);
for (int j = 0; j < N; j++)
{
pData[3 * j + 0] = picReal[2 * M * N + j * M + i];
pData[3 * j + 1] = picReal[1 * M * N + j * M + i];
pData[3 * j + 2] = picReal[0 * M * N + j * M + i];
}
}
return cvPic;
}
void getMatInfo(const char* path, const char* variableName){
// const char* path = "/Users/mac/CLionProjects/getMatInfo/Fig.mat";
MATFile* pmatFile = matOpen(path, "r");
if (pmatFile == NULL)
{
printf("mat数据读取失败!");
return;
}
int ndir;
const char** dir = (const char**)matGetDir(pmatFile, &ndir);
mxArray* array = matGetVariable(pmatFile, dir[0]);//得到cell
matClose(pmatFile);
if (array == NULL)
{
printf("结构体中cell数据不存在!");
return;
}
mxArray* pic1 = mxGetCell(array, 5);//得到图片
imshow("image", array2cv(pic1));
cv::waitKey(0);
}
int main() {
const char *path = "/Users/mac/CLionProjects/getMatInfo/Oliver.mat";
const char* variableName = "Myfig"; //FigData
getMatInfo(path, variableName);
return 0;
}
编译的CMakeLists.txt文件如下:
cmake_minimum_required(VERSION 3.15)
project(getMatInfo)
set(CMAKE_CXX_STANDARD 11)
include_directories(${PROJECT_SOURCE_DIR})
include_directories(.)
# Matlab的动态链接库
LINK_LIBRARIES("/Applications/MATLAB_R2022a.app/bin/maci64/libeng.dylib"
"/Applications/MATLAB_R2022a.app/bin/maci64/libmx.dylib"
"/Applications/MATLAB_R2022a.app/bin/maci64/libmat.dylib"
"/Applications/MATLAB_R2022a.app/bin/maci64/libmex.dylib")
#找到opencv的包
find_package(OpenCV REQUIRED)
include_directories( ${OpenCV_INCLUDE_DIRS})
# 根据的自己文件路径进行修改
add_executable(getMatInfo
g1.cpp)
#动态链接
target_link_libraries(getMatInfo ${OpenCV_LIBS} )
4 代码封装
这里我们对方法进行封装成动态链接库(供Java或C++调用,主要是供Java调用),让其可以通过传入图片编号和文件存储路径,然后返回保存的图片路径。
头文件dealImage.h
为(让编译器以处理 C 语言代码的方式来处理修饰的 C++ 代码。):
#ifndef DEALIMAGE_DEALMATLABDATA_H
#define DEALIMAGE_DEALMATLABDATA_H
#include "rtwtypes.h"
#include <cstddef>
#include <cstdlib>
extern "C"{
void main_dealImage(int index, const char* imgPath, char** saveImgPath);
};
#endif // DEALIMAGE_DEALMATLABDATA_H
源文件dealImage.cpp
为(为了可以供Java调用,我们使用JNA来进行C++动态链接库的调用,我们返回的数据结构类型均为C的数据结构):
注意⚠️:
这里为了返回一个Java中的String[]
的数据结构,我们C语句中的char**
来存储返回的三张图片的保存路径。这里我们在方法中,申请了堆上的空间来初始化char**
,如果直接使用传入的行参,而不自己申请空间,在Java调用动态链接库的时候,就会爆Java的垃圾回收异常。
#include "dealMatlabData.h"
#include "dealImage.h"
#include<opencv2/opencv.hpp>
#include <opencv2/core/core.hpp> //核心模块
#include <opencv2/highgui/highgui.hpp> //GUI用户界面
#include <opencv2/imgproc/imgproc.hpp> // 图像处理
const int filePathLengthMax = 256;
char convfigStr[filePathLengthMax], myfigStr[filePathLengthMax], trFigStr[filePathLengthMax];
cv::Mat array2cv(uchar*);
cv::Mat array2cv(uchar* array){
//int D = mxGetNumberOfDimensions(pic); // 维度 mxArray* pic
int M = 112;// mxGetM(pic); // 行(高)
int N = 92;//mxGetN(pic) / D; // 列(宽)
uchar* picReal = array;
cv::Mat cvPic(M, N, CV_8UC3);
for (int i = 0; i < M; i++)
{
uchar* pData = cvPic.ptr<uchar>(i);
for (int j = 0; j < N; j++)
{
pData[3 * j + 0] = picReal[2 * M * N + j * M + i];
pData[3 * j + 1] = picReal[1 * M * N + j * M + i];
pData[3 * j + 2] = picReal[0 * M * N + j * M + i];
}
}
return cvPic;
}
//
// Arguments : void
// Return Type : void
//
void main_dealImage(int index, const char* imgPath, char** saveImgPath)
{
static unsigned char TotG[30912];
static unsigned char TotG1[30912];
static unsigned char TotG2[30912];
//TotG=uint8(Convfig{Cope});% 原始模型
//TotG1=uint8(Myfig{Cope}); % 改进模型
//TotG2=uint8(TrFig{Cope}); %待检测模型
// Initialize function 'dealImage' input arguments.
// Call the entry-point 'dealImage'.
dealImage(index, TotG, TotG1, TotG2);
const char* convfigName = "convfig.jpg";
const char* myfigName = "myfigfig.jpg";
const char* trFigName = "trFig.jpg";
sprintf(convfigStr,"%s%s",imgPath, convfigName);
sprintf(myfigStr,"%s%s",imgPath, myfigName);
sprintf(trFigStr,"%s%s",imgPath, trFigName);
std::string convfigPath = convfigStr, myfigPath =myfigStr, trFigPath = trFigStr;
cv::imwrite(convfigPath, array2cv(TotG));
cv::imwrite(myfigPath, array2cv(TotG1));
cv::imwrite(trFigPath, array2cv(TotG2));
//char* saveImgPath[3] = {convfigStr, myfigStr, trFigStr};
// char** saveImgPath = (char **) malloc(sizeof(char**));
// saveImgPath[0] = convfigStr;
// saveImgPath[1] = myfigStr;
// saveImgPath[2] = trFigStr;
// char** saveImgPath = new char*[3];
// char* saveImgPath[3];
//封装成动态链接库时使用
saveImgPath = new char *[3];
for(int i = 0;i < 3;i++){
saveImgPath[i] = new char[filePathLengthMax];
}
saveImgPath[0] = convfigStr;//% 原始模型
saveImgPath[1] = myfigStr;//% 改进模型
saveImgPath[2] = trFigStr;// %待检测模型
}
使用cmake动态链接库的方法如下:
(base) mac@macbook dealImage % rm -rf build
(base) mac@macbook dealImage % mkdir build
(base) mac@macbook dealImage % cd build
(base) mac@macbook build % cmake ..
(base) mac@macbook build % make
Scanning dependencies of target dealImage
[ 33%] Building CXX object CMakeFiles/dealImage.dir/dealImage.cpp.o
[ 66%] Building CXX object CMakeFiles/dealImage.dir/dealMatlabData.cpp.o
[100%] Linking CXX shared library libdealImage.dylib
[100%] Built target dealImage
5 福利
matlab原码、c++转换后代码的资源链接,提取码: riig
其他
由opencv的BGR引起的问题,可以让人联想到一些其他有趣的东西:
想学习C++或者Java调用动态链接库的,可以查看本博客的其他博文。