美文网首页 移动 前端 Python Android Java
OpenCV(一)人脸定位&人脸定位模型训练

OpenCV(一)人脸定位&人脸定位模型训练

作者: zcwfeng | 来源:发表于2020-11-11 15:31 被阅读0次

简介

OpenCV是一个基于BSD许可开源发行的跨平台计算机视觉库。拥有C++,Python和Java接口,并且支持Windows, Linux, Mac OS, iOS 和 Android系统。实现了图像处理和计算机视觉方面的很多通用算法。

模块 功能
Core 核心基础模块,定义了被所有其他模块和基本数据结构(包括重要的多维数组Mat)使用的基本函数、底层数据结构和算法函数
Imgproc 图像处理模块,包括:滤波、高斯模糊、形态学处理、几何变换、颜色空间转换及直方图计算等
Highgui 高层用户交互模块,包括:GUI、图像与视频I\O等
Video 视频分析,,运动分析及目标跟踪。
Calib3d 3D模块,包括:摄像机标定、立体匹配、3D重建等
Features2d 二维特征检测与描述模块,包括:图像特征检测、描述、匹配等
Objdetect 目标检测模块,如:人脸检测等
MI 机器学习模块,包括:支持向量机、神经网络等
Flann 最近邻开源库。包含一系列查找算法,自动选取最快算法的机制。
Imgcodecs 图像编解码模块,图像文件的读写操作
Photo 图像计算(处理)模块,图像修复及去噪。
Shape 形状匹配算法模块。描述形状、比较形状
Stitching 图像拼接
Superres 超分辨率模块
Videoio 视频读写模块,视频文件包括摄像头的输入。
Videostab 解决拍摄的视频稳定
Dnn 深度神经网络
contrib 可以引入额外模块

优化补充-android 搭建和技巧
github 搜索opencv或者到官网,下载android sdk

例子: 如果你只需要图片处理的功能,又不想用其他的功能,由于库太大。你可以有选择的组合,使用静态库的方式。
但是core.a 相关的库必须有。然后组合Imgproc.a 相关的就可以了

开发环境 搭建

参见我的OpenCV文章demo
mac 环境为例子。windows 稍微麻烦一些不网上有教程

测试环境

移动端比较麻烦,我们先用Clion环境测试
新建项目,配置CMakeLists.txt

cmake_minimum_required(VERSION 3.15)
project(FaceTrain)

set(CMAKE_CXX_STANDARD 14)

add_executable(FaceTrain main.cpp)

#修改为自己的路径
#set(OpenCV_DIR F:/xxx/OpenCV/mingw-build2)   windows 环境
# Mac 环境
find_package(OpenCV REQUIRED)

include_directories(${OpenCV_INCLUDE_DIRS})
message(STATUS "${OpenCV_INCLUDE_DIRS}")
message(STATUS "${OpenCV_LIBS}")

target_link_libraries(FaceTrain ${OpenCV_LIBS})

Hello World ,打开一张图片 main.cpp

#include <iostream>
#include <opencv2/opencv.hpp>
using namespace cv;

int test(){
    Mat srcImage = imread("/Users/zcw/Downloads/WechatIMG12683.jpeg");
    if (!srcImage.data) {
        std::cout << "Image not loaded";
        return -1;
    }
    imshow("[img]", srcImage);
    waitKey(0);
    return 0;
}

int main() {
    std::cout << "Hello, World!" << std::endl;
    test();
    return 0;
}

图片能够成功打开证明没问题

人脸定位

人脸定位是人脸检测、识别等一系列后续功能的基础。比如,美颜相机的贴纸、大眼等功能都需要先定位到人脸才 能进行后续的处理,否则你放大的就不是眼睛,可能是一些什么不可描述的部位了。另外在人脸识别方面,比如支 付宝刷脸支付、张学友演唱会抓逃犯都运用到了人脸定位然后识别的功能。

#include <iostream>
#include <opencv2/opencv.hpp>

using namespace cv;

class CascadeDetectorAdapter : public DetectionBasedTracker::IDetector {
public:
    CascadeDetectorAdapter(cv::Ptr<cv::CascadeClassifier> detector) :
            IDetector(),
            Detector(detector) {
        CV_Assert(detector);
    }

