
js_john
完成了上一节的工作,我们可以获取到摄像头的每一帧图像。为了使我们的程序对图片有更好的兼容性,不对摄像头的参数设置作过多要求。所有的图像质量检测与缩放,增强操作均通过 OpenCV 完成。
要开发的是一个 1:N 的人脸识别应用,实现的人脸识别的总体思路是
0、完成基础性工作,引入主要的依赖包
1、通过 iOS 内置的摄像头捕获画面
2、将每一帧的图像传递给 OpenCV 做缩放和增强处理
3、处理后的图像交给 Vision 检测人脸
4、使用 Dlib 提取人脸的特征值,获得特征向量
5、建立用于保存人脸信息的数据库
6、从摄像头获取图像提取特征向量,逐一与数据库中的特征向量计算距离
7、若距离足够接近,输出本次识别结果,否则输出未识别
本节进度是:2、将每一帧的图像传递给 OpenCV 做缩放和增强处理
所有的需要检测的图像首先会完成一次基础的质量检测,检测维度包括图像尺寸与清晰度两项。只有通过质量检测的图像才会被交由 Dlib 完成人脸检测与特征提取。
图像质量检测主要包括以下两个维度:
图像尺寸指标
图像清晰度指标
在一切开始前,我们需要知道一些基础概念。iOS 使用的图像类型通常是 UIImage,OpenCV 处理的图像是 cv::Mat 类型。为了能让 OpenCV 可以处理图像,首先我们必须将 UIImage 转换成 cv::Mat。
OpenCV 提供了转换函数供我们使用,只需 import imgcodecs 模块的 ios.h 头文件即可方便的调用 UIImage 到 cv::Mat 的转换函数。值得一提的是,OpenCV 中处理的彩色图像通常是 BGR 通道,故我们还需将 UIImage 的 RGB 通道转换成 BRG 格式。
FaceRecognizer.mm 文件
#import <opencv2/imgcodecs/ios.h>
UIImage 的定义在 iOS 的 UI 框架 UIKit中,首先在 FaceRecognizer.h 文件中引入 iOS 的 UIKit 框架的头文件,然后添加一个检测图像的实例方法,返回 ImageQualityResult 表示图片质量检测的结果。移除之前用于检查 OpenCV 和 Dlib 是否正常引入的 test 函数。
FaceRecognizer.h 文件
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
struct ImageQualityResult {
bool passed;
double brightness;
double blur;
int minSize;
};
@interface FaceRecognizer : NSObject
+ (FaceRecognizer *) shared;
- (struct ImageQualityResult) checkImageQuality: (UIImage *) img;
@end
NS_ASSUME_NONNULL_END
接下来编写对应的实现文件里的 checkImageQuality 函数,还有对应的检测指标函数 checkImageQualityBlur 和 checkImageQualityBriteness,均返回对应的检测值。检测指标函数不对外公开,所以直接在实现文件中编写即可。Objc 代码可以与 C++ 代码混合编译,为了方便 OpenCV 处理图像,这些检测指标函数均为 C++ 代码。
FaceRecognizer.mm 文件
#import <opencv2/opencv.hpp>
#import <opencv2/imgcodecs/ios.h>
#import <dlib/image_processing.h>
#import <dlib/image_processing/frontal_face_detector.h>
#import <dlib/image_processing/render_face_detections.h>
#import <dlib/opencv.h>
#import "FaceRecognizer.h"
#import <stdio.h>
#define MIN_IMG_SIZE 480.0
#define MIN_SOBEL_VALUE 2.5
#define MIN_BRIGHTNESS_VALUE 80
#define MAX_BRIGHTNESS_VALUE 200
@implementation FaceRecognizer
FaceRecognizer *mFaceRecognizer;
+ (FaceRecognizer *) shared {
if(!mFaceRecognizer) {
mFaceRecognizer = [FaceRecognizer new];
}
return mFaceRecognizer;
}
- (struct ImageQualityResult) checkImageQuality: (UIImage *) img; {
cv::Mat src;
UIImageToMat(img, src);
cv::cvtColor(src, src, cv::COLOR_RGB2BGR);
struct ImageQualityResult result;
result.minSize = MIN(src.cols, src.rows);
if (result.minSize < MIN_IMG_SIZE) {
result.passed = false;
return result;
}
//若尺寸是符合规定的,再将图片根据长高的比例,保持原图比例缩小较短的一边的长度为 480 像素。
resizeImg(src);
cv::Mat gray;
//转为灰度
cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY);
//检测清晰度
result.blur = checkImageQualityBlur(gray);
//如果小于设定的平均值,则判定为不够清晰。经过实践,该分辨率下,取2.5是比较合理的值。
if (result.blur < MIN_SOBEL_VALUE) {
result.passed = false;
return result;
}
result.brightness = checkImageBriteness(src);
//如果平均亮度在规定范围以外,择判定为过暗或过量。经过实践,范围定在80-200是比较理想的亮度值。
if (result.brightness < MIN_BRIGHTNESS_VALUE || result.brightness > MAX_BRIGHTNESS_VALUE) {
result.passed = false;
return result;
}
result.passed = true;
return result;
}
void resizeImg(cv::Mat &img) {
int newWidth, newHeight;
double ratio = img.cols * 1.0 / img.rows * 1.0; //图片长高比
if (ratio > 1) {
newHeight = MIN_IMG_SIZE;
newWidth = MIN_IMG_SIZE * ratio;
} else {
newWidth = MIN_IMG_SIZE;
newHeight = MIN_IMG_SIZE / ratio;
}
cv::resize(img, img, cv::Size(newWidth, newHeight));
cv::cvtColor(img, img, cv::COLOR_RGBA2BGR);
}
double checkImageQualityBlur(cv::Mat &img) {
cv::Mat sobel;
//Tenengrad梯度方法利用Sobel算子分别计算水平和垂直方向的梯度,梯度值越高,图像越清晰。
cv::Sobel(img, sobel, CV_16U, 1, 1);
//图像的平均梯度值
double meanValue = cv::mean(sobel)[0];
return meanValue;
}
double checkImageBriteness(cv::Mat &img) {
cv::Mat hsvImg;
//将图像转换为HSV色彩空间,提取亮度信息。
cv::cvtColor(img, hsvImg, cv::COLOR_BGR2HSV);
//计算hsv中的亮度平均值
double meanValue = cv::mean(hsvImg)[2];
return meanValue;
}
@end
为了让我们在脱离控制台输出时也可以判断检测器是否正常工作,我们需要设计一些机制让检测的结果回传到 UI 里。得益于强大的 SwiftUI,我们可以很轻松的做到这一点。
在 CameraController 类中添加一个 imageQualityResult 的变量用于保存图像质量检测结果。
CameraController.swift 文件
...
class CameraController: ObservableObject {
...
@Published var imageQualityResult = ImageQualityResult.init()
...
}
在 FaceRecognizeView 中编写对应的视图元素代码,实现在屏幕的左上方显示质量指标,当图像达标时使用绿色字体,不达标时显示红色字体。
FaceRecognizeView.swift 文件
...
ZStack {
CameraPreviewView(cameraController: cameraController)
VStack {
HStack {
Text("清晰度:\(cameraController.imageQualityResult.blur) 亮度:\(cameraController.imageQualityResult.brightness)")
.fontWeight(.bold)
.foregroundColor(cameraController.imageQualityResult.passed ? .green : .red)
.padding()
.background(Color.white)
Spacer()
}
Spacer()
}
}
.ignoresSafeArea()
...
定义一个委托,实现检测结果传递到 UI。
FaceRecognizeController.swift 文件
@objc protocol FaceRecognizerDelegate: NSObjectProtocol {
@objc optional func updateImageQualityResult(result: ImageQualityResult)
}
CameraController.swift 文件
...
class CameraController: NSObject, ObservableObject, FaceRecognizerDelegate {
...
var faceRecognizeController: FaceRecognizeController!
...
func setup() {
do {
...
faceRecognizeController = FaceRecognizeController.init();
faceRecognizeController.delegate = self
...
} catch {
print(error.localizedDescription)
}
}
...
func updateImageQualityResult(result: ImageQualityResult) {
DispatchQueue.main.async {
self.imageQualityResult = result
}
}
在 FaceRecognizeController 中添加一个 delegete 变量,获得摄像头图像输出时执行图片质量检测。
FaceRecognizeController.swift 文件
class FaceRecognizeController: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate {
weak var delegate: FaceRecognizerDelegate?
...
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
...
let image = UIImage(cgImage: cgImage)
let result = faceRecognizer.checkImageQuality(image)
self.delegate?.updateImageQualityResult?(result: result)
if (!result.passed) {
lock = false;
return;
}
lock = false
}
...
}
连接真机调试,运行 App。
至此,程序已经有一定检测图像质量的能力,可以进入下一步的开发。