您现在的位置是:网站首页> C/C++
Qt OpenCV开发笔记
- C/C++
- 2022-04-24
- 1412人已阅读
Qt OpenCV开发笔记
C++ / Opencv 简单实现美颜效果(瘦脸、大眼、磨皮等)
Qt界面显示OpenCV读取的图片
前言:
1,在Qt编写的界面中显示Opencv读取的图片;
由于Qt有自己的读取文件的工具格式和图片显示格式:QImage,SetPixmap,SetPixel,setImage,…………
Qt中文件的读取是基于文件流形式或者数组形式等等,
2,OpenCV也有自己的读取图片的格式和显示图片的格式:imread(),imshow()…………
OpenCV图像文件是Mat格式;
3,如何使用Qt中的控件上能显示OpenCV读取的数据文件呢?
直接把Mat格式数据显示肯定是不能成功的,因此需要把Mat格式转换成Qt下的QImage格式;
搭建开发环境:
在项目的.pro里面的最下方添加如下代码,把编译好的OpenCV库添加进去;
NCLUDEPATH += D:\OpenCV_Lib\OpenCV_MinGW_Build_3.4.6_Lib\include
D:\OpenCV_Lib\OpenCV_MinGW_Build_3.4.6_Lib\include\opencv
D:\OpenCV_Lib\OpenCV_MinGW_Build_3.4.6_Lib\include\opencv2
LIBS += -L D:\OpenCV_Lib\OpenCV_MinGW_Build_3.4.6_Lib\x86\mingw\lib\libopencv_*.a
图像格式转化:
因为Qt和OpenCV分别实现了自己的图像数据结构格式(QImage, Mat),因此在使用Qt显示OpenCV打开的图像数据时,需要对图像格式进行转换:
实现步骤:
1,用OpenCV的imread()函数读取图片数据;
2,将读取出来的图像数据BGR格式或者BGRA格式转换为RGB格式,用cvtColor()函数转换;
3,将转换过的RGB格式图像数据转换成Qt中的QImage格式;
4,通过QLabel控件显示出来;
转换方法:
下面以Qt开发中的Qt界面显示作为主要的界面工具,这里主要实现cv::Mat图像数据转换到QImage;
1,首先新建一个类:命名为:QCVMatDataCorvent,并返回QImage图像数据格式:
static QImage QCVMat2QImage(const cv::Mat& mat);
1
2,cv::Mat作为OpenCV的基础数据格式,提供了大量的矩阵类型(cv::Mat::type),这里主要针对BGRA,BGR和灰度图进行转换;
//值得注意的是,在转化8位3通道的图像时,OpenCV和QImage使用的红蓝通道是相反的,需要使用rgbSwapped方法互换一下
//---------------------------------------------------------------------------------
QImage QCVMatDataCorvent::QCVMat2QImage(const cv::Mat& mat)
{
const unsigned char* data = mat.data;
int width = mat.cols;
int height = mat.rows;
int bytesPerLine = static_cast<int>(mat.step);
switch(mat.type())
{
//8 bit , ARGB
case CV_8UC4:
{
QImage image(data, width, height, bytesPerLine, QImage::Format_ARGB32);
return image;
}
//8 bit BGR
case CV_8UC3:
{
QImage image(data, width, height, bytesPerLine, QImage::Format_RGB888);
//swap blue and red channel
return image.rgbSwapped();
}
//8 bit Gray shale
case CV_8UC1:
{
QImage image(data, width, height, bytesPerLine, QImage::Format_Grayscale8);
return image;
}
//
default:
{
//Unsupported format
qWarning()<<"Unsupported cv::Mat type:"<<mat.type()
<<", Empty QImage will be returned!";
return QImage();
}
}
}
图片显示:
完成数据的转换后,开始对布局进行设计;
1,使用PushButton按钮,实现打开文件操作,借助Qt中的QFileDialog控件对文件进行过滤,并获取到当前选择的图片路径;
2,使用OpenCV的imread()函数读取图片数据为cv::Mat
3,使用QImage QCVMatDataCorvent::QCVMat2QImage(const cv::Mat& mat)转换Mat格式为QImage格式;
4,将QImage显示到QLabel控件上;
void MainWindow::on_OpenImgBtn_clicked()
{
QString fileName;
fileName = QFileDialog::getOpenFileName(this, "Open Image", ".", "Image Files(*.png *.jpg *.jpeg)");
if(!fileName.isEmpty())
{
showImage(fileName);
}
}
void MainWindow::showImage(QString imgPath)
{
ui->ImgLabel->setText("");
cv::Mat imgMat = cv::imread(imgPath.toStdString().c_str());
QImage img = QCVMatDataCorvent::QCVMat2QImage(imgMat);
ui->ImgLabel->setPixmap(QPixmap::fromImage(img));
resize(width()-ui->ImgLabel->width()+img.width(),
height()-ui->ImgLabel->height()+img.height());
}
在QML中显示OpenCV图片
正文
首先要有一个显示图片的控件。根据 用 C++ 编写 QML 扩展 可知,这可以是一个继承QQuickPaintedItem 的自定义类,在自定义类中实现绘制图片然后将它注册到QML中。这个类尽量只显示图片和做一些交互操作,既然如此,那么需要再定义一个工具类,OpenCV图片处理的操作都在工具类中进行。
显示图片的控件如下:
#ifndef SHOWMATITEM_H
#define SHOWMATITEM_H
#include <QQuickPaintedItem>
#include <QPixmap>
#include <opencv2/core/core.hpp>
class ShowMatItem : public QQuickPaintedItem
{
Q_OBJECT
QML_ELEMENT
public:
ShowMatItem(QQuickItem *parent = 0);
void paint(QPainter *painter)override;
Q_INVOKABLE void setSrcFile(const QString & filePath);
private:
cv::Mat mat;
QString srcFilePath;
};
#endif // SHOWMATITEM_H
#include "showmatitem.h"
#include <QPainter>
#include <QPixmap>
#include <QDebug>
#include "util.h"
ShowMatItem::ShowMatItem(QQuickItem *parent)
:QQuickPaintedItem(parent)
{
}
void ShowMatItem::paint(QPainter *painter)
{
painter->setRenderHints(QPainter::Antialiasing, true);
auto rect = boundingRect().toRect();
painter->drawPixmap(rect,util::matToPixmap(rect.size(),mat));
}
void ShowMatItem::setSrcFile(const QString & filePath)
{
QString temp = filePath;
srcFilePath = temp.remove("file:///").replace("/","\\");
util::readImage(temp,mat);
update();
}
工具类如下:
#ifndef UTIL_H
#define UTIL_H
#include <QPixmap>
#include <opencv2/core/core.hpp>
class util
{
public:
static QPixmap matToPixmap(const QSize & size,const cv::Mat & mat);//Mat转QPixmap
static void readImage(const QString & filePath,cv::Mat & mat);//打开一张图片
private:
util();
};
#endif // UTIL_H
#include "util.h"
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <QTextCodec>
util::util()
{
}
QPixmap util::matToPixmap(const QSize & size, const cv::Mat & mat)
{
if(mat.empty())
return QPixmap();
auto img = QImage((const unsigned char*)(mat.data),
mat.cols,
mat.rows,
mat.cols * mat.channels(),
QImage::Format_RGB888);
return QPixmap::fromImage(img.scaled(size));
}
void util::readImage(const QString & filePath,cv::Mat & mat)
{
mat = cv::imread(QTextCodec::codecForName("gb18030")->fromUnicode(filePath).data());
cv::cvtColor(mat,mat,cv::COLOR_BGR2RGB);
}
定义完成,然后是注册,注册以后定义的控件就能在QML中使用了:
qmlRegisterType<ShowMatItem>("com.qmlcompany.cppComponents", 1, 0, "ShowMatItem");
main.qml 代码:
import QtQuick 2.14
import QtQuick.Controls
import com.qmlcompany.cppComponents 1.0
import Qt.labs.platform 1.1 as Labs //名称冲突了要用as,注意Labs要大写开头
ApplicationWindow
{
visible: true
title: qsTr("OpenCV图像处理助手")
color:"#FFFFFF"
width: 1000; height: 600
menuBar: MenuBar
{
Menu
{
title: qsTr("文件")
MenuItem
{
text: qsTr("打开文件")
onTriggered:
{
fileDialog.open()
}
Labs.FileDialog
{
id: fileDialog
title: qsTr("打开图片或者txt文件")
nameFilters: ["image files (*.png *.jpg *.jpeg *.bmp)"]
acceptLabel: qsTr("确定")
rejectLabel: qsTr("取消")
fileMode: Labs.FileDialog.OpenFile
onAccepted:
{
var filePath = fileDialog.files[0]
srcImage.setSrcFile(filePath)
}
}
}
MenuSeparator {}
MenuItem
{
text: qsTr("退出")
onTriggered: Qt.quit()
}
}
}
ShowMatItem
{
id:srcImage
anchors.fill: parent
}
}
OPENCV CV_64FC1含义
CV_64FC1 64F代表每一个像素点元素占64位浮点数,通道数为1
CV_64FC3 64F代表每一个像素点元素占64点×3个浮点数,通道数为4
同理
CV_32FC1
CV_32FC3
用cv::Scalar来设置opencv中图片的颜色
1 怎样使用cv::Scalar来设置opencv中的颜色
cv::Scalar的构造函数是cv::Scalar(v1, v2, v3, v4),前面的三个参数是依次设置BGR的,和RGB相反,第四个参数设置图片的透明度。
2 使用cv::Scalar的规则
当使用opencv提供的库函数imread()、imwrite()和imshow()时,cv::Scalar(v1, v2, v3, v4)的这四个参数就依次是BGRA,即蓝、绿、红和透明度。
Mat m3=Mat(Size(10,10,)CV_8UC3);
m3=Scalar(127,127,127);//赋值
//拷贝构造函数3--从指定行列构造
//从img3中拷贝0-239行以及0-319行到img6
cv::Range rows(0, 240);
cv::Range cols(0, 320);
cv::Mat img6(img3, rows, cols);
颜色空间转换——cv::cvtColor()
cv::cvtColor()用于将图像从一个颜色空间转换到另一个颜色空间的转换(目前常见的颜色空间均支持),并且在转换的过程中能够保证数据的类型不变,即转换后的图像的数据类型和位深与源图像一致。
具体调用形式如下:
void cv::cvtColor(
cv::InputArray src, // 输入序列
cv::OutputArray dst, // 输出序列
int code, // 颜色映射码
int dstCn = 0 // 输出的通道数 (0='automatic')
);
其中,最后一个参数dstCn用于指定目标图像的通道数,如果指定的值是默认值0,那么通道数将由输入图像和颜色转换码决定。
cv::cvtColor()支持多种颜色空间之间的转换,其支持的转换类型和转换码如下:
1、RGB和BGR(opencv默认的彩色图像的颜色空间是BGR)颜色空间的转换
cv::COLOR_BGR2RGB
cv::COLOR_RGB2BGR
cv::COLOR_RGBA2BGRA
cv::COLOR_BGRA2RGBA
2、向RGB和BGR图像中增添alpha通道
cv::COLOR_RGB2RGBA
cv::COLOR_BGR2BGRA
3、从RGB和BGR图像中去除alpha通道
cv::COLOR_RGBA2RGB
cv::COLOR_BGRA2BGR
4、从RBG和BGR颜色空间转换到灰度空间
cv::COLOR_RGB2GRAY
cv::COLOR_BGR2GRAY
cv::COLOR_RGBA2GRAY
cv::COLOR_BGRA2GRAY
5、从灰度空间转换到RGB和BGR颜色空间
cv::COLOR_GRAY2RGB
cv::COLOR_GRAY2BGR
cv::COLOR_GRAY2RGBA
cv::COLOR_GRAY2BGRA
6、RGB和BGR颜色空间与BGR565颜色空间之间的转换
cv::COLOR_RGB2BGR565
cv::COLOR_BGR2BGR565
cv::COLOR_BGR5652RGB
cv::COLOR_BGR5652BGR
cv::COLOR_RGBA2BGR565
cv::COLOR_BGRA2BGR565
cv::COLOR_BGR5652RGBA
cv::COLOR_BGR5652BGRA
7、灰度空间域BGR565之间的转换
cv::COLOR_GRAY2BGR555
cv::COLOR_BGR5552GRAY
8、RGB和BGR颜色空间与CIE XYZ之间的转换
cv::COLOR_RGB2XYZ
cv::COLOR_BGR2XYZ
cv::COLOR_XYZ2RGB
cv::COLOR_XYZ2BGR
9、RGB和BGR颜色空间与uma色度(YCrCb空间)之间的转换
cv::COLOR_RGB2YCrCb
cv::COLOR_BGR2YCrCb
cv::COLOR_YCrCb2RGB
cv::COLOR_YCrCb2BGR
10、RGB和BGR颜色空间与HSV颜色空间之间的相互转换
cv::COLOR_RGB2HSV
cv::COLOR_BGR2HSV
cv::COLOR_HSV2RGB
cv::COLOR_HSV2BGR
11、RGB和BGR颜色空间与HLS颜色空间之间的相互转换
cv::COLOR_RGB2HLS
cv::COLOR_BGR2HLS
cv::COLOR_HLS2RGB
cv::COLOR_HLS2BGR
12、RGB和BGR颜色空间与CIE Lab颜色空间之间的相互转换
cv::COLOR_RGB2Lab
cv::COLOR_BGR2Lab
cv::COLOR_Lab2RGB
cv::COLOR_Lab2BGR
13、RGB和BGR颜色空间与CIE Luv颜色空间之间的相互转换
cv::COLOR_RGB2Luv
cv::COLOR_BGR2Luv
cv::COLOR_Luv2RGB
cv::COLOR_Luv2BGR
14、Bayer格式(raw data)向RGB或BGR颜色空间的转换
cv::COLOR_BayerBG2RGB
cv::COLOR_BayerGB2RGB
cv::COLOR_BayerRG2RGB
cv::COLOR_BayerGR2RGB
cv::COLOR_BayerBG2BGR
cv::COLOR_BayerGB2BGR
cv::COLOR_BayerRG2BGR
cv::COLOR_BayerGR2BGR
例子
Mat img1, img2, img3, img4;
img1 = imread("猫1.jpg");
imshow("原图", img1);
cvtColor(img1, img2, COLOR_RGB2GRAY);
imshow("灰度图", img2);
cvtColor(img1, img3, COLOR_RGB2HSV);
imshow("HSV", img3);
cvtColor(img1, img4, COLOR_RGB2BGR);
imshow("BGR", img4);
waitKey(0);
访问图像像素点
void pixel_visit_demo(Mat &image)
{
int w=image.cols;
int h=image.rows;
int dims=image,channels();
for(int row=0;i<row<h;row++)
{
//指针方式
uchar *current_row=image.ptr<uchar>(row);
for(int col=0;col<wlcol++)
{
if(dims==1)//灰色图像
{
int pv=image.at<uchar>(row.col);
image.at<uchar>(row,col)=255-pv;
//指针方式
pv=*current_row;
*current_row++=255-pv;
}
if(dims==3)//彩色图像
{
Vec3b bgr=image.at<Vec3b>(row,col);
image.at<Vec3b>(row,col)[0]=255-bgr[0];
image.at<Vec3b>(row,col)[1]=255-bgr[1];
image.at<Vec3b>(row,col)[2]=255-bgr[2];
//指针方式
*current_row++=255-*current_row;
*current_row++=255-*current_row;
*current_row++=255-*current_row;
}
}
}
像素操作
Mat dst
dst=image+Scalar(50,50,50);
imgshow("加法运算",dst);
dst=image-Scalar(50,50,50);
imgshow("减法运算",dst);
图像相乘
m=Mat(image.size(),image.type());
m=Scalar(2,2,2);
multiply(image,m,dst);
图像相除
m=Mat(image.size(),image.type());
m=Scalar(2,2,2);
divide(image,m,dst);
一、矩阵
矩阵是什么呢?如果你去书本或者网上查资料,会得到如下东西:
在数学中,矩阵(Matrix)是一个按照长方阵列排列的复数或实数集合,最早来自于方程组的系数及常数所构成的方阵。这一概念由19世纪英国数学家凯利首先提出。矩阵是高等代数学中的常见工具,也常见于统计分析等应用数学学科中。其定义如下:
由 m × n 个数aij排成的m行n列的数表称为m行n列的矩阵,简称m × n矩阵。记作:
这m×n 个数称为矩阵A的元素,简称为元,数aij位于矩阵A的第i行第j列,称为矩阵A的(i , j)元,以数 aij为(i , j)元的矩阵可记为(aij)或(aij)m × n,m×n矩阵A也记作Amn 。元素是实数的矩阵称为实矩阵,元素是复数的矩阵称为复矩阵。而行数与列数都等于n的矩阵称为n阶矩阵或n阶方阵。
然后还有矩阵的历史、词源来历等等。
但是,看完这些定义等的, 还是糊涂,为什么会有矩阵呢?为什么要这样定义呢?要矩阵有什么用呢?
矩阵是在线性代数这门课里学的,其实,矩阵是应线性方程式而生的,将线性方程组的系数及常数组成矩阵,一开始的矩阵就是这么来的。这样就可以将线性方程式的研究转化为矩阵的研究,简化了研究。线性代数是向量计算的基础,很多重要的数学模型都要用到向量计算,所以,矩阵的研究与计算最终将影响的是向量计算。好了,唠叨了这么多,还没说重点呢:矩阵的本质就是线性方程式,两者是一一对应关系。下面是它的复杂版本矩阵的实质意义:
给定了线性变换,它的系数所构成的矩阵也就确定。线性变换和矩阵之间存在着一一对应的关系。正是由于矩阵和线性变换之间存在着一一对应关系,因此可以利用矩阵来研究线性变换,也可以利用线性变换来解释矩阵的涵义。
二、矩阵的基本运算
矩阵的基本运算为:加、减、乘法及数乘。
加、减法及数乘都很简单,加法就是相同位置的数字加一下,减法也类似。矩阵乘以一个常数,就是所有位置都乘以这个数。
但是乘法就比较复杂了,计算规则是:矩阵第m行与第n列交叉位置的那个值,等于第一个矩阵第m行与第二个矩阵第n列,对应位置的每个值的乘积之和。
如下图所示,第一个矩阵第一行的每个数字(2和1),各自乘以第二个矩阵第一列对应位置的数字(1和1),然后将乘积相加( 2 x 1 + 1 x 1),得到结果矩阵左上角的那个值3。
1*7+2*9+11*3=58
这个规则确实也是有点复杂而奇怪的,上学的时候也仅仅是记住了这个规则,因为要考试嘛,从来没有想过为什么是这样,今天查矩阵的时候偶然看到一位前辈的文章:http://www.ruanyifeng.com/blog/2015/09/matrix-multiplication.html,认真拜读后整理过来,加深一下自己对矩阵乘法规则的理解和矩阵本质的理解。
结合矩阵的本质,从线性方程式的角度,理解矩阵乘法就毫无难度。
下面是一组线性方程式。
矩阵的最初目的,只是为线性方程组提供一个简写形式。
老实说,从上面这种写法,已经能看出矩阵乘法的规则了:系数矩阵第一行的2和1,各自与 x 和 y 的乘积之和,等于3。不过,这不算严格的证明,只是线性方程式转为矩阵的书写规则。
下面是严格的证明。有三组未知数 x、y 和 t,其中 x 和 y 的关系如下。
x 和 t 的关系如下。
有了这两组方程式,就可以求 y 和 t 的关系。从矩阵来看,很显然,只要把第二个矩阵代入第一个矩阵即可。
从方程式来看,也可以把第二个方程组代入第一个方程组。
上面的方程组可以整理成下面的形式。
最后那个矩阵等式,与前面的矩阵等式一对照,就会得到下面的关系。
矩阵乘法的计算规则,从而得到证明。
OpenCV基本矩阵算法
一、矩阵
Mat I,img,I1,I2,dst,A,B;
double k,alpha;
Scalar s;
1.加法
I=I1+I2;//等同add(I1,I2,I);
add(I1,I2,dst,mask,dtype);
scaleAdd(I1,scale,I2,dst);//dst=scale*I1+I2;
2.减法
absdiff(I1,I2,I);//I=|I1-I2|;
A-B;A-s;s-A;-A;
subtract(I1,I2,dst);
3.乘法
I=I.mul(I);//点乘,I.mul(I,3);–>I=3*I.^2
Mat C=A.mul(5/B);//==divide(A,B,C,5);
A*B;矩阵相乘
I=alpha*I;
Mat::cross(Mat);//三维向量(或矩阵)的叉乘,A.cross(B)
double Mat::dot(Mat);//2个向量(或矩阵)的点乘的结果,A.dot(B)
mul——-multiply
pow(src,double p,dst);//如果p是整数dst(I)=src(I)^p;其他|src(I)|^p
4.除法
divide(I1,I2,dst,scale,int dtype=-1);//dst=saturate_cast(I1*scale/I2);
A/B;alpha/A;都是点除
5.转换
I.convertTo(I1,CV_32F);//类型转换
A.t();//转置
flip(I,dst,int flipCode);//flipCode=0是上下翻转,>0时左右翻转,<0时一起来
sqrt(I,dst);
cvtColor(I,dst,int code,int dstCn=0);
resize:对图像进行形变
————————————————————————–
6.其他
Scalar s=sum(I);各通道求和
norm,countNonZero,trace,determinant,repeat都是返回Mat或者Scalar
countNonZero:用来统计非零的向量个数.(rows*cols个)
Scalar m=mean(I);//各通道求平均
Mat RowClone=C.row(1).clone();//复制第2行
addWeight(I1,alpha,I2,beta,gamma,dst,int dtype=-1);//dst=saturate(alpha*I1+beta*I2+gamma);dtype是dst的深度
—————————————————————————-
7.运算符
log10()
exp(I,dst);//dst=exp(I);计算每个数组元素的指数
log(I,dst);//如果Iij!=0;则dstij=log(|Iij|)
randu(I,Scalar::all(0),Scalar::all(255));
Mat::t()转置
Mat::inv(int method=DECOMP_LU)求逆。method=DECOMP_CHOLESKY(专门用于对称,速度是LU的2倍),DECOMP_SVD//A.inv();A.inv()*B;
invert(I1,dst,int method=DECOMP_LU);//用法同上
MatExpr abs(Mat)//求绝对值
A cmpop B;A compop alpha;alpha cmpop A;这里cmpop表示>,>=,==,!=,<=,<等,结果是CV_8UC1的mask的0或255
按位运算:A logicop B;A logicop s;s logicop A;~A;这里logicop代表&,|,^
bitwise_not(I,dst,mask);//inverts所有的队列
还有bitwise_and,bitwise_or,bitwise_xor,
min(A,B);min(A,alpha);max(A,B);max(A,alpha);都返回MatExpr,返回的dst和A的类型一样
double determinant(Mat);//行列式
bool eigen(I1,dst,int lowindex=-1,int highindex=-1);//
bool eigen(I1,dst,I,int…);//得到特征值向量dst和对应特征值的特征向量
minMaxLoc(I1,&minVal,&maxVal,Point *minLoc=0,Point* MaxLoc=0,mask);
//minLoc是2D时距原点最小的点(未考证)
——————————————————————————
8.初始化
Mat I(img,Rect(10,10,100,100));//用一块地方初始化。
Mat I=img(Range:all(),Range(1,3));//所有行,1~3列
Mat I=img.clone();//完全复制
img.copyTo(I);//传递矩阵头
Mat I(2,2,CV_8UC3,Scalar(0,0,255));//I=[0,0,255,0,0,255;0,0,255,0,0,255];
Mat E=Mat::eye(4,4,CV_64F);//对角矩阵
Mat O=Mat::ones(2,2,CV_32F);//全一矩阵
Mat Z=Mat::zeros(3,3,CV_8UC1);//全零矩阵
Mat C=(Mat_<double>(2,2)<<0,-1,2,3);//如果是简单矩阵的初始化
Mat::row(i);Mat::row(j);Mat::rowRange(start,end);Mat::colRange(start,end);都只是创建个头
Mat::diag(int d);d=0是是主对角线,d=1是比主低的对角线,d=-1….
static Mat Mat::diag(const Mat& matD)
Mat::setTo(Scalar &s);以s初始化矩阵
Mat::push_back(Mat);在原来的Mat的最后一行后再加几行
Mat::pop_back(size_t nelems=1);//移出最下面几行
——————————————————————————-
9.矩阵读取和修改
(1)1个通道:
for(int i=0;i<I.rows;++i)
for(int j=0;j<I.cols;++j)
I.at<uchar>(i,j)=k;
(2)3个通道:
Mat_<Vec3b> _I=I;//他没有4个通道寸,只有3个通道!
for(int i=0;i<I.rows;++i)
for(int j=0;j<I.cols;++j)
{
_I(i,j)[0]=b;
_I(i,j)[1]=g;
_I(i,j)[2]=r;
}
I=_I;
————————————————————
或者直接用I.at<Vec3b>(i,j)[0]….
————————————————-
float *s;
for(i=0;i<dealImg.rows;i++)
{s=proImg.ptr<float>(i);
for(j=0;j<dealImg.cols;j++)
{a1=s[3*j+1]-m1;
a2=s[3*j+2]-m2;}}
————————————————————————-
(3)其他机制
I.rows(0).setTo(Scalar(0));//把第一行清零
saturate_cast<uchar>(…);//可以确保内容为0~255的整数
Mat::total();返回一共的元素数量
size_t Mat::elemSize();返回元素的大小:CV_16SC3–>3*sizeof(short)–>6
size_t Mat::elemSize1();返回元素一个通道的大小CV_16SC3–>sizeof(short)–>2
int Mat::type()返回他的类型CV_16SC3之类
int Mat::depth()返回深度:CV_16SC3–>CV_16S
int Mat::channels()返回通道数
size_t Mat:step1()返回一个被elemSize1()除以过的step
Size Mat::size()返回Size(cols,rows);如果大于2维,则返回(-1,-1),都是先宽再高的
bool Mat::empty()如果没有元素返回1,即Mat::total()==0或者Mat::data==NULL
uchar *Mat::ptr(int i=0)指向第i行
Mat::at(int i)(int i,int j)(Point pt)(int i,int j,int k)
RNG随机类:next,float RNG::uniform(float a,float b);..
double RNG::gaussian(double sigma);
RNG::fill(I,int distType,Mat low,Mat up);//用随机数填充
randu(I,low,high);
randn(I,Mat mean,Mat stddev);
reduce(I,dst,int dim,int reduceOp,int dtype=-1);//可以统计每行或每列的最大、最小、平均值、和
setIdentity(dst,Scalar &value=Scalar(1));//把对角线替换为value
//效果等同:Mat A=Mat::eye(4,3,CV_32F)*5;
————————————————————–
10.较复杂运算
gemm(I1,I2,alpha,I3,beta,dst,int flags=0);//I1至少是浮点型,I2同I1,flags用来转置
//gemm(I1,I2,alpha,I3,beta,dst,GEMM_1_T,GEMM_3_T);–>dst=alpha*I1.t()*I2+beta*I3.t();可用此完全代替此函数
mulTransposed(I,dst,bool aTa,Mat delta=noArray(),double scale=1,int rtype=-1);
//I是1通道的,和gemm不同,他可用于任何类型。
//如果aTa=flase时,dst=scale*(I-delta).t()*(I-delta);
//如果是true,dst=scale*(I-delta)(I-delta).t();
calcCovarMatrix(Mat,int,Mat,Mat,int,int=);calcCovarMatrix(Mat I,Mat covar,Mat mean,int flags,int=);
cartToPolar//转到极坐标
compare(I1,I2,dst,cmpop);cmpop=CMP_EQ,CMP_GT,CMP_GE,CMP_LT,CMP_LE,COM_NE
completeSymm(M,bool lowerToUpper=false);当lowerToUpper=true时Mij=Mji(i<j);当为flase时,Mij=Mji(i>j)
变成可显示图像:convertScaleAbs(I,dst,alpha,beta);dst=saturate_cast<uchar>(|alpha*I+beta|);
dct(I,dst,int flags=0);//DCT变换,1维、2维的矩阵;flags=DCT_INVERSE,DCT_ROWS
idct,dft,idft
inRange(I1,I_low,I_up,dst);//dst是CV_8UC1,在2者之间就是255
Mahalanobis(vec1,vec2,covar);
merge(vector<Mat>,Mat);//把多个Mat组合成一个和split相反
double norm(…):当src2木有时,norm可以计算出最长向量、向量距离和、向量距离和的算术平方根
solveCubic解3次方程,solvePoly解n次方程
排列:sort,sortIdx
mixChannels();对某个通道进行各种传递
—————————————————————–
11.未懂的函数
getConvertElem,extractImageCOI,LUT
magnitude(x,y,dst);//I1,I2都是1维向量,dst=sqrt(x(I)^2+y(I)^2);
meanStdDev,
MulSpectrums(I1,I2,dst,flags);傅里叶
normalize(I,dst,alpha,beta,int normType=NORM_L2,int rtype=-1,mask);//归一化
PCA,SVD,solve,transform,transpose
二、其他数据结构
Point2f P(5,1);
Point3f P3f(2,6,7);
vector<float> v;v.push_back((float)CV_PI);v.push_back(2);v.push_back(3.01f);//不断入
vector<Point2f> vPoints(20);//一次定义20个
三、常用方法
Mat mask=src<0;这样很快建立一个mask了
四、以后可能用到的函数
randShuffle,repeat
五、矩阵处理
1、矩阵的内存分配与释放
(1) 总体上:
Opencv 使用C语言来进行矩阵操作。不过实际上有很多C++语言的替代方案可以更高效地完成。
在OpenCV中向量被当做是有一个维数为1的N维矩阵.
矩阵按行-行方式存储,每行以4字节(32位)对齐.
(2) 为新矩阵分配内存:
CvMat* cvCreateMat(int rows, int cols, int type);
type: 矩阵元素类型.
按CV_<bit_depth>(S|U|F)C<number_of_channels> 方式指定. 例如: CV_8UC1 、CV_32SC2.
示例:
CvMat* M = cvCreateMat(4,4,CV_32FC1);
(3) 释放矩阵内存:
CvMat* M = cvCreateMat(4,4,CV_32FC1);
cvReleaseMat(&M);
(4) 复制矩阵:
CvMat* M1 = cvCreateMat(4,4,CV_32FC1);
CvMat* M2;
M2=cvCloneMat(M1);
(5) 初始化矩阵:
double a[] = { 1, 2, 3, 4,
5, 6, 7, 8,
9, 10, 11, 12 };
CvMat Ma=cvMat(3, 4, CV_64FC1, a);
//等价于:
CvMat Ma;
cvInitMatHeader(&Ma, 3, 4, CV_64FC1, a);
(6) 初始化矩阵为单位矩阵:
CvMat* M = cvCreateMat(4,4,CV_32FC1);
cvSetIdentity(M); // does not seem to be working properl
2、访问矩阵元素
(1) 假设需要访问一个2D浮点型矩阵的第(i, j)个单元.
(2) 间接访问:
cvmSet(M,i,j,2.0); // Set M(i,j)
t = cvmGet(M,i,j); // Get M(i,j)
(3) 直接访问(假设矩阵数据按4字节行对齐):
CvMat* M = cvCreateMat(4,4,CV_32FC1);
int n = M->cols;
float *data = M->data.fl;
data[i*n+j] = 3.0;
(4) 直接访问(当数据的行对齐可能存在间隙时 possible alignment gaps):
CvMat* M = cvCreateMat(4,4,CV_32FC1);
int step = M->step/sizeof(float);
float *data = M->data.fl;
(data+i*step)[j] = 3.0;
(5) 对于初始化后的矩阵进行直接访问:
double a[16];
CvMat Ma = cvMat(3, 4, CV_64FC1, a);
a[i*4+j] = 2.0; // Ma(i,j)=2.0;
3、矩阵/向量运算
(1) 矩阵之间的运算:
CvMat *Ma, *Mb, *Mc;
cvAdd(Ma, Mb, Mc); // Ma+Mb -> Mc
cvSub(Ma, Mb, Mc); // Ma-Mb -> Mc
cvMatMul(Ma, Mb, Mc); // Ma*Mb -> Mc
(2) 矩阵之间的元素级运算:
CvMat *Ma, *Mb, *Mc;
cvMul(Ma, Mb, Mc); // Ma.*Mb -> Mc
cvDiv(Ma, Mb, Mc); // Ma./Mb -> Mc
cvAddS(Ma, cvScalar(-10.0), Mc); // Ma.-10 -> Mc
(3) 向量乘积:
double va[] = {1, 2, 3};
double vb[] = {0, 0, 1};
double vc[3];
CvMat Va=cvMat(3, 1, CV_64FC1, va);
CvMat Vb=cvMat(3, 1, CV_64FC1, vb);
CvMat Vc=cvMat(3, 1, CV_64FC1, vc);
double res=cvDotProduct(&Va,&Vb); // 向量点乘: Va . Vb -> res
cvCrossProduct(&Va, &Vb, &Vc); // 向量叉乘: Va x Vb -> Vc
注意在进行叉乘运算时,Va, Vb, Vc 必须是仅有3个元素的向量.
(4) 单一矩阵的运算:
CvMat *Ma, *Mb;
cvTranspose(Ma, Mb); // 转置:transpose(Ma) -> Mb (注意转置阵不能返回给Ma本身)
CvScalar t = cvTrace(Ma); // 迹:trace(Ma) -> t.val[0]
double d = cvDet(Ma); // 行列式:det(Ma) -> d
cvInvert(Ma, Mb); // 逆矩阵:inv(Ma) -> Mb
(5) 非齐次线性方程求解:
CvMat* A = cvCreateMat(3,3,CV_32FC1);
CvMat* x = cvCreateMat(3,1,CV_32FC1);
CvMat* b = cvCreateMat(3,1,CV_32FC1);
cvSolve(&A, &b, &x); // solve (Ax=b) for x
(6) 特征值与特征向量 (矩阵为方阵):
CvMat* A = cvCreateMat(3,3,CV_32FC1);
CvMat* E = cvCreateMat(3,3,CV_32FC1);
CvMat* l = cvCreateMat(3,1,CV_32FC1);
cvEigenVV(A, E, l); // l = A 的特征值(递减顺序)
// E = 对应的特征向量 (行向量)
(7) 奇异值分解(SVD):====
CvMat* A = cvCreateMat(3,3,CV_32FC1);
CvMat* U = cvCreateMat(3,3,CV_32FC1);
CvMat* D = cvCreateMat(3,3,CV_32FC1);
CvMat* V = cvCreateMat(3,3,CV_32FC1);
cvSVD(A, D, U, V, CV_SVD_U_T|CV_SVD_V_T); // A = U D V^T
标志位使矩阵U或V按转置形式返回 (若不转置可能运算出错)
C++ / Opencv 简单实现美颜效果(瘦脸、大眼、磨皮等)
最近项目需要用到美颜的一些效果,因此开始接触opencv 计算机视觉库,在腾讯课堂上找到一个简单且免费的入门视频《Opencv4 快速入门视频30讲》,看完视频后,初步才对opencv 有一个比较清晰的概念和基本用法。 接下来就是开始对美颜的一些初步接触,下面写的一个简单的测试 效果,具体功能包括亮度、对比度、瘦脸、大眼、美白磨皮等,但实际上用于项目使用还是问题很多,需要更多的优化。
1.图像创建各个功能滑动条
void BeautyCam::initMainImgUI()
{
namedWindow("BeautyCam", WINDOW_AUTOSIZE);
string path = "6.jpg";
m_MainImg =imread(path);
imshow("src", m_MainImg);
//检测人脸数据68点
m_vecFaceData = dectectFace68(path);
int max_value = 100;
int con_value = 100;
int lignhtnesss = 50;
int contrast = 2;
int bigeyeval = 0;
int faceval = 0;
int beautyval = 0;
createTrackbar("亮度", "BeautyCam", &lignhtnesss, max_value, on_lightness, (void*)(&m_MainImg));
createTrackbar("对比度", "BeautyCam", &contrast, max_value, on_contrast, (void*)(&m_MainImg));
createTrackbar("大眼", "BeautyCam", &bigeyeval, 60, on_BigEye, (void*)(&m_MainImg));
createTrackbar("瘦脸", "BeautyCam", &faceval, 70, on_thinFace, (void*)(&m_MainImg));
createTrackbar("美颜", "BeautyCam", &beautyval, 200, on_beautyFace, (void*)(&m_MainImg));
on_lightness(50, (void*)(&m_MainImg));
imshow("BeautyCam", m_MainImg);
}
此代码就是创建滚动条的初始化程序,主要就是createTrackbar函数的使用:
原型:
CV_EXPORTS int createTrackbar(const String& trackbarname, //滚动条名称
const String& winname, //滚动条作用在哪一个窗口上 窗口名称
int* value, int count, //滑块初始值 和 滚动条最大值
TrackbarCallback onChange = 0, // 回调函数 滑条值变化
void* userdata = 0); //用户创给回调函数的用户数据
这里就是需要定义相关的回调函数,原型为:void (*TrackbarCallback)(int pos, void* userdata);
因此只需要创建相同的函数就行,滑条的变化就会导致回调函数触发,传递形参pos值的变化。
例如:
//对比度调节
static void on_contrast(int b, void*userdata);
//亮度调节
static void on_lightness(int b, void*userdata);
//眼睛调节
static void on_BigEye(int b, void*userdata);
//瘦脸效果
static void on_thinFace(int b, void*userdata);
//美颜效果
static void on_beautyFace(int b, void*userdata);
2.回调函数 对比度调节的实现
void BeautyCam::on_contrast(int b, void*userdata)
{
Mat img = *((Mat *)userdata);
Mat m = Mat::zeros(img.size(), img.type());
Mat dst = Mat::zeros(img.size(), img.type());
m = Scalar(b, b, b);
double con = b / 100.0;
addWeighted(img, con, m, 0, 0, dst);
imshow("BeautyCam", dst);
}
addWeighted()函数是将两张相同大小,相同类型的图片融合的函数;
原型:CV_EXPORTS_W void addWeighted(InputArray src1, //第一个输入图像
double alpha, //第一个元素权重
InputArray src2, //第二个输入图像
double beta, // 第二个元素权重
double gamma, //图1和图2 作和后添加的数值
OutputArray dst,//输出图像
int dtype = -1);
3. 回调函数 亮度调节的实现
void BeautyCam::on_lightness(int b, void*userdata)
{
Mat img = *((Mat *)userdata);
Mat m = Mat::zeros(img.size(), img.type());
Mat dst = Mat::zeros(img.size(), img.type());
m = Scalar(b, b, b);
addWeighted(img, 1.0, m, 0, b, dst);
imshow("BeautyCam", dst);
}
同上,对比度和亮度其实可以直接用一个addWeighted 实现,同时设置亮度和对比度(差值在扩大),主要是就是对addWeighed 的beta 和alpha参数去调节。
4.人脸数据检测
需要实现大眼或者瘦脸的效果,首先是需要检测人脸并提取特征点,一般常用的就是68个点,如下:
在此处是通过调用三方库dlib来获取68点特性点数据的:
std::vector<std::vector<Point2f>> BeautyCam::dectectFace68(const string &path)
{
std::vector<std::vector<Point2f>> rets;
//加载图片路径
array2d<rgb_pixel> img;
load_image(img, path.c_str());
//定义人脸检测器
frontal_face_detector detector = get_frontal_face_detector();
std::vector<dlib::rectangle> dets = detector(img);
for (auto var : dets)
{
//关键点检测器
shape_predictor sp;
deserialize("shape_predictor_68_face_landmarks.dat") >> sp;
//定义shape对象保存检测的68个关键点
full_object_detection shape = sp(img, var);
//存储文件
ofstream out("face_detector.txt");
//读取关键点到容器中
std::vector<Point2f> points_vec;
for (int i = 0; i < shape.num_parts(); ++i)
{
auto a = shape.part(i);
out << a.x() << " " << a.y() << " ";
Point2f ff(a.x(), a.y());
points_vec.push_back(ff);
}
rets.push_back(points_vec);
}
cout << "人脸检测结束:" <<dets.size()<<"张人脸数据"<< endl;
return rets;
}
5.图像平移变形算法
接下来的难题就是图像局部变形算法,具体原理就是瘦脸是使用图像局部平移变形,放大眼睛是图像局部缩放变形。
图像局部平移变形公式:
说实话我看着这个公式看不懂,好像是结合《Interactive Image Warping》交互式图像变形算法而来的,去查看此文章过程中,发现全英文的,只能说一句,干瞪眼,完全看不懂,更别说公式是怎么推导出来的了,放弃了,直接根据大佬的代码去看懂这个公式。
参考:https://blog.csdn.net/grafx/article/details/70232797 图像处理算法之瘦脸及放大眼睛
图像局部缩放公式(大眼):
参考:https://www.freesion.com/article/40151105562/ 瘦脸大眼算法
百度过程中并没有发现c++编写的一些瘦脸大眼测试例子,有些贴的代码比较简单参数有点不好理解,但是python却有相关的例子(原理一样 ),因此通过python代码去封装成c++ 能使用的接口。
瘦脸和大眼主要是参考如下两篇博客来封装的函数接口:
参考:https://www.cnblogs.com/ckAng/p/10978078.html python+opencv+dlib瘦脸效果
参考:https://www.freesion.com/article/40151105562/ 瘦脸大眼算法
6.回调函数 大眼效果调节
void BeautyCam::on_BigEye(int b, void*userdata)
{
Mat src = *((Mat *)userdata);
Mat dst = src.clone();
for (auto points_vec : m_pIntance->m_vecFaceData)
{
Point2f left_landmark = points_vec[38];
Point2f left_landmark_down = points_vec[27];
Point2f right_landmark = points_vec[44];
Point2f right_landmark_down = points_vec[27];
Point2f endPt = points_vec[30];
//# 计算第4个点到第6个点的距离作为距离
/*float r_left = sqrt(
(left_landmark.x - left_landmark_down.x) * (left_landmark.x - left_landmark_down.x) +
(left_landmark.y - left_landmark_down.y) * (left_landmark.y - left_landmark_down.y));
cout << "左眼距离:" << r_left;*/
float r_left = b;
// # 计算第14个点到第16个点的距离作为距离
//float r_right = sqrt(
// (right_landmark.x - right_landmark_down.x) * (right_landmark.x - right_landmark_down.x) +
// (right_landmark.y - right_landmark_down.y) * (right_landmark.y - right_landmark_down.y));
//cout << "右眼距离:" << r_right;
float r_right = b;
// # 瘦左
m_pIntance->LocalTranslationWarp_Eye(src, dst, left_landmark.x, left_landmark.y, endPt.x, endPt.y, r_left);
// # 瘦右
m_pIntance->LocalTranslationWarp_Eye(src, dst, right_landmark.x, right_landmark.y, endPt.x, endPt.y, r_right);
}
imshow("BeautyCam", dst);
}
对于眼睛的放大,主要是对特征点38 27 44 30 四个点来进行图像局部缩放,也可以自己找适当的点尝试,点并不是唯一的。
此处用作滑条调节,因此把左眼距离和右眼距离都设置成滑条值,但这样是有问题的,因为不是每对眼睛拍照的眼珠都是一样大的。因此可以直接根据计算的距离(代码注释的地方)来进行加减滑条值。
例如:r_right = (系数)*r_right + b r_left= (系数)*r_left+ b 需要自己去调节适当的系数
图像局部缩放算法代码实现:
void BeautyCam::LocalTranslationWarp_Eye(Mat &img, Mat &dst, int warpX, int warpY, int endX, int endY, float radius)
{
//平移距离
float ddradius = radius * radius;
//计算|m-c|^2
size_t mc = (endX - warpX)*(endX - warpX) + (endY - warpY)*(endY - warpY);
//计算 图像的高 宽 通道数量
int height = img.rows;
int width = img.cols;
int chan = img.channels();
auto Abs = [&](float f) {
return f > 0 ? f : -f;
};
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
// # 计算该点是否在形变圆的范围之内
//# 优化,第一步,直接判断是会在(startX, startY)的矩阵框中
if ((Abs(i - warpX) > radius) && (Abs(j - warpY) > radius))
continue;
float distance = (i - warpX)*(i - warpX) + (j - warpY)*(j - warpY);
if (distance < ddradius)
{
float rnorm = sqrt(distance) / radius;
float ratio = 1 - (rnorm - 1)*(rnorm - 1)*0.5;
//映射原位置
float UX = warpX + ratio * (i - warpX);
float UY = warpY + ratio * (j - warpY);
//根据双线性插值得到UX UY的值
BilinearInsert(img, dst, UX, UY, i, j);
}
}
}
}
这其中使用到了双线性插值算法,因此也去查看了双线性插值算法原理,这里我只是对每一个像素点单独进行双线性插值如下:
void BeautyCam::BilinearInsert(Mat &src, Mat &dst, float ux, float uy, int i, int j)
{
auto Abs = [&](float f) {
return f > 0 ? f : -f;
};
int c = src.channels();
if (c == 3)
{
//存储图像得浮点坐标
CvPoint2D32f uv;
CvPoint3D32f f1;
CvPoint3D32f f2;
//取整数
int iu = (int)ux;
int iv = (int)uy;
uv.x = iu + 1;
uv.y = iv + 1;
//step图象像素行的实际宽度 三个通道进行计算(0 , 1 2 三通道)
f1.x = ((uchar*)(src.data + src.step*iv))[iu * 3] * (1 - Abs(uv.x - iu)) + \
((uchar*)(src.data + src.step*iv))[(iu + 1) * 3] * (uv.x - iu);
f1.y = ((uchar*)(src.data + src.step*iv))[iu * 3 + 1] * (1 - Abs(uv.x - iu)) + \
((uchar*)(src.data + src.step*iv))[(iu + 1) * 3 + 1] * (uv.x - iu);
f1.z = ((uchar*)(src.data + src.step*iv))[iu * 3 + 2] * (1 - Abs(uv.x - iu)) + \
((uchar*)(src.data + src.step*iv))[(iu + 1) * 3 + 2] * (uv.x - iu);
f2.x = ((uchar*)(src.data + src.step*(iv + 1)))[iu * 3] * (1 - Abs(uv.x - iu)) + \
((uchar*)(src.data + src.step*(iv + 1)))[(iu + 1) * 3] * (uv.x - iu);
f2.y = ((uchar*)(src.data + src.step*(iv + 1)))[iu * 3 + 1] * (1 - Abs(uv.x - iu)) + \
((uchar*)(src.data + src.step*(iv + 1)))[(iu + 1) * 3 + 1] * (uv.x - iu);
f2.z = ((uchar*)(src.data + src.step*(iv + 1)))[iu * 3 + 2] * (1 - Abs(uv.x - iu)) + \
((uchar*)(src.data + src.step*(iv + 1)))[(iu + 1) * 3 + 2] * (uv.x - iu);
((uchar*)(dst.data + dst.step*j))[i * 3] = f1.x*(1 - Abs(uv.y - iv)) + f2.x*(Abs(uv.y - iv)); //三个通道进行赋值
((uchar*)(dst.data + dst.step*j))[i * 3 + 1] = f1.y*(1 - Abs(uv.y - iv)) + f2.y*(Abs(uv.y - iv));
((uchar*)(dst.data + dst.step*j))[i * 3 + 2] = f1.z*(1 - Abs(uv.y - iv)) + f2.z*(Abs(uv.y - iv));
}
}
整体的一个大眼效果就完成了。
7.回调函数 瘦脸效果调节
void BeautyCam::on_thinFace(int b, void*userdata)
{
Mat src = *((Mat *)userdata);
Mat dst = src.clone();
for (auto points_vec : m_pIntance->m_vecFaceData)
{
Point2f endPt = points_vec[34];
for (int i = 3; i < 15; i = i + 2)
{
Point2f start_landmark = points_vec[i];
Point2f end_landmark = points_vec[i + 2];
//计算瘦脸距离(相邻两个点算距离)
/*float dis = sqrt(
(start_landmark.x - end_landmark.x) * (start_landmark.x - end_landmark.x) +
(start_landmark.y - end_landmark.y) * (start_landmark.y - end_landmark.y));*/
float dis = b;
dst = m_pIntance->LocalTranslationWarp_Face(dst, start_landmark.x, start_landmark.y, endPt.x, endPt.y, dis);
}
}
imshow("BeautyCam", dst);
}
在这里就没有选择进行选定指定特征点进行计算距离,我直接使用滑条值来作为瘦脸距离进行操作的。具体可以根据上面68特征点采取几个点来计算瘦脸距离。
图像局部平移算法代码实现:
Mat dst = img.clone();
//平移距离
float ddradius = radius * radius;
//计算|m-c|^2
size_t mc = (endX - warpX)*(endX - warpX) + (endY - warpY)*(endY - warpY);
//计算 图像的高 宽 通道数量
int height = img.rows;
int width = img.cols;
int chan = img.channels();
auto Abs = [&](float f) {
return f > 0 ? f : -f;
};
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
// # 计算该点是否在形变圆的范围之内
//# 优化,第一步,直接判断是会在(startX, startY)的矩阵框中
if ((Abs(i - warpX) > radius) && (Abs(j - warpY) > radius))
continue;
float distance = (i - warpX)*(i - warpX) + (j - warpY)*(j - warpY);
if (distance < ddradius)
{
//# 计算出(i, j)坐标的原坐标
//# 计算公式中右边平方号里的部分
float ratio = (ddradius - distance) / (ddradius - distance + mc);
ratio *= ratio;
//映射原位置
float UX = i - ratio * (endX - warpX);
float UY = j - ratio * (endY - warpY);
//根据双线性插值得到UX UY的值
BilinearInsert(img, dst, UX, UY, i, j);
//改变当前的值
}
}
}
return dst;
}
以上两个平移和缩放算法主要就是根据python代码来进行封装的,整体效果还行,但还是需要不断的优化和更改。
8.回调函数 美颜磨皮算法
原理是使用opencv自带的人脸训练数据来获取人脸矩阵数据,进行双边滤波和高斯模糊来实现的;
美颜磨皮算法公式:
代码实现如下:
void BeautyCam::on_beautyFace(int b, void*userdata)
{
Mat src = *((Mat *)userdata);
Mat img = src.clone();
double scale = 1.3;
CascadeClassifier cascade = m_pIntance->loadCascadeClassifier("./haarcascade_frontalface_alt.xml");//人脸的训练数据
CascadeClassifier netcascade = m_pIntance->loadCascadeClassifier("./haarcascade_eye_tree_eyeglasses.xml");//人眼的训练数据
if (cascade.empty() || netcascade.empty())
return;
m_pIntance->detectAndDraw(img, cascade, scale,b);
if (m_pIntance->isDetected == false)
{
cout << "enter" << endl;
Mat dst;
int value1 = 3, value2 = 1;
int dx = value1 * 5; //双边滤波参数之一
//double fc = value1 * 12.5; //双边滤波参数之一
double fc = b;
int p = 50;//透明度
Mat temp1, temp2, temp3, temp4;
//对原图层image进行双边滤波,结果存入temp1图层中
bilateralFilter(img, temp1, dx, fc, fc);
//将temp1图层减去原图层image,将结果存入temp2图层中
temp2 = (temp1 - img + 128);
//高斯模糊
GaussianBlur(temp2, temp3, Size(2 * value2 - 1, 2 * value2 - 1), 0, 0);
//以原图层image为基色,以temp3图层为混合色,将两个图层进行线性光混合得到图层temp4
temp4 = img + 2 * temp3 - 255;
//考虑不透明度,修正上一步的结果,得到最终图像dst
dst = (img*(100 - p) + temp4 * p) / 100;
dst.copyTo(img);
}
imshow("BeautyCam", img);
}
美颜效果主要是通过双边滤波参数来调节的。
void BeautyCam::detectAndDraw(Mat& img, CascadeClassifier& cascade, double scale, int val)
{
std::vector<Rect> faces;
const static Scalar colors[] = { CV_RGB(0,0,255),
CV_RGB(0,128,255),
CV_RGB(0,255,255),
CV_RGB(0,255,0),
CV_RGB(255,128,0),
CV_RGB(255,255,0),
CV_RGB(255,0,0),
CV_RGB(255,0,255) };//用不同的颜色表示不同的人脸
//将图片缩小,加快检测速度
Mat gray, smallImg(cvRound(img.rows / scale), cvRound(img.cols / scale), CV_8UC1);
//因为用的是类haar特征,所以都是基于灰度图像的,这里要转换成灰度图像
cvtColor(img, gray, CV_BGR2GRAY);
resize(gray, smallImg, smallImg.size(), 0, 0, INTER_LINEAR);//将尺寸缩小到1/scale,用线性插值
equalizeHist(smallImg, smallImg);//直方图均衡
cascade.detectMultiScale(smallImg, //image表示的是要检测的输入图像
faces,//objects表示检测到的人脸目标序列
1.1, //caleFactor表示每次图像尺寸减小的比例
2, //minNeighbors表示每一个目标至少要被检测到3次才算是真的目标(因为周围的像素和不同的窗口大小都可以检测到人脸),
0 | CASCADE_SCALE_IMAGE ,//minSize为目标的最小尺寸
Size(30, 30)); //minSize为目标的最大尺寸
int i = 0;
//遍历检测的矩形框
for (std::vector<Rect>::const_iterator r = faces.begin(); r != faces.end(); r++, i++)
{
isDetected = true;
Mat smallImgROI;
std::vector<Rect> nestedObjects;
Point center, left, right;
Scalar color = colors[i % 8];
int radius;
center.x = cvRound((r->x + r->width*0.5)*scale);//还原成原来的大小
center.y = cvRound((r->y + r->height*0.5)*scale);
radius = cvRound((r->width + r->height)*0.25*scale);
left.x = center.x - radius;
left.y = cvRound(center.y - radius * 1.3);
if (left.y < 0)
{
left.y = 0;
}
right.x = center.x + radius;
right.y = cvRound(center.y + radius * 1.3);
if (right.y > img.rows)
{
right.y = img.rows;
}
/*原理算法
美肤-磨皮算法
Dest =(Src * (100 - Opacity) + (Src + 2 * GuassBlur(EPFFilter(Src) - Src + 128) - 256) * Opacity) /100 ;
*/
//绘画识别的人脸框
//rectangle(img, left, right, Scalar(255, 0, 0));
Mat roi = img(Range(left.y, right.y), Range(left.x, right.x));
Mat dst;
int value1 = 3, value2 = 1;
int dx = value1 * 5; //双边滤波参数之一
//double fc = value1 * 12.5; //双边滤波参数之一
double fc = val;//变化值
int p = 50;//透明度
Mat temp1, temp2, temp3, temp4;
//双边滤波 输入图像 输出图像 每像素领域的直径范围颜色空间过滤器的sigma 坐标空间滤波器的sigma
bilateralFilter(roi, temp1, dx, fc, fc);
temp2 = (temp1 - roi + 128);
//高斯模糊
GaussianBlur(temp2, temp3, Size(2 * value2 - 1, 2 * value2 - 1), 0, 0);
temp4 = roi + 2 * temp3 - 255;
dst = (roi*(100 - p) + temp4 * p) / 100;
dst.copyTo(roi);
}
}
到此,美白磨皮的简单功能就实现了。
参考:https://blog.csdn.net/zhangqipu000/article/details/53260647 opencv 美白磨皮人脸检测
整体效果只是简单的实现,如果要运用到项目中,问题还是很多的,需要不断的优化和算法的更改呀。作为最近学习opencv的一个简单demo,来巩固知识点。
下一篇:Qt与FFmpeg开发指南