    void detect(const cv::Mat &Image, std::vector<cv::Rect> &objects) {

        Detector->detectMultiScale(Image, objects, scaleFactor, minNeighbours, 0, minObjSize, maxObjSize);

    }

    virtual ~CascadeDetectorAdapter() {

    }

private:
    CascadeDetectorAdapter();

    cv::Ptr<cv::CascadeClassifier> Detector;
};

int main() {


    String path = "xxx\\OpenCV\\face\\train\\samples\\result\\cascade.xml";
//    String path = "xxx\\OpenCV\\opencv\\build\\etc\\lbpcascades\\lbpcascade_frontalface.xml"; -> 训练模型
    /**
   *   显示摄像头的图像
   */

    //智能指针
    Ptr<CascadeClassifier> classifier = makePtr<CascadeClassifier>(path);
    //创建一个跟踪适配器
    Ptr<CascadeDetectorAdapter> mainDetector = makePtr<CascadeDetectorAdapter>(classifier);

    Ptr<CascadeClassifier> classifier1 = makePtr<CascadeClassifier>(path);
    //创建一个跟踪适配器
    Ptr<CascadeDetectorAdapter> trackingDetector = makePtr<CascadeDetectorAdapter>(classifier1);

    //拿去用的跟踪器
    DetectionBasedTracker::Parameters DetectorParams;
    Ptr<DetectionBasedTracker> tracker = makePtr<DetectionBasedTracker>(mainDetector, trackingDetector, DetectorParams);
    //开启跟踪器
    tracker->run();

    // android不能使用这个玩意
    VideoCapture capture(0);
    Mat img;
    Mat gray;
    int i = 0;
    while (1) {

        capture >> img;

        // 预处理, 去噪 ,取出图片噪声

        // img的 颜色空间是 BGR,不像现在,早期的计算机中主流是bgr,而不是rgb
        cvtColor(img, gray, COLOR_BGR2GRAY);
        //增强对比度 (直方图均衡)
        equalizeHist(gray, gray);

        std::vector<Rect> faces;
        //定位人脸 N个
        tracker->process(gray);
        tracker->getObjects(faces);
        //classifier->detectMultiScale(gray, faces);
        for (Rect face : faces) {
            //画矩形
            //分别指定 bgra
            if (face.x < 0 || face.width < 0 || face.x + face.width > img.cols ||
                face.y < 0 || face.height < 0 || face.y + face.height > img.rows) {
                continue;
            }
            rectangle(img, face, Scalar(255, 0, 255));
#if 0
            /**
             * 制作训练正样本........
             */
            //使用opencv自带的模型 记录你的脸作为样本
            //把找到的人脸扣出来
            Mat m;
            //把img中的脸部位拷贝到m中
            img(face).copyTo(m);
            //把人脸 重新设置为 24x24大小的图片
            resize(m, m, Size(24, 24));
            //转成灰度
            cvtColor(m, m, COLOR_BGR2GRAY);
            char p[100];
            sprintf(p, "F:/Lance/OpenCV/face/train/samples/lance/%d.jpg", i++);
            //把mat写出为jpg文件
            imwrite(p, m);
            m.release();
#endif
        }
        imshow("摄像头", img);
        //延迟30ms 按Esc退出 ,27 =Esc
        if (waitKey(30) == 27) {
            break;
        }
    }
    if (!img.empty()) img.release();
    if (!gray.empty()) gray.release();
    capture.release();
    tracker->stop();
    return 0;
}

级联分类器

从一堆弱分类器里面,挑出一个最符合要求的弱分类器,用着这个弱分类器把不想要的数据剔除,保留想要的数据 然后再从剩下的弱分类器里,再挑出一个最符合要求的弱分类器,对上一级保留的数据,把不想要的数据剔除,保 留想要的数据。最后,通过不断串联几个弱分类器,进过数据层层筛选,最后得到我们想要的数据。这就是级联分 类器。弱分类器分类正确率较低,但是较容易获得,强分类器分类正确率较高,但是较难获得。只要样本充足,弱 学习可以通过一定的组合获得任意精度的强学习。级联分类器就是 N个弱分类器 以级联的方式,从简单到复杂逐 步串联而成。
可以用决策树来构建一个简单的弱分类器, 将提取到的特征与分类器的特征进行逐个比较,从而判断该特征是否属 于人脸:

算法.png

而强分类器相当于先让各个弱分类器进行投票,然后让投票结果根据各弱分类器的错误率进行加权相加,最后与平 均的投票结果进行比较得到最终结果。

我们将使用OpenCV中提供的LBP特征提取Adaboost算法(机器学习算法)训练人脸定位级联分类器。

训练

http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/user_guide/ug_traincascade.html

正样本:包含人脸的图片,灰度图。 正样本大小:统一大小并且不能小于负样本 负样本:不包含人脸的图片 负样本大小:无所谓
正、负样本个数比列大约为 1: 3

制作正样本

正样本目录为:xxx,内容为100张大小为24x24的人脸 假设目录结构如下:

 /xxx 
0.jpg 
1.jpg

制作xxx.data文件,文件内容如下:

 #分别表示 1张人脸 ;人脸从 0,0坐标开始;大小为24x24 xxx/0.jpg 1 0 0 24 24

#================================================================== 
#假设为2个人脸; 则数据为人脸分别为 100,200处的50x50 和 50,30处的25x25为人脸
 xxx/1.jpg 2 100 200 50 50 50 30 25 25

data文件内容可以使用java 程序来制作

 public class GeneateFile{
    public static void main(String[] args) throws Exception{ 
    String content = String.format("xxx/%d.jpg 1 0 0 24 24\n",i);
    fos.write(content.getBytes()); }
    fos.close(); }
}

data目录执行: opencv_createsamples

 
opencv_createsamples -info xxx.data -vec xxx.vec -num 100 -w 24 -h 24 -info: 正样本描述
-vec : 输出的正样本文件
-num : 正样本数量
-w -h: 输出样本的大小
#输出:Done. Created 100 samples 表示成功生成100个样本

制作负样本

负样本大小无所谓,个数为300。假如目录结构如下:

/bg
 0.jpg
1.jpg
 bg.data

则bg.data文件中的内容将如下所示:

 bg/0.jpg bg/1.jpg

训练data

创建一个data 目录,执行:

opencv_traincascade -data data -vec xxx.vec -bg bg.data -numPos 100 -numNeg 300 -numStages 15 -featureType LBP -w 24 -h 24
-data : 目录,需要手动创建,生成的结果 训练的模型会输出到这个目录 -vec : 正样本
-bg : 负样本
-numPos :每级分类器训练时所用到的正样本数目
-numNeg :每级分类器训练时所用到的负样本数目,可以大于-bg数目 -numStages:训练分类器的级数,如果层数多,分类器的误差就更小,但是检测速度慢。(15-20) -featureType: LBP
-w -h
输出:
Training until now has taken 0 days 0 hours 0 minutes 10 seconds. 表示成功
输出:
Required leaf false alarm rate achieved. Branch training terminated. 表示成功,但是误检率已经达标。(样本太少了,生成的模型质量不行)

输出:
Bad argument < Can not get new positive sample. The most possible reason is insufficient count of samples in given vec-file.
则意味着错误。
#参数:(未使用) minHitRate:分类器的每一级希望得到的最小检测率。当设置为0.995(默认)时,如果numPos个数为1000个,那么第一 级分类器其中的5个就很可能不被检测,第二级选择的时候必须多选择后面的5个,按照这种规律我们为后面的每级多增 加numPos*minHitRate,5个正样本.
实际准备的正样本数量应该(读入vec的正样本数) >= numPos + (numStage - 1) * numPos * (1 - minHitRate) 按照此公式计算, numPos+14* numPos * 0.005 = 1.07*numPos ,也就是正样本数量要大于等于 1.07*numPos。 即:numPos:100,正样本数则为 107。
而我们正样本是100,所以numPos应该传:100/1.07=93。 因为实际设置numPos时可以将其设置的稍微再大些,最终的目的是要尽量让所有的正样本都参与到训练中。但是,过大 就会出错。
  
  

