AR学习笔记:配准特征点的选择

Posted Sakurazzy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了AR学习笔记:配准特征点的选择相关的知识,希望对你有一定的参考价值。

课题需要选择合适的特征点,计算相机的位姿变换矩阵,实现2D-3D配准的功能,编程语言主要采用C++,部分通过python验证,下面记录一下自己的思路


特征点选择思路

因为对3D模型特征点检测不熟悉,我主要还是在2D图像上选可自动检测的特征点,然后在3D模型上对应地选取

  1. 用dlib库识别唇部轮廓线,选择唇部内侧特征点,再确定牙模上的对应点
  2. 用opencv库进行图像处理,先分割出牙齿区域,再提取牙齿的边缘轮廓,最后提取牙齿的特征点
    我了解的特征点提取方法包括SIFT、SURF、ORB等,但常用在多帧2D图像的匹配跟踪上,在本项目里需要提取便于在牙模上直接确定的点,所以不适合
    我的思路是在提取到牙齿区域和轮廓的基础上,选择固定几颗牙齿的角点或者质心作为特征点,这样在牙模上的特征点也可以固定下来,角点提取的方法有harris角点、Shi-Tomasi角点等,质心计算则是提取canny边缘后找出每颗牙齿的轮廓,进而计算矩得到质心,或是提取最小外接矩形,取矩形中心点

用到的库

OPENCV

我用的opencv库是当前最新版的4.5.3,过程在上一篇博客

DLIB

官方文档:http://dlib.net/

Dlib is a modern C++ toolkit containing machine learning algorithms and tools for creating complex software in C++ to solve real world problems. It is used in both industry and academia in a wide range of domains including robotics, embedded devices, mobile phones, and large high performance computing environments.

Dlib是一个现代化的C ++工具箱,其中包含用于在C ++中创建复杂软件以解决实际问题的机器学习算法和工具,这里用来实现唇部的识别,版本是19.22

安装步骤:https://www.dazhuanlan.com/cbwintering/topics/1223874

$ mkdir build
$ cd build
$ cmake .. -DDLIB_USE_CUDA=0
$ cmake --build . --config Release
$ sudo make install

在github上找了几个dlib库人脸检测的项目

  1. Face_Recognition_dlib (python)
  2. Dlib_face_recognition_from_camera (python)
  3. head-pose-estimation (c++ python)

dilb对于侧脸的检测效果不好,这里有一个DAN方法(人脸对齐)项目:DeepAlignmentNetwork


特征点选择测试

这几天测试流程的安排:(粗体为已测试)

  1. 测试dlib库,检测到唇部轮廓点并显示,制作为视频
  2. 利用opencv库以及边缘检测的方法,提取牙齿的轮廓,检测角点
  3. 利用opencv库以及颜色分割的方法,分割牙齿的图像,检测角点
  4. 利用opencv库以及矩形轮廓检测的方法,框出牙齿的位置,进而获取特征点
  5. 在4的基础上计算牙齿轮廓的矩信息,实现质心的提取(牙齿质心应该在一条抛物线上)
  6. 深度学习的方法

根据唇部轮廓线选择特征点

先通过dlib库识别唇部的特征点,包含内侧唇部轮廓和外侧唇部轮廓,以及嘴角点

通过IvoSmile APP猜了一下它用的方法,主要有两个功能:

  1. 美白,我认为它使用dlib先识别了唇部轮廓线,定位到了唇部位置,然后识别了牙齿(可能是颜色分割)进行美白,我测试了它的实时美白功能,在偏向一侧角度稍大时(大概45度)就会识别不出来,这种情况在我测试dlib人脸识别时也出现过;
  2. 牙齿修复,首先应该还是用dlib识别唇部轮廓线,特征点应该是内部轮廓线对称的几个点,默认的牙模特征点应该是牙齿顶部的点,进行了配准。在测试过程中,首先是需要拍一张正面的照片,用于确定中心点便于特征点的统一,然后拍摄一段图片序列,自动将3D牙模投影到了2D图像上。
    PS:我在拍摄的过程中只露出一半牙齿,而牙模投影时是整颗牙齿,如下图所示,左侧为原始牙齿,右侧为修复投影的牙齿(说明2D图像特征点选取不在牙齿的下端)
    后续还要进行手动的调整,才准确完成了2D-3D配准,此外,在直播模式中,同样侧过45度左右就会失效。

