跨平台桌面软件开发实践——在基于Qt(C++)框架下的硬件上位机开发中调用OpenCV进行图像分析处理
【在macOS上通过homebrew安装OpenCV】
我们已经知道这个东西很牛逼了,那么要怎么安装它呢?
在M1 Mac上通过homebrew来自动化安装OpenCV之后在Qt项目的.pro配置文件中引入安装的OpenCV之后会无法进行编译。原因是homebrew上OpenCV的安装脚本是arm架构的OpenCV,我们知道M1芯片是arm架构这本身没什么问题,但是我们前面有提到,Qt在M1 Mac上只有x86版本,是通过Rosetta 2转译运行的,当然无法过编译。关于一个软件是否能在arm架构的Mac上运行可以上doesitarm.com查询。
解决办法是编译安装OpenCV,先安装cmake
brew install cmake
然后从Github上把OpenCV的源代码clone到电脑上。
git clone https://github.com/opencv/opencv.git
进入源码目录,新建一个文件夹build
mkdir build
进入并构建项目
cd build
x86_64 \-DCMAKE_OSX_ARCHITECTURES=x86_64 \-DCMAKE_BUILD_TYPE=RELEASE\-DCMAKE_INSTALL_PREFIX=/usr/local .. =
编译(M1是八核心处理器,j之后跟的是线程数,越大编译越快)
make -j8
编译完成之后,进行安装
sudo make install
INCLUDEPATH += /usr/local/include/opencv4
/usr/local/include/opencv4/opencv2
LIBS += /usr/local/lib/libopencv_*
这样我们就可以调用OpenCV图形库了
示例:
using namespace cv
//在主要生命周期函数中调用
void main(){
Mat img = imread("/Users/wuruihao/Desktop/学习资料.jpg");
imshow("91吴先生",img);
}
二、图像中的像素三通道值近似
我们前面提到:每个像素点只能显示有限个数种类的颜色,我们将这有限的颜色的RGB值保存在Vec3b三通道向量数组里。然后遍历我们要上传的图片,将遍历的每一个像素的RGB值用Vec3b表示,将其与之前的颜色数组里每一个元素用norm取向量范数,与哪一个元素的范数最小,我们就将该颜色近似于该颜色的色值。
代码实现如下(近似之后的图像预览):
void MainWindow::preview_img(const cv::Mat& mat)
{
Mat imgzero(cv::Mat::zeros(56, 98, CV_8UC3));
cv::resize(mat,mat,Size(98,56));
for(int unit =0; unit< wall.size(); unit++)
{
for(int element = 0; element < wall[unit].size(); element++)
{
for(int colornum=0;colornum<sizeof(color)/sizeof(color[0]);colornum++){
imgzero.at<Vec3b>(wall[unit][element])=colorreload(mat.at<Vec3b>(wall[unit][element]));
}
}
}
namedWindow("imgpreview",0);
imshow("imgpreview",imgzero);
}
Vec3b MainWindow::colorreload(Vec3b &a){
double similarity[22];
int min=0;
for(int i=0;i<sizeof(color)/sizeof(color[0]);i++){
similarity[i]=norm(color[i], a);
if (similarity[i]<similarity[min]){
min=i;
}
}
a=color[min];
return a;
}
三、图像单元像素级细分
这个程序的本质是将要上传的图片进行编码按照图像单元一条一条发给下位机,图像的大小、单元和像素的位置是根据项目工程图来定的,定下来很容易,这个工程问题最后进代码落地就没有那么容易了。我们前面讲的算法里一个是遍历图像中所有的像素点,这个遍历有两种方法,一种是按照图像的rows、cols(排与列),一种是通过单元——元素的动态二维数组。后面我们发送串口报文时就必须通过遍历单元元素这种二维数组来按照规定拼接串口报文。所以我们必须将工程图中一百多个单元每个单元及其包含的元素坐标全部进入对象。
这是一个浩大的工程,老吴预计怎么也得花一天时间,于是把小秦抓来一起数格子。然后小秦同志“老吴我又想出了一个好idea”,于是趴在我旁边写了一个相同形状单元的拖动函数,于是我俩搞了三个小时就搞完了。
代码实现如下:
Point WALL::trans(Point input, char mark, int distance)
{
if(mark == 'x'|| mark == 'X')
{
return input+Point(distance,0);
}
else if (mark == 'y'|| mark == 'Y')
{
return input+Point(0,distance);
}
}
vector<Point2d> WALL::shift(vector<Point2d> input, char mark, int distance)
{
for(int i =0; i < input.size(); i++)
{
input[i] = trans(input[i],mark,distance);
}
return input;
}
四、串口报文发送。
这个本质上就是文本拼接,前面有提到那个单元和元素的二维数组,遍历单元内所有元素的颜色代码,与起始位、间隔符、停止位一起拼接,然后换行换下一单元。
for(int picdiv=0;picdiv<uploadnum;picdiv++){
int num=picdiv+1;
QString picnum="PIC"+QString::number(num);;
for(int unit =0; unit< wall.size(); unit++)
{
QString unitnum=picnum+"UNIT"+QString::number(unit);
unitnum+='B';
for(int element = 0; element < wall[unit].size(); element++)
{
unitnum+=senddata[picdiv][unit][element];
unitnum+='/';
}
unitnum+='E';
unitnum+='\n';
mainport.write(unitnum.toLatin1());
ui->sendTextEdit->insertPlainText(unitnum);
}
}
之前在公司的时候,帮老板写一个试卷系统,老板是做培训机构的,想搞个在线考试。我就把试题全部按条进数据库,然后组卷子把这些条数据按照一定条件select出来,然后两层套娃组JSON进试卷数据库。你没听错,组成的JSON试卷直接就是一个参数值,两层JSON一层是题型,比如选择填空,一层是题号和问题答案之类的。然后试卷的前端显示实现就是上面这种方式,从后端接口拿一大段JSON,一层一层遍历渲染加控件。
五、项目运行(Windows下)
这个也叫做项目打包,就是我们写的软件最后应该是一个.exe文件。
是这样,我们在Windows上用Qt Creater打开项目,以release方式编译运行一次项目,然后关闭项目。找到项目以release方式build的目录,找到那个.exe文件,单独(没错只有这个exe文件)把它拖到你想拖到的那个位置,比如“D:\学习资料\”。然后打开你的版本的Qt(命令行),输入
windeployqt D:\学习资料\guluguluscreen.exe
然后该工具会自动补全项目所需要的其他库。
这里特别注意,如果开发过程中引入了第三方库,比如把第三方库的dll文件全部复制粘贴进刚才的exe所在的目录下,这样你的exe才能够运行。
最后通过安装包制作工具将这个目录下所有的内容制作成安装程序向导即可。