AR学习笔记:配准特征点的选择
Posted Sakurazzy
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了AR学习笔记:配准特征点的选择相关的知识,希望对你有一定的参考价值。
AR学习笔记(三):配准特征点的选择
课题需要选择合适的特征点,计算相机的位姿变换矩阵,实现2D-3D配准的功能,编程语言主要采用C++,部分通过python验证,下面记录一下自己的思路
特征点选择思路
因为对3D模型特征点检测不熟悉,我主要还是在2D图像上选可自动检测的特征点,然后在3D模型上对应地选取
- 用dlib库识别唇部轮廓线,选择唇部内侧特征点,再确定牙模上的对应点
- 用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库人脸检测的项目
- Face_Recognition_dlib (python)
- Dlib_face_recognition_from_camera (python)
- head-pose-estimation (c++ python)
dilb对于侧脸的检测效果不好,这里有一个DAN方法(人脸对齐)项目:DeepAlignmentNetwork
特征点选择测试
这几天测试流程的安排:(粗体为已测试)
- 测试dlib库,检测到唇部轮廓点并显示,制作为视频
- 利用opencv库以及边缘检测的方法,提取牙齿的轮廓,检测角点
- 利用opencv库以及颜色分割的方法,分割牙齿的图像,检测角点
- 利用opencv库以及矩形轮廓检测的方法,框出牙齿的位置,进而获取特征点
- 在4的基础上计算牙齿轮廓的矩信息,实现质心的提取(牙齿质心应该在一条抛物线上)
- 深度学习的方法
根据唇部轮廓线选择特征点
先通过dlib库识别唇部的特征点,包含内侧唇部轮廓和外侧唇部轮廓,以及嘴角点
通过IvoSmile APP猜了一下它用的方法,主要有两个功能:
- 美白,我认为它使用dlib先识别了唇部轮廓线,定位到了唇部位置,然后识别了牙齿(可能是颜色分割)进行美白,我测试了它的实时美白功能,在偏向一侧角度稍大时(大概45度)就会识别不出来,这种情况在我测试dlib人脸识别时也出现过;
- 牙齿修复,首先应该还是用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上类似的项目,大部分都是检测牙齿,而不是提取特征点
- TeethDectcotor:基于TensorFlow检测牙齿 (python)
- TeethCV:Incisor Segmentation project. The implementation is using Active Shape Model approach to identify teeth in radiograph image (python)
- ToothFeaturePoints:An algorithm to get feature points from a mesh model (python)
- ToothDetectionAndColorMatch:色卡检测 (c++)
问题
- 目前的测试都是基于dlib检测出嘴部区域,再提取特征点,但是dlib的侧脸识别效果不好,比如素材中90度侧脸图片不能识别出来
其他的人脸识别方法? - 怎样把2D牙齿图像的特征点自动与3D模型对应起来(就是怎么判断提取的特征点对应的牙模位置)
- 怎么自动且准确地把每颗牙齿分割出来(在颜色分割中,阈值如何进行自动的调整)
以上是关于AR学习笔记:配准特征点的选择的主要内容,如果未能解决你的问题,请参考以下文章