如果要直接完成配准不进行手动调节,那么特征点必须选取在牙齿上,如果仍选择唇部特征点,那么对于照片的拍摄会有更高的要求(尽量露出整颗牙齿,即唇部内轮廓线刚好在牙齿的顶部)


DLIB唇部识别测试

参考python实现:https://blog.csdn.net/AAliuxiaolei/article/details/107821085
在DLIB中面部识别了68个点,取49-68号点即可取出唇部特征点,建立工程dlib_test,程序为mouth_detect.py和video_dlib.py,结果放在了results文件夹中,整体效果比较好,在侧脸角度比较大的时候还是会出现检测不到人脸的情况,计算速度有点慢

下面是导出视频的一张截图


后面写了一份c++版本的代码配合opencv,用来提取唇部的图像,便于后续的牙齿分割,下面是部分代码

#include <dlib/image_processing/frontal_face_detector.h>
#include <dlib/image_processing/render_face_detections.h>
#include <dlib/image_processing.h>
#include <dlib/gui_widgets.h>
#include <dlib/image_io.h>
#include <dlib/opencv.h>

#include <iostream>
#include <vector>
#include <ctime>
 
using namespace std;
using namespace cv;

void get_mouth_points(Mat img, std::vector<dlib::full_object_detection> fs) //提取唇部特征点

    int i, j;
    for(j=0; j<fs.size(); j++)
    
        Point p1, p2;
        for(i = 48; i<59; i++)
        
            //嘴唇外圈  48 ~ 59
            //嘴唇内圈  59 ~ 67
            switch(i)
            
                case 16:
                case 21:
                case 26:
                case 30:
                case 35:
                case 41:
                case 47:
                case 59:
                    i++;
                    break;
                default:
                    break;
            
            p1.x = fs[j].part(i).x();
            p1.y = fs[j].part(i).y();
            p2.x = fs[j].part(i+1).x();
            p2.y = fs[j].part(i+1).y();
            //cv::line(img, p1, p2, cv::Scalar(0,0,255), 2, 4, 0);
            if(i == 58)
                mask_points.push_back(p2);
            else
                mask_points.push_back(p1);
        
    


int main()

	Mat frame = cv::imread("front.jpg");
    Mat dst;
    // 减小图像尺寸 1200*800
    Mat img_resize = Mat::zeros(1200,800 , CV_8UC3); 
    resize(frame, frame, img_resize.size());
    //提取灰度图
    cvtColor(frame, dst, CV_BGR2GRAY);
    //加载dlib的人脸识别器
    dlib::frontal_face_detector detector = dlib::get_frontal_face_detector();
    //加载人脸形状探测器
    dlib::shape_predictor sp;
    dlib::deserialize("./shape_predictor_68_face_landmarks.dat") >> sp;
    //Mat转化为dlib的matrix
    dlib::array2d<dlib::bgr_pixel> dimg;
    dlib::assign_image(dimg, dlib::cv_image<uchar>(dst)); 
    //获取一系列人脸所在区域
    std::vector<dlib::rectangle> dets = detector(dimg);
    std::cout << "Number of faces detected: " << dets.size() << std::endl;
    if (dets.size() == 0)
        return 0;
    //获取人脸特征点分布
    std::vector<dlib::full_object_detection> shapes;
    int i = 0;
    for(i = 0; i < dets.size(); i++)
    
        dlib::full_object_detection shape = sp(dimg, dets[i]); //获取指定一个区域的人脸形状
        shapes.push_back(shape); 
       
    //获取唇部特征点集
    get_mouth_points(frame, shapes);

得到的结果如下,自动分割出唇部区域图像


根据牙齿的角点选择特征点

只利用opencv来进行图像分割的话,会有很多噪声干扰,我觉得先定位到嘴的区域比较方便处理,然后采用边缘提取或者颜色分割的方法,将牙齿分割出来,然后计算角点作为特征点,想象中角点应该在牙齿的端点(需要测试),对应的牙模取点也可以选取在牙齿(类似矩形)的端点。

边缘提取测试

思路:假设已经提取到了嘴部的区域(通过上面的方法可以做到),设置了ROI,然后进行gamma矫正和高斯滤波,平滑掉细小噪声(尤其是牙齿的高光),通过形态学处理中的开运算分开每颗牙齿,最后进行canny算子提取边缘
结果:原图/开运算处理/Canny边缘结果如下图所示,牙齿能够分离出来,对于侧脸效果应该也类似,但是还是存在不想要的边缘,阈值设置起来也比较麻烦

