CNLiveFaceAuthController.swift
35.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
//
// CNLiveFaceAuthController.swift
// metaCode
//
// Created by zx on 2024/6/11.
// 人脸识别页面
import Foundation
import SnapKit
import QMUIKit
import UIKit
import AVFoundation
import Vision
class CNLiveFaceAuthController:CNLiveBaseViewController{
var channelId:String = "0"
lazy var previewView :CNLiveAuthPreviewView = {
let previewView = CNLiveAuthPreviewView(frame: CGRect(x: 0, y: KNavigationHeight, width: KSreenWidth, height: KSreenHeight-KNavigationHeight))
return previewView
}()
// VNRequest: Either Retangles or Landmarks
private var faceDetectionRequest: VNRequest!
// TODO: Decide camera position --- front or back
private var devicePosition: AVCaptureDevice.Position = .front
// Session Management
private enum SessionSetupResult {
case success
case notAuthorized
case configurationFailed
}
private let session = AVCaptureSession()
private var isSessionRunning = false
// Communicate with the session and other session objects on this queue.
private let sessionQueue = DispatchQueue(label: "session queue", attributes: [], target: nil)
private var setupResult: SessionSetupResult = .success
private var videoDeviceInput: AVCaptureDeviceInput!
private var videoDataOutput: AVCaptureVideoDataOutput!
private var videoDataOutputQueue = DispatchQueue(label: "VideoDataOutputQueue")
private var requests = [VNRequest]()
private let circleWidth:CGFloat = 250
var realName:String = ""
var realNumber:String = ""
lazy var circleBgView :UIView = {
let circleBgView = UIView(frame: self.previewView.bounds)
circleBgView.backgroundColor = .white
return circleBgView
}()
lazy var circleBgImgView :UIImageView = {
let iconImgView = UIImageView()
iconImgView.image = UIImage(named: "auth_faceAuth")
return iconImgView
}()
lazy var descTitleLabel: UILabel = {
let titleLabel = UILabel()
titleLabel.textColor = HexColor("#333333")
titleLabel.textAlignment = .center
titleLabel.numberOfLines = 2
titleLabel.font = Font_18
return titleLabel
}()
lazy var yanzhengBtn: UIButton = {
let nextBtn = UIButton(type: .custom)
nextBtn.setTitle("立即验证", for: .normal)
nextBtn.setTitleColor(.white, for: .normal)
nextBtn.backgroundColor = HexColor("#1961FE")
nextBtn.layer.cornerRadius = 22
nextBtn.clipsToBounds = true
nextBtn.addTarget(self, action: #selector(yanzhengBtnAction), for: .touchUpInside)
return nextBtn
}()
lazy var xieyiLabel: UILabel = {
let label = UILabel()
let attrString = NSMutableAttributedString(string: "查看《人脸识别用户信息采集协议》")
label.frame = CGRect(x: 91.5, y: 558, width: 192, height: 16.5)
label.numberOfLines = 0
let attr: [NSAttributedString.Key : Any] = [.font: Font_12,.foregroundColor: UIColor(red: 0.2, green: 0.2, blue: 0.2, alpha: 1)]
attrString.addAttributes(attr, range: NSRange(location: 0, length: attrString.length))
view.addSubview(label)
let strSubAttr1: [NSMutableAttributedString.Key: Any] = [.font: Font_12,.foregroundColor: UIColor(red: 0.2, green: 0.2, blue: 0.2,alpha:1.000000)]
attrString.addAttributes(strSubAttr1, range: NSRange(location: 0, length: 2))
let strSubAttr2: [NSMutableAttributedString.Key: Any] = [.font: Font_12,.foregroundColor: UIColor(red: 0.1, green: 0.38, blue: 1,alpha:1.000000)]
attrString.addAttributes(strSubAttr2, range: NSRange(location: 2, length: 14))
label.attributedText = attrString
label.textAlignment = .center
label.isUserInteractionEnabled = true
label.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(xieyiAction)))
return label
}()
//是否上传图片中
var isUploadingImage:Bool = false
override func viewDidLoad() {
super.viewDidLoad()
navigationBarHidden = false
titleName = "元码数字认证"
self.view.addSubview(self.previewView)
// Set up the video preview view.
previewView.session = session
// Set up Vision Request
faceDetectionRequest = VNDetectFaceRectanglesRequest(completionHandler: self.handleFaces) // Default
setupVision()
/*
Check video authorization status. Video access is required and audio
access is optional. If audio access is denied, audio is not recorded
during movie recording.
*/
switch AVCaptureDevice.authorizationStatus(for: AVMediaType.video){
case .authorized:
// The user has previously granted access to the camera.
break
case .notDetermined:
/*
The user has not yet been presented with the option to grant
video access. We suspend the session queue to delay session
setup until the access request has completed.
*/
sessionQueue.suspend()
AVCaptureDevice.requestAccess(for: AVMediaType.video, completionHandler: { [unowned self] granted in
if !granted {
self.setupResult = .notAuthorized
}
self.sessionQueue.resume()
})
default:
// The user has previously denied access.
setupResult = .notAuthorized
}
/*
Setup the capture session.
In general it is not safe to mutate an AVCaptureSession or any of its
inputs, outputs, or connections from multiple threads at the same time.
Why not do all of this on the main queue?
Because AVCaptureSession.startRunning() is a blocking call which can
take a long time. We dispatch session setup to the sessionQueue so
that the main queue isn't blocked, which keeps the UI responsive.
*/
sessionQueue.async { [unowned self] in
self.configureSession()
}
self.previewView.addSubview(self.circleBgView)
//通过设置mask透明区域来实现镂空效果
let path:UIBezierPath = UIBezierPath.init(rect: UIScreen.main.bounds)
//rect 镂空的区域
let rect = CGRectMake((self.view.bounds.width-circleWidth)/2, 90,circleWidth, circleWidth)
let appendPath: UIBezierPath = UIBezierPath.init(roundedRect: rect, cornerRadius: circleWidth/2)
path.append(appendPath.reversing())
let shapeLayer: CAShapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath;
self.circleBgView.layer.mask = shapeLayer
self.circleBgImgView.frame = rect
self.previewView.addSubview(self.circleBgImgView)
self.previewView.addSubview(self.descTitleLabel)
self.descTitleLabel.text = String(format: "验证 %@ 的人脸完成身份认证, 以继续使用服务", self.realName)
self.descTitleLabel.snp.makeConstraints { make in
make.top.equalTo(self.circleBgImgView.snp.bottom).offset(15)
make.left.equalTo(15)
make.right.equalToSuperview().offset(-15)
make.height.equalTo(50)
}
self.previewView.addSubview(self.yanzhengBtn)
self.yanzhengBtn.snp.makeConstraints { make in
make.top.equalTo(self.descTitleLabel.snp.bottom).offset(90)
make.height.equalTo(44)
make.left.equalTo(30)
make.right.equalToSuperview().offset(-30)
}
self.previewView.addSubview(self.xieyiLabel)
self.xieyiLabel.snp.makeConstraints { make in
make.top.equalTo(self.yanzhengBtn.snp.bottom).offset(15)
make.height.equalTo(17)
make.left.equalTo(30)
make.right.equalToSuperview().offset(-30)
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
sessionQueue.async { [unowned self] in
switch self.setupResult {
case .success:
// Only setup observers and start the session running if setup succeeded.
self.addObservers()
self.session.startRunning()
self.isSessionRunning = self.session.isRunning
case .notAuthorized:
DispatchQueue.main.async { [unowned self] in
let message = NSLocalizedString("AVCamBarcode doesn't have permission to use the camera, please change privacy settings", comment: "Alert message when the user has denied access to the camera")
let alertController = UIAlertController(title: "AppleFaceDetection", message: message, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: "Alert OK button"), style: .cancel, handler: nil))
alertController.addAction(UIAlertAction(title: NSLocalizedString("Settings", comment: "Alert button to open Settings"), style: .`default`, handler: { action in
UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!, options: [:], completionHandler: nil)
}))
self.present(alertController, animated: true, completion: nil)
}
case .configurationFailed:
DispatchQueue.main.async { [unowned self] in
let message = NSLocalizedString("Unable to capture media", comment: "Alert message when something goes wrong during capture session configuration")
let alertController = UIAlertController(title: "AppleFaceDetection", message: message, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: "Alert OK button"), style: .cancel, handler: nil))
self.present(alertController, animated: true, completion: nil)
}
}
}
}
override func viewWillDisappear(_ animated: Bool) {
sessionQueue.async { [unowned self] in
if self.setupResult == .success {
self.session.stopRunning()
self.isSessionRunning = self.session.isRunning
self.removeObservers()
}
}
super.viewWillDisappear(animated)
}
@objc func yanzhengBtnAction(){
self.circleBgImgView.isHidden = true
}
@objc func xieyiAction(){
let webVC = CNWebViewController()
webVC.url = URL(string: "https://wjj.ys1.cnliveimg.com/term/ymRlsbyhxxcjxy.html")
self.navigationController?.pushViewController(webVC, animated: true)
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
if let videoPreviewLayerConnection = previewView.videoPreviewLayer.connection {
let deviceOrientation = UIDevice.current.orientation
guard let newVideoOrientation = deviceOrientation.videoOrientation, deviceOrientation.isPortrait || deviceOrientation.isLandscape else {
return
}
videoPreviewLayerConnection.videoOrientation = newVideoOrientation
}
}
// Segmente Control to switch over FaceOnly or FaceLandmark
func UpdateDetectionType(_ sender: UISegmentedControl) {
faceDetectionRequest = sender.selectedSegmentIndex == 0 ? VNDetectFaceRectanglesRequest(completionHandler: handleFaces) : VNDetectFaceLandmarksRequest(completionHandler: handleFaceLandmarks)
setupVision()
}
//zx
var screenImage:UIImage?
lazy var imageView :UIImageView = {
let imageView = UIImageView()
return imageView
}()
// lazy var faceImageView :UIImageView = {
// let imageView = UIImageView()
// return imageView
// }()
//zx
var tempPixelBuffer:CVImageBuffer?
func imageFromSampleBuffer(sampleBuffer: CMSampleBuffer) -> UIImage? {
guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
return nil
}
// 锁定像素缓冲区基地址
CVPixelBufferLockBaseAddress(imageBuffer, .readOnly)
// 获取像素缓冲区的宽度和高度
let baseAddress = CVPixelBufferGetBaseAddress(imageBuffer)
let bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer)
let width = CVPixelBufferGetWidth(imageBuffer)
let height = CVPixelBufferGetHeight(imageBuffer)
// 创建CIImage
let colorSpace = CGColorSpaceCreateDeviceRGB()
let bitmapInfo: CGBitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedFirst.rawValue)
let context = CGContext(
data: baseAddress,
width: width,
height: height,
bitsPerComponent: 8,
bytesPerRow: bytesPerRow,
space: colorSpace,
bitmapInfo: bitmapInfo.rawValue
)
guard let cgImage = context?.makeImage() else {
CVPixelBufferUnlockBaseAddress(imageBuffer, .readOnly)
return nil
}
// 解锁像素缓冲区基地址
CVPixelBufferUnlockBaseAddress(imageBuffer, .readOnly)
// 创建UIImage
let image = UIImage(cgImage: cgImage)
return image
}
func takeScreenshot(view: UIView? = nil) -> UIImage? {
// 获取屏幕截图
let bounds = UIScreen.main.bounds
let scale = UIScreen.main.scale
// 开始图形上下文
UIGraphicsBeginImageContextWithOptions(bounds.size, false, scale)
// 如果没有指定view,则截取整个屏幕
if let context = UIGraphicsGetCurrentContext() {
if let view = view {
// 截取view的截图
view.layer.render(in: context)
} else {
// 截取整个屏幕的截图
UIApplication.shared.keyWindow?.layer.render(in: context)
}
// 从图形上下文获取图片
let image = UIGraphicsGetImageFromCurrentImageContext()
// 结束图形上下文
UIGraphicsEndImageContext()
return image
}
return nil
}
}
// Video Sessions
extension CNLiveFaceAuthController {
private func configureSession() {
if setupResult != .success { return }
session.beginConfiguration()
session.sessionPreset = .high
// Add video input.
addVideoDataInput()
// Add video output.
addVideoDataOutput()
session.commitConfiguration()
}
private func addVideoDataInput() {
do {
var defaultVideoDevice: AVCaptureDevice!
if devicePosition == .front {
if let frontCameraDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: AVMediaType.video, position: .front) {
defaultVideoDevice = frontCameraDevice
}
}
else {
// Choose the back dual camera if available, otherwise default to a wide angle camera.
if let dualCameraDevice = AVCaptureDevice.default(.builtInDualCamera, for: AVMediaType.video, position: .back) {
defaultVideoDevice = dualCameraDevice
}
else if let backCameraDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: AVMediaType.video, position: .back) {
defaultVideoDevice = backCameraDevice
}
}
let videoDeviceInput = try AVCaptureDeviceInput(device: defaultVideoDevice!)
if session.canAddInput(videoDeviceInput) {
session.addInput(videoDeviceInput)
self.videoDeviceInput = videoDeviceInput
DispatchQueue.main.async {
/*
Why are we dispatching this to the main queue?
Because AVCaptureVideoPreviewLayer is the backing layer for PreviewView and UIView
can only be manipulated on the main thread.
Note: As an exception to the above rule, it is not necessary to serialize video orientation changes
on the AVCaptureVideoPreviewLayer’s connection with other session manipulation.
Use the status bar orientation as the initial video orientation. Subsequent orientation changes are
handled by CameraViewController.viewWillTransition(to:with:).
*/
let statusBarOrientation = UIApplication.shared.statusBarOrientation
var initialVideoOrientation: AVCaptureVideoOrientation = .portrait
if statusBarOrientation != .unknown {
if let videoOrientation = statusBarOrientation.videoOrientation {
initialVideoOrientation = videoOrientation
}
}
self.previewView.videoPreviewLayer.connection!.videoOrientation = initialVideoOrientation
}
}
}
catch {
print("Could not add video device input to the session")
setupResult = .configurationFailed
session.commitConfiguration()
return
}
}
private func addVideoDataOutput() {
videoDataOutput = AVCaptureVideoDataOutput()
videoDataOutput.videoSettings = [(kCVPixelBufferPixelFormatTypeKey as String): Int(kCVPixelFormatType_32BGRA)]
if session.canAddOutput(videoDataOutput) {
videoDataOutput.alwaysDiscardsLateVideoFrames = true
videoDataOutput.setSampleBufferDelegate(self, queue: videoDataOutputQueue)
session.addOutput(videoDataOutput)
}
else {
print("Could not add metadata output to the session")
setupResult = .configurationFailed
session.commitConfiguration()
return
}
}
}
// MARK: -- Observers and Event Handlers
extension CNLiveFaceAuthController {
private func addObservers() {
/*
Observe the previewView's regionOfInterest to update the AVCaptureMetadataOutput's
rectOfInterest when the user finishes resizing the region of interest.
*/
NotificationCenter.default.addObserver(self, selector: #selector(sessionRuntimeError), name: Notification.Name("AVCaptureSessionRuntimeErrorNotification"), object: session)
/*
A session can only run when the app is full screen. It will be interrupted
in a multi-app layout, introduced in iOS 9, see also the documentation of
AVCaptureSessionInterruptionReason. Add observers to handle these session
interruptions and show a preview is paused message. See the documentation
of AVCaptureSessionWasInterruptedNotification for other interruption reasons.
*/
NotificationCenter.default.addObserver(self, selector: #selector(sessionWasInterrupted), name: Notification.Name("AVCaptureSessionWasInterruptedNotification"), object: session)
NotificationCenter.default.addObserver(self, selector: #selector(sessionInterruptionEnded), name: Notification.Name("AVCaptureSessionInterruptionEndedNotification"), object: session)
}
private func removeObservers() {
NotificationCenter.default.removeObserver(self)
}
@objc func sessionRuntimeError(_ notification: Notification) {
guard let errorValue = notification.userInfo?[AVCaptureSessionErrorKey] as? NSError else { return }
let error = AVError(_nsError: errorValue)
print("Capture session runtime error: \(error)")
/*
Automatically try to restart the session running if media services were
reset and the last start running succeeded. Otherwise, enable the user
to try to resume the session running.
*/
if error.code == .mediaServicesWereReset {
sessionQueue.async { [unowned self] in
if self.isSessionRunning {
self.session.startRunning()
self.isSessionRunning = self.session.isRunning
}
}
}
}
@objc func sessionWasInterrupted(_ notification: Notification) {
/*
In some scenarios we want to enable the user to resume the session running.
For example, if music playback is initiated via control center while
using AVCamBarcode, then the user can let AVCamBarcode resume
the session running, which will stop music playback. Note that stopping
music playback in control center will not automatically resume the session
running. Also note that it is not always possible to resume, see `resumeInterruptedSession(_:)`.
*/
if let userInfoValue = notification.userInfo?[AVCaptureSessionInterruptionReasonKey] as AnyObject?, let reasonIntegerValue = userInfoValue.integerValue, let reason = AVCaptureSession.InterruptionReason(rawValue: reasonIntegerValue) {
print("Capture session was interrupted with reason \(reason)")
}
}
@objc func sessionInterruptionEnded(_ notification: Notification) {
print("Capture session interruption ended")
}
}
// MARK: -- Helpers
extension CNLiveFaceAuthController {
func setupVision() {
self.requests = [faceDetectionRequest]
}
func handleFaces(request: VNRequest, error: Error?) {
DispatchQueue.main.async {
//perform all the UI updates on the main queue
guard let results = request.results as? [VNFaceObservation] else { return }
self.previewView.removeMask()
//zx
let ciImage = CIImage(cvPixelBuffer: self.tempPixelBuffer!)
let context = CIContext(options: nil)
if let cgImage = context.createCGImage(ciImage, from: ciImage.extent) {
let uiImage = UIImage(cgImage: cgImage, scale: UIScreen.main.scale, orientation: .leftMirrored)
for face in results {
// self.previewView.drawFaceboundingBox(face: face)
//zx
let transform = CGAffineTransform(scaleX: 1, y: -1).translatedBy(x: 0, y: -self.previewView.frame.height)
let translate = CGAffineTransform.identity.scaledBy(x: self.previewView.frame.width, y: self.previewView.frame.height)
// The coordinates are normalized to the dimensions of the processed image, with the origin at the image's lower-left corner.
let facebounds = face.boundingBox.applying(translate).applying(transform)
print("11111facebounds:\(facebounds)")
//是否在镂空区域内
let circleRect = CGRectMake((self.view.bounds.width-self.circleWidth)/2, 90,self.circleWidth, self.circleWidth)
if circleRect.contains(facebounds) {
print("人脸在圆形区域内")
if self.isUploadingImage == false{
self.cropImageAndUpload(uiImage: uiImage, facebounds: facebounds, cgImage: cgImage)
}
} else {
print("人脸不在圆形区域内")
}
//眨眼部分逻辑
/*
let landmarkRequest = VNDetectFaceLandmarksRequest { (request, error) in
guard let results = request.results as? [VNFaceObservation],
let faceObservation = results.first,
let landmarks = faceObservation.landmarks else { return }
faceObservation.landmarks?.rightEye?.normalizedPoints
let leftEyePoints = landmarks.leftEye
let rightEyePoints = landmarks.rightEye
// 简化处理:假设每个眼睛都有两个关键点,分别代表眼睛的左上角和右上角
if let leftEyeTop = leftEyePoints?.normalizedPoints.first,let leftEyeBottom = leftEyePoints?.normalizedPoints.last
,let rightEyeTop = rightEyePoints?.normalizedPoints.first
,let rightEyeBottom = rightEyePoints?.normalizedPoints.last{
let leftEyeHeight = leftEyeBottom.x - leftEyeTop.x
let leftEyeWidth = leftEyeBottom.y - leftEyeTop.y
print("11111leftH:\(leftEyeHeight)")
print("11111leftW:\(leftEyeWidth)")
// 计算眼睛之间的距离(简化为两个眼睛中心点之间的距离)
let leftEyeCenter = CGPoint(x: (leftEyeTop.x + leftEyeBottom.x) / 2, y: (leftEyeTop.y + leftEyeBottom.y) / 2)
let rightEyeCenter = CGPoint(x: (rightEyeTop.x + rightEyeBottom.x) / 2, y: (rightEyeTop.y + rightEyeBottom.y) / 2)
let eyeDistance = hypot(rightEyeCenter.x - leftEyeCenter.x, rightEyeCenter.y - leftEyeCenter.y)
// 你需要维护一个状态来跟踪之前帧的眼睛距离
// 如果eyeDistance突然变小并且小于某个阈值,则认为发生了眨眼
// 这里需要你自己实现逻辑来保存和比较之前的距离
print("11111eyeDistance:\(eyeDistance)")
// 示例逻辑(需要完善)
// if eyeDistance < someBlinkThreshold {
// // 检测到眨眼
// }
}
}
// 使用Vision框架处理图像并传入landmarkRequest
try? VNImageRequestHandler(ciImage: ciImage, options: [:]).perform([landmarkRequest])
*/
// self.view.addSubview(self.imageView)
// self.imageView.isHidden = true
// self.imageView.image = uiImage
// self.imageView.frame = CGRect(x: 0, y: 0, width: self.view.bounds.size.width, height: self.view.bounds.size.height)
}
}
}
}
func handleFaceLandmarks(request: VNRequest, error: Error?) {
DispatchQueue.main.async {
//perform all the UI updates on the main queue
guard let results = request.results as? [VNFaceObservation] else { return }
self.previewView.removeMask()
for face in results {
self.previewView.drawFaceWithLandmarks(face: face)
}
}
}
// MARK: - 上传人脸图片
func cropImageAndUpload(uiImage:UIImage ,facebounds:CGRect ,cgImage:CGImage){
self.isUploadingImage = true
let sacle = uiImage.size.width/self.view.bounds.width
let scaleFaceBounds = CGRect(x: 0, y: 0, width: facebounds.width*sacle, height: facebounds.height*sacle*self.view.bounds.height/self.view.bounds.width)
let scaleUiImage = UIImage(cgImage: cgImage, scale: 1.0, orientation: .leftMirrored)
self.cropFacesToAvatarsWithExtendedArea(image: scaleUiImage) { avatarImage in
if let avatarImage = avatarImage {
// 使用裁剪后的头像
print("11111Avatar image: \(avatarImage)")
let data = avatarImage.compressImage()
print("11111Image size: \(data.count/1024) KB")
CNLiveAuthTool.shared.authToolHiddenBlock = {
self.isUploadingImage = false
}
CNLiveDigitalAuthViewModel.postFaceImageDataWithParams(name: self.realName, idNo: self.realNumber, fileData: data) { result, status in
if status == .success{
//跳转到身份认证页面
let listModel = StandListModel()
listModel.id = Int(self.channelId) ?? 0
listModel.name = self.realName
self.navigationController?.pushViewController(CNLiveMineInputViewController.init(listModel: listModel, authType: .person), animated: pushAnimated)
}else{
//提示认证失败
CNLiveAuthTool.shared.showAuthFailAlert()
}
}
// self.view.addSubview(self.faceImageView)
// self.faceImageView.image = avatarImage
// self.faceImageView.frame = CGRect(x: 50, y: 200, width: avatarImage.size.width, height: avatarImage.size.height)
// self.faceImageView.backgroundColor = .red
} else {
print("No faces detected or error occurred.")
}
}
}
//zx
func cropFacesToAvatarsWithExtendedArea(image: UIImage, completion: @escaping (UIImage?) -> Void) {
guard let cgImage = image.cgImage else {
completion(nil)
return
}
let imageRequestHandler = VNImageRequestHandler(cgImage: cgImage, options: [:])
let faceDetectionRequest = VNDetectFaceRectanglesRequest { (request, error) in
guard let observations = request.results as? [VNFaceObservation], !observations.isEmpty else {
completion(nil)
return
}
let faceObservation = observations.first // 假设我们只处理第一张脸
// 扩大边界框以包含更多区域(如脖子和头发)
// 这可以通过调整边界框的origin和size来实现
let leftRightScale = 0.1
let upDownScale = 0.2
let minX = max(faceObservation!.boundingBox.minX - upDownScale, 0.0)// 向上调整边界以包括头发
let minY = max(faceObservation!.boundingBox.minY - leftRightScale, 0.0) // 向左调整边界
let maxX = min(faceObservation!.boundingBox.maxX + upDownScale, 1.0)// 向下调整边界以包括脖子
let maxY = min(faceObservation!.boundingBox.maxY + leftRightScale, 1.0) // 向右调整边界
let extendedBoundingBox = CGRectMake(minX, minY, maxX - minX, maxY - minY)
// 将归一化坐标转换为像素坐标
let scale:CGFloat = image.scale
let cropRect = CGRectMake(extendedBoundingBox.minX * CGFloat(cgImage.width) * scale, extendedBoundingBox.minY * CGFloat(cgImage.height) * scale, extendedBoundingBox.width * CGFloat(cgImage.width) * scale, extendedBoundingBox.height * CGFloat(cgImage.height) * scale)
// 使用Core Graphics裁剪图片
if let croppedCGImage = cgImage.cropping(to: cropRect) {
let croppedImage = UIImage(cgImage: croppedCGImage, scale: UIScreen.main.scale, orientation: image.imageOrientation)
DispatchQueue.main.async {
completion(croppedImage)
}
} else {
DispatchQueue.main.async {
completion(nil)
}
}
}
// 执行请求
do {
try imageRequestHandler.perform([faceDetectionRequest])
} catch {
DispatchQueue.main.async {
completion(nil)
}
}
}
}
// Camera Settings & Orientation
extension CNLiveFaceAuthController {
func availableSessionPresets() -> [String] {
let allSessionPresets = [AVCaptureSession.Preset.photo,
AVCaptureSession.Preset.low,
AVCaptureSession.Preset.medium,
AVCaptureSession.Preset.high,
AVCaptureSession.Preset.cif352x288,
AVCaptureSession.Preset.vga640x480,
AVCaptureSession.Preset.hd1280x720,
AVCaptureSession.Preset.iFrame960x540,
AVCaptureSession.Preset.iFrame1280x720,
AVCaptureSession.Preset.hd1920x1080,
AVCaptureSession.Preset.hd4K3840x2160]
var availableSessionPresets = [String]()
for sessionPreset in allSessionPresets {
if session.canSetSessionPreset(sessionPreset) {
availableSessionPresets.append(sessionPreset.rawValue)
}
}
return availableSessionPresets
}
func exifOrientationFromDeviceOrientation() -> UInt32 {
enum DeviceOrientation: UInt32 {
case top0ColLeft = 1
case top0ColRight = 2
case bottom0ColRight = 3
case bottom0ColLeft = 4
case left0ColTop = 5
case right0ColTop = 6
case right0ColBottom = 7
case left0ColBottom = 8
}
var exifOrientation: DeviceOrientation
switch UIDevice.current.orientation {
case .portraitUpsideDown:
exifOrientation = .left0ColBottom
case .landscapeLeft:
exifOrientation = devicePosition == .front ? .bottom0ColRight : .top0ColLeft
case .landscapeRight:
exifOrientation = devicePosition == .front ? .top0ColLeft : .bottom0ColRight
default:
exifOrientation = devicePosition == .front ? .left0ColTop : .right0ColTop
}
return exifOrientation.rawValue
}
}
// MARK: - AVCaptureVideoDataOutputSampleBufferDelegate
extension CNLiveFaceAuthController: AVCaptureVideoDataOutputSampleBufferDelegate {
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer),
let exifOrientation = CGImagePropertyOrientation(rawValue: exifOrientationFromDeviceOrientation()) else { return }
//zx
self.tempPixelBuffer = pixelBuffer
var requestOptions: [VNImageOption : Any] = [:]
if let cameraIntrinsicData = CMGetAttachment(sampleBuffer, key: kCMSampleBufferAttachmentKey_CameraIntrinsicMatrix, attachmentModeOut: nil) {
requestOptions = [.cameraIntrinsics : cameraIntrinsicData]
}
let imageRequestHandler = VNImageRequestHandler(cvPixelBuffer: pixelBuffer, orientation: exifOrientation, options: requestOptions)
do {
try imageRequestHandler.perform(requests)
}
catch {
print(error)
}
}
}