您现在的位置是:网站首页> C/C++

Qt OpenCV开发笔记

  • C/C++
  • 2022-04-24
  • 1412人已阅读
摘要

Qt OpenCV开发笔记

Qt界面显示OpenCV读取的图片

在QML中显示OpenCV图片

OPENCV CV_64FC1含义

用cv::Scalar来设置opencv中图片的颜色

颜色空间转换——cv::cvtColor()

OpenCV访问图像像素

OpenCV像素操作

矩阵运算

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 的关系如下。


1.png


x 和 t 的关系如下。


1.png


有了这两组方程式,就可以求 y 和 t 的关系。从矩阵来看,很显然,只要把第二个矩阵代入第一个矩阵即可。


1.png


从方程式来看,也可以把第二个方程组代入第一个方程组。


1.png


上面的方程组可以整理成下面的形式。


1.png


最后那个矩阵等式,与前面的矩阵等式一对照,就会得到下面的关系。


2.png


矩阵乘法的计算规则,从而得到证明。


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个点,如下:

1.png

在此处是通过调用三方库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.图像平移变形算法


接下来的难题就是图像局部变形算法,具体原理就是瘦脸是使用图像局部平移变形,放大眼睛是图像局部缩放变形。


图像局部平移变形公式:


1.png


说实话我看着这个公式看不懂,好像是结合《Interactive Image Warping》交互式图像变形算法而来的,去查看此文章过程中,发现全英文的,只能说一句,干瞪眼,完全看不懂,更别说公式是怎么推导出来的了,放弃了,直接根据大佬的代码去看懂这个公式。


参考:https://blog.csdn.net/grafx/article/details/70232797  图像处理算法之瘦脸及放大眼睛


图像局部缩放公式(大眼):


2.png


参考: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自带的人脸训练数据来获取人脸矩阵数据,进行双边滤波和高斯模糊来实现的;


美颜磨皮算法公式:

1.png

代码实现如下:

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,来巩固知识点。


Top