能否先对第一帧提取特征点,然后通过SIFT/SURF这些方法进行后续图像的特征点匹配来跟踪?


测试代码(部分):

#include "opencv2/opencv.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <opencv2/imgproc.hpp>
#include "opencv2/imgproc/imgproc_c.h"
#include <iostream>

using namespace cv;
using namespace std;

int main()

	Mat img, img_gray, img_blur, img_canny, bw;
	int edgeThresh = 1;
	int lowThreshold = 20;
	int ratio = 2;
	int kernel_size = 3;
	
	img = imread("front.jpg", 1);
	// 减小图像尺寸 1200*800
    Mat img_resize = Mat::zeros(1200,800 , CV_8UC3); 
    resize(img, img_resize, img_resize.size());
	// 设置ROI取出嘴部图像
	Rect myROI(320, 680, 200, 100);
	Mat mask = Mat::zeros(img_resize.size(), CV_8UC1);
	mask(myROI).setTo(255);
	Mat img_ROI;
	img_resize.copyTo(img_ROI, mask);
	imshow("img_ROI", img_ROI);
	waitKey(0);
	// 图像预处理
	cvtColor(img_ROI,img_gray,CV_BGR2GRAY);
	//直方图均衡化
	/*equalizeHist(img_gray, img_gray);
	imshow("img_equalhist", img_gray);
	waitKey(0);*/
    // gamma矫正
	MyGammaCorrection(img_gray,  img_gray, 2);
    // 高斯平滑
	GaussianBlur(img_gray, img_blur,  Size(5,5), 0, 0);
	// 腐蚀膨胀去除牙齿的细小纹路
	bw=(img_blur>75);
	Mat	Structure0=getStructuringElement(MORPH_RECT,Size(5,5));
    erode(bw,bw,Structure0,Point(-1,-1));
	Mat	Structure1=getStructuringElement(MORPH_RECT,Size(3,3));
    dilate(bw,bw,Structure1, Point(-1,-1));
	imshow("img_ed", bw);
	waitKey(0);
	// canny边缘提取
	Canny( bw, img_canny, lowThreshold, lowThreshold*ratio, kernel_size );
	imshow("img_canny", img_canny);
	waitKey(0);

	return 0;

后面测试了一下harris角点检测,参数没太细调,效果很差,牙齿本身不太规则,角点检测出来很难具有代表性,感觉用角点检测不太稳定,结果如下图

在用dilb提取唇部区域后再进行上述操作,得到结果如下


颜色分割测试

参考【opencv学习之二十九】彩色分割 写了一个HSV颜色分割的程序
同样先把嘴部区域取出来,单独进行颜色分割,调整了一下阈值,把牙齿的部分提取出来,结果如下


做了一下canny边缘提取和harris角点检测,结果如下

相对直接进行边缘提取,把牙齿外的图像滤除掉了,但是角点提取的效果还是不好,不够稳定


轮廓检测提取特征点

直接计算harris角点的方法不太好,打算先把每颗牙齿的外接最小矩形找出来,然后取矩形中心点作为特征点,这里用到的函数是findContours()和minAreaRect(),结果画在原图上,部分代码如下

	Mat image = bw.clone();
	vector<vector<Point>> contours;
	vector<Vec4i> hierarchy;
	findContours(image, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_NONE, Point());
 
	for (int i = 0; i < contours.size(); i++)
	
         /*
		//获取区域的面积,如果小于某个值就忽略
		double area_1 = contourArea(contours[i]);
		cout << area_1 << endl;
		if (area_1 < 40) 
			continue;
        */
		//绘制轮廓的最小外接矩形
		RotatedRect rect = minAreaRect(contours[i]);
		//绘制最小外接矩形的中心点
		circle(img_ROI, Point(rect.center.x, rect.center.y), 2, Scalar(0, 255, 0), -1, 8);  
		//绘制最小外接矩形
		Point2f P[4];
		rect.points(P);
		vector<int> X_Contours;
		for (int j = 0; j <= 3; j++)
		
			X_Contours.push_back(P[j].x);
			X_Contours.push_back(P[(j + 1) % 4].x);
			line(img_ROI, P[j], P[(j + 1) % 4], Scalar(255, 255, 255), 2);
		
	
	imshow("contours", img_ROI);
	waitKey(0);

