I am trying to integrate openCV
with native iOS
app, The problem I am facing is my code is not able to detect faces properly and draw a bounding box around the same. Here is the complete source code, please drag and drop the openCV
iOS framework in the project if you decide to clone as it was too large for GitHub so I had to remove it.
So here is my Objective-C++
code
#import <opencv2/opencv.hpp>
#import <opencv2/imgcodecs/ios.h>
#import "OpenCVWrapper.h"
/*
* Add a method convertToMat to UIImage class
*/
@interface UIImage (OpenCVWrapper)
- (void)convertToMat: (cv::Mat *)pMat: (bool)alphaExists;
@end
@implementation UIImage (OpenCVWrapper)
- (void)convertToMat: (cv::Mat *)pMat: (bool)alphaExists {
UIImageOrientation orientation = self.imageOrientation;
cv::Mat mat;
UIImageToMat(self, mat, alphaExists);
switch (orientation) {
case UIImageOrientationRight:
cv::rotate(mat, *pMat, cv::ROTATE_90_CLOCKWISE);
break;
case UIImageOrientationLeft:
cv::rotate(mat, *pMat, cv::ROTATE_90_COUNTERCLOCKWISE);
break;
case UIImageOrientationDown:
cv::rotate(mat, *pMat, cv::ROTATE_180);
break;
case UIImageOrientationUp:
default:
*pMat = mat;
break;
}
}
@end
@implementation OpenCVWrapper
+ (NSArray<NSValue *> *)detectFaceRectsInUIImage:(UIImage *)image {
// Convert UIImage to cv::Mat
cv::Mat mat;
[image convertToMat:&mat :false];
// Load the face detection model
NSString *faceCascadePath = [[NSBundle mainBundle] pathForResource:@"haarcascade_frontalface_default" ofType:@"xml"];
cv::CascadeClassifier faceCascade;
if (!faceCascade.load([faceCascadePath UTF8String])) {
NSLog(@"Error loading face detection model");
return @[];
}
// Convert the image to grayscale
cv::Mat gray;
cv::cvtColor(mat, gray, cv::COLOR_BGR2GRAY);
cv::equalizeHist(gray, gray);
// Detect faces
std::vector<cv::Rect> faces;
faceCascade.detectMultiScale(gray, faces, 1.1, 2, 0 | cv::CASCADE_SCALE_IMAGE, cv::Size(30, 30));
// Convert cv::Rect to CGRect and wrap in NSValue
NSMutableArray<NSValue *> *faceRects = [NSMutableArray arrayWithCapacity:faces.size()];
for (const auto &face : faces) {
CGRect faceRect = CGRectMake(face.x, face.y, face.width, face.height);
[faceRects addObject:[NSValue valueWithCGRect:faceRect]];
}
return [faceRects copy];
}
@end
Following is my swift code
import UIKit
import AVFoundation
import VideoToolbox
class ViewController: UIViewController,AVCaptureVideoDataOutputSampleBufferDelegate {
var previewView : UIView!
var boxView:UIView!
//Camera Capture requiered properties
var videoDataOutput: AVCaptureVideoDataOutput!
var videoDataOutputQueue: DispatchQueue!
var previewLayer:AVCaptureVideoPreviewLayer!
var captureDevice : AVCaptureDevice!
let session = AVCaptureSession()
private var faceOverlayView: FaceOverlayView!
override func viewDidLoad() {
super.viewDidLoad()
previewView = UIView(frame: CGRect(x: 0,
y: 0,
width: UIScreen.main.bounds.size.width,
height: UIScreen.main.bounds.size.height))
previewView.contentMode = UIView.ContentMode.scaleAspectFit
view.addSubview(previewView)
boxView = UIView(frame: self.view.frame)
view.addSubview(boxView)
// Initialize face overlay view
faceOverlayView = FaceOverlayView(frame: view.bounds)
view.addSubview(faceOverlayView)
setupAVCapture()
}
override var shouldAutorotate: Bool {
if (UIDevice.current.orientation == UIDeviceOrientation.landscapeLeft ||
UIDevice.current.orientation == UIDeviceOrientation.landscapeRight ||
UIDevice.current.orientation == UIDeviceOrientation.unknown) {
return false
}
else {
return true
}
}
func setupAVCapture(){
session.sessionPreset = AVCaptureSession.Preset.vga640x480
guard let device = AVCaptureDevice
.default(AVCaptureDevice.DeviceType.builtInWideAngleCamera,
for: .video,
position: AVCaptureDevice.Position.back) else {
return
}
captureDevice = device
beginSession()
}
func beginSession(){
var deviceInput: AVCaptureDeviceInput!
do {
deviceInput = try AVCaptureDeviceInput(device: captureDevice)
guard deviceInput != nil else {
print("error: cant get deviceInput")
return
}
if self.session.canAddInput(deviceInput){
self.session.addInput(deviceInput)
}
videoDataOutput = AVCaptureVideoDataOutput()
videoDataOutput.alwaysDiscardsLateVideoFrames=true
videoDataOutputQueue = DispatchQueue(label: "VideoDataOutputQueue")
videoDataOutput.setSampleBufferDelegate(self, queue:self.videoDataOutputQueue)
if session.canAddOutput(self.videoDataOutput){
session.addOutput(self.videoDataOutput)
}
videoDataOutput.connection(with: .video)?.isEnabled = true
previewLayer = AVCaptureVideoPreviewLayer(session: self.session)
previewLayer.videoGravity = AVLayerVideoGravity.resizeAspect
let rootLayer :CALayer = self.previewView.layer
rootLayer.masksToBounds=true
previewLayer.frame = rootLayer.bounds
rootLayer.addSublayer(self.previewLayer)
DispatchQueue.global(qos: .userInitiated).async {
self.session.startRunning()
}
} catch let error as NSError {
deviceInput = nil
print("error: (error.localizedDescription)")
}
}
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return }
guard let image = UIImage(pixelBuffer: imageBuffer) else {
return
}
detectFaces(in: image)
//stopCamera()
}
func stopCamera(){
session.stopRunning()
}
private func detectFaces(in image: UIImage) {
guard let faceRects = OpenCVWrapper.detectFaceRects(in: image) else { return }
DispatchQueue.main.async {
let viewWidth = self.faceOverlayView.bounds.width
let viewHeight = self.faceOverlayView.bounds.height
let imageWidth = image.size.width
let imageHeight = image.size.height
let scaleX = viewWidth / imageWidth
let scaleY = viewHeight / imageHeight
let scaleFactor = min(scaleX, scaleY)
let offsetX = (viewWidth - imageWidth * scaleFactor) / 2
let offsetY = (viewHeight - imageHeight * scaleFactor) / 2
let transformedRects = faceRects.map { $0.cgRectValue }.map { face in
return CGRect(
x: face.origin.x * scaleFactor + offsetX,
y: face.origin.y * scaleFactor + offsetY,
width: face.size.width * scaleFactor,
height: face.size.height * scaleFactor
)
}
self.faceOverlayView.setFaces(transformedRects)
}
}
}
extension UIImage {
public convenience init?(pixelBuffer: CVPixelBuffer) {
var cgImage: CGImage?
VTCreateCGImageFromCVPixelBuffer(pixelBuffer, options: nil, imageOut: &cgImage)
guard let cgImage = cgImage else {
return nil
}
self.init(cgImage: cgImage)
}
}
I am able to build the project properly, just the face detection part is a bit off and not sure what else needs to be added