由于此处我只拿了自己的脸来训练,所以模型只能定位xxx的脸......

 
采集样本
for (Rect face : faces) { //画矩形
#if 0
//把人脸 重新设置为 24x24大小的图片 
resize(m, m, Size(24, 24)); //转成灰度
cvtColor(m, m, COLOR_BGR2GRAY); char p[100];
sprintf(p, "D:/Lance/ndk/lsn27_opencv_face/资料/img/info/%d.jpg",i++); 
//把mat写出为jpg文件
imwrite(p,m);
}

LBP

LBP(Local Binary Pattern,局部二值模式)是一种用来描述图像局部纹理特征的算子,具有多分辨率、灰度
尺度不变、旋转不变等特性。主要用于特征提取中的纹理提取。
使用LBP作为人脸检测的特征提取方式具有:计算量小;存储空间小;计算过程简单,没有复杂的除法和特 殊运算,便于硬件实现;检测的时间短,检测的实时性好。

LBP的核心思想就是:以中心像素的灰度值作为阈值,与他的领域相比较得到相对应的二进制码来表示局部纹理特 征。

LBP 核心原理.png

基本LBP

原始的LBP算子定义为在3*3的窗口内,处理83这个像素点的lbp值:
将83与包围83的8个位置进行比较。如果大于83则取值为1,否则为0,然后将这些数据顺时针组合在一起,这样的 到一个01111100,即:0x7C(124)。这就是83位置的lbp值。顺序并无硬性要求,只要在同一处理中保持相同的顺序 即可。提取的LBP算子在每个像素点都可以得到一个LBP值,对一幅图像提取其原始的LBP算子之后,得到的原始 LBP特征依然是“一幅图片”(记录的是每个像素点的LBP值)这种图片称之为lbp图谱。

/**
* 原始lbp:应该是3x3的src(先不管3x3)
* src: 原图
* dst: 计算出的lbp图谱 *
*/
void processLBP(Mat src, Mat &dst) { // 循环处理图像数据
    for (int i = 1; i < src.rows - 1; i++) {
        for (int j = 1; j < src.cols - 1; j++) {
            uchar lbp = 0;

            uchar center = src.at<uchar>(i, j);
//取出对应 高、宽位置的像素 与 中心点位置进行比较 
            if (src.at<uchar>(i - 1, j - 1) > center) { lbp += 1 << 7; }
            if (src.at<uchar>(i - 1, j) > center) { lbp += 1 << 6; }
            if (src.at<uchar>(i - 1, j + 1) > center) { lbp += 1 << 5; }
            if (src.at<uchar>(i, j + 1) > center) { lbp += 1 << 4; }
            if (src.at<uchar>(i + 1, j + 1) > center) { lbp += 1 << 3; }
            if (src.at<uchar>(i + 1, j) > center) { lbp += 1 << 2; }
            if (src.at<uchar>(i + 1, j - 1) > center) { lbp += 1 << 1; }
            if (src.at<uchar>(i, j - 1) > center) { lbp += 1 << 0; }
            dst.at<uchar>(i - 1, j - 1) = lbp;
        }
    }
}

//读取一张图片
Mat img = imread("/path/x.png");
cvtColor(img, img, COLOR_BGR2GRAY
); //计算lbp图谱
Mat lbp = Mat(img.rows - 2, img.cols - 2, CV_8UC1);
processLBP(img, lbp
);


从lBP定义可以看出LBP是灰度不变的,简单来说就是对图像的灰度值根据一个系数X进行修改,得出的LBP值不 变。

圆形LBP

基本的 LBP算子的最大缺陷在于它只覆盖了一个固定3x3范围内的小区域,为了满足不同尺寸的需要,并达到灰度 和旋转不变性的要求,对 LBP 算子进行了改进,将 3×3邻域扩展到任意邻域,并用圆形邻域代替了正方形邻域。以 某个像素点为中心,以一个任意大小半径R画一个圆,将落在圆内的P个像素与中心点像素比较得到LBP算子。

圆形LBP.png

旋转不变LBP

LBP 算子是灰度不变的,但却不是旋转不变的,图像的旋转就会得到不同的 LBP值。

旋转不变LBP.png

为了解决这个问题,将 LBP算子进行了扩展具有旋转不变性。LBP的旋转不变模式,即不断旋转圆形邻域得到一系 列初始定义的 LBP值,取其最小值作为该邻域的 LBP 值。