提取结果如下图,中心点基本都在牙齿的中心位置,受形态学处理影响比较大,结果可能会有点偏差,另外在提取边缘时必须把每颗牙齿分开,否则不能正确提取特征点

取第二张图片12clock进行测试结果如下

dlib库提取唇部区域后再进行上述操作得到结果如下


根据牙齿的质心选择特征点

记得上课的时候提到过图像的矩,这种描述子具有一定的尺度、旋转不变性,所以测试了一下,根据canny边缘提取得到的图像,用findContours()得到每颗牙齿的轮廓,然后调用moments计算图像的质心,参考了opencv11-计算不规则图像的质心,部分代码如下:

	vector<vector<Point>> contours;
	vector<Vec4i> hierarchy;
	findContours(image, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_NONE, Point());
	
	//计算轮廓矩       
	vector<Moments> mu(contours.size() );       
	for( int i = 0; i < contours.size(); i++ )     
	   
		mu[i] = moments( contours[i], false );   
	     
	
	//计算轮廓的质心     
	vector<Point2f> mc( contours.size() );      
	for( int i = 0; i < contours.size(); i++ )     
	   
		mc[i] = Point2d( mu[i].m10/mu[i].m00 , mu[i].m01/mu[i].m00 );   
	     

	for (int i = 0; i < contours.size(); i++)
	
		//绘制质心
		circle( img_ROI, mc[i], 2, Scalar( 255, 0, 255), -1, 8, 0 );  
		//char tam[100];   
		//sprintf(tam, "(%0.0f,%0.0f)",mc[i].x,mc[i].y);   
		//putText(img_ROI, tam, Point(mc[i].x, mc[i].y), FONT_HERSHEY_SIMPLEX, 0.4, cvScalar(255,0,255),1); 
	

	imshow("contours", img_ROI);
	waitKey(0);

得到的结果如下


取第二张图片12clock测试结果如下,有偏差应该是与光照不均和阈值设置有关,上嘴唇的反光太亮了,和牙齿几乎是一个颜色


dlib库提取唇部区域后再进行上述操作得到结果如下

ps.还有一个看起来比较有效的方法,但是博客没有给出具体的程序,所以还没有尝试

参考博客:https://www.jishufanyi.cn/222005.html 把牙齿类似成圆形来检测

1.对图像进行二值化处理,可以使用sklearn.preprocessing.binarize
2.计算所有非零像素的质心。 这是图像中的中心蓝色圆圈。 称这个为structure_centroid
3.以structure_centroid的位置为中心,制作整个图像的极片,参见: polarTransform库
4.为这些极片中的每​​一个确定非零像素的质心的位置。找到点和曲线python之间的距离/找到一点到曲线的最小距离
5.包含这些质心的数组可提供牙齿平均位置的轨迹(路径)centroid_path
6.在能够检测到的,最接近centroid_path的圆上运行消除/选择算法,使用阈值距离降低异常值。


基于深度学习的方法

找了几个github上类似的项目,大部分都是检测牙齿,而不是提取特征点

  1. TeethDectcotor:基于TensorFlow检测牙齿 (python)
  2. TeethCV:Incisor Segmentation project. The implementation is using Active Shape Model approach to identify teeth in radiograph image (python)
  3. ToothFeaturePoints:An algorithm to get feature points from a mesh model (python)
  4. ToothDetectionAndColorMatch:色卡检测 (c++)

问题

  1. 目前的测试都是基于dlib检测出嘴部区域,再提取特征点,但是dlib的侧脸识别效果不好,比如素材中90度侧脸图片不能识别出来
    其他的人脸识别方法?
  2. 怎样把2D牙齿图像的特征点自动与3D模型对应起来(就是怎么判断提取的特征点对应的牙模位置)
  3. 怎么自动且准确地把每颗牙齿分割出来(在颜色分割中,阈值如何进行自动的调整)

以上是关于AR学习笔记:配准特征点的选择的主要内容,如果未能解决你的问题,请参考以下文章

点云配准 7-特征描述子

点云配准 7-特征描述子

AR学习笔记:边缘分割优化和提取特征点

AR学习笔记:边缘分割优化和提取特征点

AR学习笔记:边缘分割优化和提取特征点

AR学习笔记:边缘分割优化和提取特征点