人脸识别追踪
-
OpenCV是一个开源发行的跨平台计算机视觉库。
-
人脸识别使用到了OpenCV里面的
Objdetect
模块,目标检测模块,如:人脸检测等。 -
人脸检测原理:
LBP
LBP(Local Binary Pattern,局部二值模式)是一种用来描述图像局部纹理特征的算子,具有多分辨率、灰度尺度不变、旋转不变等特性。主要用于特征提取中的纹理提取。
基本LBP
基本LBP
将83与包围83的8个位置进行比较。如果大于83则取值为1,否则为0,然后将这些数据顺时针组合在一起,这样的到一个01111100,即:0x7C(124)。这就是83位置的lbp值。
检测原理
将一幅图片划分为若干的子区域,对每个子区域内的每个像素点都提取LBP特征,然后,在每个子区域内建立LBPH(LBP特征的统计直方图)。 每个子区域就可以用一个统计直方图来进行描述;整个图片就由若干个统计直方图组成 之后,将图片和人脸的直方图进行相似性比较。
用横轴表示LBP值0到255,用竖轴表示区域中对应LBP数值的像素数量,这个函数图像称为直方图。
LBPH是用来表现图像中LBP分布的情况。
LBPH示意 -
android中实现
Java层调用Camera接口进行离屏渲染,在回调接口中获取摄像头数据data[ ],提供给Native层备用。
Java层准备SurfaceView,将Surface转递给Native层备用。
Native层接收Surface,使用ANativeWindow
API获取到window对象。
Native层接收到Java层传来的data[ ]后:
- 开辟一个OpenCV的
Mat
//1、高 2、宽,rows表示高,cols表示宽
Mat src(h + h / 2, w, CV_8UC1, data);
因为data[ ]是NV21格式的YUV420数据,
NV21
数据实际高度是h*3/2。
- 将 nv21的yuv数据转成了rgba
//将 nv21的yuv数据转成了rgba
cvtColor(src, src, COLOR_YUV2RGBA_NV21);
- 根据摄像头的前后,进行旋转、水平翻转处理
if (cameraId == 1) {
//前置摄像头,需要逆时针旋转90度
rotate(src, src, ROTATE_90_COUNTERCLOCKWISE);
//水平翻转 镜像
flip(src, src, 1);
} else {
//顺时针旋转90度
rotate(src, src, ROTATE_90_CLOCKWISE);
}
- 灰度处理、对比度增强
//灰色
cvtColor(src, gray, COLOR_RGBA2GRAY);
//增强对比度 (直方图均衡)
equalizeHist(gray, gray);
- 加载分类器、跟踪器
分类器的加载需要传入模型文件地址,分类器可以定位出人脸区域。跟踪器是对分类器的优化,在视频中可以根据上一次分类器的定位结果优化下一次定位的运算过程。 - 跟踪器运行,得到定位区域列表,在原图中绘制人脸范围矩形框
//定位人脸 N个
tracker->process(gray);
tracker->getObjects(faces);
for (Rect face : faces) {
//画矩形
//分别指定 bgra
rectangle(src, face, Scalar(255, 0, 255));
}
- 调用
ANativeWindow
API将Mat src
绘制到window中
//显示
if (window) {
//设置windows的属性
// 因为旋转了 所以宽、高需要交换
//这里使用 cols 和rows 代表 宽、高 就不用关心上面是否旋转了
ANativeWindow_setBuffersGeometry(window, src.cols, src.rows, WINDOW_FORMAT_RGBA_8888);
ANativeWindow_Buffer buffer;
do {
//lock失败 直接brek出去
if (ANativeWindow_lock(window, &buffer, 0)) {
ANativeWindow_release(window);
window = 0;
break;
}
//src.data : rgba的数据
//把src.data 拷贝到 buffer.bits 里去
// 一行一行的拷贝
uint8_t *dst_data = static_cast<uint8_t *>(buffer.bits);
int dst_linesize = buffer.stride * 4;
uint8_t *src_data = src.data;
int src_linesize = src.cols * 4;
for (int i = 0; i < src.rows; ++i) {
memcpy(dst_data + i * dst_linesize, src_data + i * src_linesize, dst_linesize);
}
//提交刷新
ANativeWindow_unlockAndPost(window);
} while (0);
}
车牌识别
第一步 车牌的定位
有两种方式:
- 利用车牌加文体边缘区域丰富的特性,用
Sobel
进行边缘检测,分离出车牌区域。 - 利用车牌蓝色底色的特性,用HSV原理分离出车牌区域。
Sobel过程分析
- 高斯模糊(平滑、降噪 )。
- 灰度化 去掉颜色 因为它对于我们这里没用 降噪。
- 边缘检测 让车牌更加突出 在调用时需要以16位来保存数据 在后续操作 以及显示的时候需要转回8位。
- 二值化 黑白,采用了 大律法 最大类间算法。
- 闭操作,将相邻的白色区域扩大 连接成一个整体。
- 轮廓检测
findContours
,将结果变成点序列放入 集合。 - 利用
minAreaRect
将 点序列 得到最小包裹的 有角度矩形RotatedRect
。 - 进行初步的筛选 把完全不符合的轮廓给排除掉 ( 比如:1x1,5x1000 )。
- 矫正,因为可能斜的,处理扭曲。
HSV过程分析(颜色模型,H色调、S饱和度、V明亮度)
- 原Mat转换成HSV格式的Mat hsv。
-
Mat hsv的数据在内存中一行是按照h、s、v连续分布的,所以需要遍历每一行,得到具体的h、s、v数值,比较hsv分布表:
hsv.png
,判断一组h、s、v是否在蓝色范围内,把蓝色像素 凸显出来 ,其他的区域全变成黑色:
if (h >= 100 && h <= 124 && s >= 43 && s <= 255 && v >= 46 && v <= 255) {
p[j] = 0;
p[j + 1] = 0;
p[j + 2] = 255;
} else {
p[j] = 0;
p[j + 1] = 0;
p[j + 2] = 0;
}
- 把Mat hsv的h、s、v分离出来
vector<Mat> hsv_split;
split(hsv, hsv_split);
Mat target = hsv_split[2];//凸显出来的图像
- 二值化 黑白,采用了 大律法 最大类间算法。
- 闭操作,将相邻的白色区域扩大 连接成一个整体。
- 轮廓检测
findContours
,将结果变成点序列放入 集合。 - 利用
minAreaRect
将 点序列 得到最小包裹的 有角度矩形RotatedRect
。 - 进行初步的筛选 把完全不符合的轮廓给排除掉 ( 比如:1x1,5x1000 )。
- 矫正,因为可能斜的,处理扭曲。
使用SVM测评Sobel和HSV定位的结果
- 灰度化 去掉颜色 因为它对于我们这里没用 降噪。
- 二值化 黑白,采用了 大律法 最大类间算法。
- 提取特征,把特征置为一行。
- 调用svm测评特征,得到评分。
- 评分最小,即为最优定位结果。
SVM测评的模型需要大量的正样本进行训练:
image.png
SVM介绍
svm,英文全称为 Support Vector Machine,中文名叫支持向量机
,是机器学习最为经典的分类方法之一。
SVM是一种线性分类器,分类的对象要求是线性可分。因此我们首先要了解什么是线性可分与线性不可分。
假如在课桌“三八线”的两旁分别放了一堆苹果和一堆荔枝,通过“三八线”这样一条直线就能把苹果和荔枝这两种类别的水果分开了(如左下图),这种情况就是线性可分的。但是如果苹果和荔枝的放置位置是苹果包围荔枝的局面(如右下图),就无法通过一条直线将它们分开(即这样的直线是不存在的),这种情况则是线性不可分的情形。
因此,只有当样本数据是线性可分的,才能找到一条线性分割线或分割面等,SVM分类才能成立。假如样本特征数据是线性不可分的,则这样的线性分割线或分割面是根本不存在的,SVM分类也就无法实现。
在二维的平面课桌上,一条直线就足以将桌面一分为二。但如果扩展到三维空间中,则需要一个平面(比如一面墙、一扇屏风等)才能将立体空间区域一分为二。
如何处理线性不可分?
在前面苹果和荔枝的例子当中,我们已经了解到 SVM 要求样本数据是线性可分的,这样才会存在分类超平面。而如果样本数据是非线性的情况,那将如何处理呢?SVM的解决办法就是先将数据变成线性可分的,再构造出最优分类超平面。
SVM 通过选择一个核函数 K ,将低维非线性数据映射到高维空间中。原始空间中的非线性数据经过核函数映射转换后,在高维空间中变成线性可分的数据,从而可以构造出最优分类超平面。
如下图所示:原始样本数据在二维空间里无法线性分割,经过核函数映射到三维空间中则可构造出分类超平面进行二类划分。
image.png
举个形象的例子:一把瓜子和一把钢珠混在一起铺在桌面,它们在二维空间是线性不可分的。如果用手在桌面上用力一拍,瓜子会弹得更高,在三维空间,瓜子和钢珠就变成了线性可分的了。
第二步 车牌的识别
在第一步中定位到了车牌整体的轮廓,还要继续把车牌中的字符Mat抠出来,把每一个字符Mat识别对应的文字。
- 如何抠字符?
第一步定位后的的车牌先转灰度图,然后二值化
,得到了 image.png
可以发现底部有一些干扰(固定螺丝),可以逐行读取Mat,分析黑白跳变的次数,如果次数较少就可以把这一行的数据全部转为黑色,得到下图:
image.png
接下来查找轮廓findContours
得到轮廓的Poin集合,再用boundingRect
包裹Pont集合得到若干个矩形Rect
,按照宽高情况可以筛除一下不是字符的Rect,最终得到字符的Rect集合,绘制到原图中这个样子:
然后可以在整个车牌图片宽度的2/7左右出找到城市代号字母,响应的它的左边就是省份的简称汉子。可以把这些字符分为两类:汉字+字母or数字,接下来把抠出来的这些文字图片识别即可。
- 如何识别字符?
刚抠出来的字符Mat是二值化后的,接下来: - 提取特征,把特征置为一行。
-
调用ANN_MLP测评特征。汉字由于是省份的简称数量有限,因此训练模型时可以将汉字作为单独一个模型,每个省份简称汉字单独一个文件夹存放:
image.png
image.png
这样训练出来的模型测评就会返回最匹配的文件下下标,我们就可以知道对应的汉字是什么了。
同理,可以把0-9 和A-Z这些字符在一个模型中测评。
image.png
image.png
- 所有的字符抠图经过测评后,就可以拼接成最终的车牌号了。
网友评论