2020-11-10 18.17.44.png

首先获得LBP值为: 11100001 (255),进过旋转分别得到8中不同的LBP值,最终得到的具有旋转不变性的 LBP值为 15。

等价LBP

对于半径为R的圆形区域内含有P个采样点的LBP算子将会产生2^P(0和1的排列组合)种模式。随着邻域集内采样点 数的增加,二进制模式的种类是以指数形式增加的。这么多的二进制模式导致在人脸检测时候LBP模式统计直方图 过于稀疏(见下面检测原理部分)。因此需要对原始的LBP模式进行降维,也就是减少数据量。
等价模式(均匀模式)就是解决这个问题的。在实际图像中,绝大多数LBP模式最多只包含两次从1到0或从0到1的跳 变。因此,等价模式定义为:当某个LBP所对应的循环二进制数从0到1或从1到0最多有两次跳变时,该LBP所对应 的二进制就称为一个等价模式类,除等价模式类以外的模式都归为另一类,称为混合模式类。
00000000(0次跳变),00000111(1次从0到1的跳变),10001111(1到0,0到1,两次跳变)是等价模式 类。
10010111(共四次跳变)是混合模式类。

通过这样的改进,二进制模式数量由原来的2^P种减少为 P* ( P-1)+2种。

比如:3x3的8采样本来有256种,现在变成58(等价模式)+1种(混合模式)。即本来lbp值为0-255,也就是256
种结果,转化为了59种。混合模式编码为0,等价模式根据值大小编码为1—58。
00000000 : 1
00000001 : 2
00000010 : 3
00000101 : 0 (跳变3次)
58种情况如下:
0次跳变:11111111 00000000
1次跳变(14个): 01111111 00111111 00011111 00001111 00000111
00000011 00000001 2次跳变(42个)
1:
01000000 00100000 00010000 00001000 00000100 00000010 2:
01100000 00110000 00011000 00001100 00000110

3:
01110000 00111000 00011100 00001110
4:
01111000 00111100 00011110
4:
01111100 00111110
6:
01111110

检测原理

将一幅图片划分为若干的子区域,对每个子区域内的每个像素点都提取LBP特征,然后,在每个子区域内建立 LBPH(LBP特征的统计直方图)。 每个子区域就可以用一个统计直方图来进行描述;整个图片就由若干个统计直 方图组成 之后,将图片和人脸的直方图进行相似性比较。

直方图:
把图片的亮度分为0到255共256个数值,数值越大,代表的亮度越高。其中0代表纯黑色的最暗区域,255表 示最亮的纯白色,而中间的数字就是不同亮度的灰色。用横轴代表0-255的亮度数值。竖轴代表照片中对应亮 度的像素数量,这个函数图像就被称为直方图。
简单来说,图像的直方图是用来表现图像中亮度分布的情况,给出的是图像中某个亮度或者某个范围亮度下共 有几个像素

直方图.png

人脸识别

OpenCV提供了 cv::face::FaceRecognizer 帮助我们进行人脸识别,但是它位于 opencv_contrib 模块中,因此
如果需要使用,那么需要将Opencv与opencv_contrib共同编译。 下载:https://github.com/opencv/opencv_contrib

在编译OpenCV时,搜索: OPENCV_EXTRA_MODULES_PATH 并配置 contrib的解压目录

在配置阶段可能因为某些依赖文件下载失败。根据提示手动下载或者挂上代理重 试即可。
编译完成之后,即可使用:
可参考:https://www.cnblogs.com/llguanli/p/7286615.html

知识点

智能指针,A对象不用我们手动释放

class A{

};

class MyPtr{
public:
    MyPtr(A *a) ;

    virtual ~MyPtr() {
        delete a;
    }
private:
    A *a;
};

int main() {
    ...

    MyPtr ptr(new A());
    return 0;
}

类和结构体最主要的区别

类变量的作用域,是默认私有的成员
结构体的变量作用于,是公有的

所以我们可以用结构体封装一些,带有锁的功能,降低锁的作用域

相关文章

网友评论

    本文标题:OpenCV(一)人脸定位&人脸定位模型训练

    本文链接:https://www.haomeiwen.com/subject/lhzlbktx.html