-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
39 changed files
with
736 additions
and
0 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
8 changes: 8 additions & 0 deletions
8
.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||
<plist version="1.0"> | ||
<dict> | ||
<key>IDEDidComputeMac32BitWarning</key> | ||
<true/> | ||
</dict> | ||
</plist> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<Scheme | ||
LastUpgradeVersion = "1530" | ||
version = "1.7"> | ||
<BuildAction | ||
parallelizeBuildables = "YES" | ||
buildImplicitDependencies = "YES" | ||
buildArchitectures = "Automatic"> | ||
<BuildActionEntries> | ||
<BuildActionEntry | ||
buildForTesting = "YES" | ||
buildForRunning = "YES" | ||
buildForProfiling = "YES" | ||
buildForArchiving = "YES" | ||
buildForAnalyzing = "YES"> | ||
<BuildableReference | ||
BuildableIdentifier = "primary" | ||
BlueprintIdentifier = "Mentalist" | ||
BuildableName = "Mentalist" | ||
BlueprintName = "Mentalist" | ||
ReferencedContainer = "container:"> | ||
</BuildableReference> | ||
</BuildActionEntry> | ||
</BuildActionEntries> | ||
</BuildAction> | ||
<TestAction | ||
buildConfiguration = "Debug" | ||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" | ||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" | ||
shouldUseLaunchSchemeArgsEnv = "YES" | ||
shouldAutocreateTestPlan = "YES"> | ||
<Testables> | ||
<TestableReference | ||
skipped = "NO"> | ||
<BuildableReference | ||
BuildableIdentifier = "primary" | ||
BlueprintIdentifier = "MentalistTests" | ||
BuildableName = "MentalistTests" | ||
BlueprintName = "MentalistTests" | ||
ReferencedContainer = "container:"> | ||
</BuildableReference> | ||
</TestableReference> | ||
</Testables> | ||
</TestAction> | ||
<LaunchAction | ||
buildConfiguration = "Debug" | ||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" | ||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" | ||
launchStyle = "0" | ||
useCustomWorkingDirectory = "NO" | ||
ignoresPersistentStateOnLaunch = "NO" | ||
debugDocumentVersioning = "YES" | ||
debugServiceExtension = "internal" | ||
allowLocationSimulation = "YES"> | ||
</LaunchAction> | ||
<ProfileAction | ||
buildConfiguration = "Release" | ||
shouldUseLaunchSchemeArgsEnv = "YES" | ||
savedToolIdentifier = "" | ||
useCustomWorkingDirectory = "NO" | ||
debugDocumentVersioning = "YES"> | ||
<MacroExpansion> | ||
<BuildableReference | ||
BuildableIdentifier = "primary" | ||
BlueprintIdentifier = "Mentalist" | ||
BuildableName = "Mentalist" | ||
BlueprintName = "Mentalist" | ||
ReferencedContainer = "container:"> | ||
</BuildableReference> | ||
</MacroExpansion> | ||
</ProfileAction> | ||
<AnalyzeAction | ||
buildConfiguration = "Debug"> | ||
</AnalyzeAction> | ||
<ArchiveAction | ||
buildConfiguration = "Release" | ||
revealArchiveInOrganizer = "YES"> | ||
</ArchiveAction> | ||
</Scheme> |
54 changes: 54 additions & 0 deletions
54
.swiftpm/xcode/xcshareddata/xcschemes/MentalistTests.xcscheme
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<Scheme | ||
LastUpgradeVersion = "1530" | ||
version = "1.7"> | ||
<BuildAction | ||
parallelizeBuildables = "YES" | ||
buildImplicitDependencies = "YES" | ||
buildArchitectures = "Automatic"> | ||
</BuildAction> | ||
<TestAction | ||
buildConfiguration = "Debug" | ||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" | ||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" | ||
shouldUseLaunchSchemeArgsEnv = "YES" | ||
shouldAutocreateTestPlan = "YES"> | ||
<Testables> | ||
<TestableReference | ||
skipped = "NO"> | ||
<BuildableReference | ||
BuildableIdentifier = "primary" | ||
BlueprintIdentifier = "MentalistTests" | ||
BuildableName = "MentalistTests" | ||
BlueprintName = "MentalistTests" | ||
ReferencedContainer = "container:"> | ||
</BuildableReference> | ||
</TestableReference> | ||
</Testables> | ||
</TestAction> | ||
<LaunchAction | ||
buildConfiguration = "Debug" | ||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" | ||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" | ||
launchStyle = "0" | ||
useCustomWorkingDirectory = "NO" | ||
ignoresPersistentStateOnLaunch = "NO" | ||
debugDocumentVersioning = "YES" | ||
debugServiceExtension = "internal" | ||
allowLocationSimulation = "YES"> | ||
</LaunchAction> | ||
<ProfileAction | ||
buildConfiguration = "Release" | ||
shouldUseLaunchSchemeArgsEnv = "YES" | ||
savedToolIdentifier = "" | ||
useCustomWorkingDirectory = "NO" | ||
debugDocumentVersioning = "YES"> | ||
</ProfileAction> | ||
<AnalyzeAction | ||
buildConfiguration = "Debug"> | ||
</AnalyzeAction> | ||
<ArchiveAction | ||
buildConfiguration = "Release" | ||
revealArchiveInOrganizer = "YES"> | ||
</ArchiveAction> | ||
</Scheme> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
// swift-tools-version:5.6 | ||
import PackageDescription | ||
|
||
let package = Package( | ||
name: "Mentalist", | ||
platforms: [ | ||
.macOS(.v10_13), | ||
.iOS(.v15) | ||
], | ||
products: [ | ||
.library( | ||
name: "Mentalist", | ||
targets: ["Mentalist"]), | ||
], | ||
dependencies: [], | ||
targets: [ | ||
.target( | ||
name: "Mentalist", | ||
dependencies: [], | ||
resources: [ | ||
.process("Resources/FacialExpressionModel.mlpackage") | ||
] | ||
), | ||
.testTarget( | ||
name: "MentalistTests", | ||
dependencies: ["Mentalist"], | ||
resources: [ | ||
.process("Resources") | ||
] | ||
), | ||
] | ||
) |
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
// | ||
// UIView + Extension.swift | ||
// | ||
// | ||
// Created by Enebin on 6/12/24. | ||
// | ||
|
||
import UIKit | ||
|
||
extension UIView { | ||
func asUIImage() -> UIImage { | ||
let renderer = UIGraphicsImageRenderer(bounds: bounds) | ||
return renderer.image { rendererContext in | ||
layer.render(in: rendererContext.cgContext) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
// | ||
// View + Extentsion.swift | ||
// | ||
// | ||
// Created by Enebin on 6/12/24. | ||
// | ||
|
||
import SwiftUI | ||
|
||
extension View { | ||
func asUIImage() -> UIImage { | ||
let controller = UIHostingController(rootView: self) | ||
controller.view.backgroundColor = .clear | ||
|
||
controller.view.frame = CGRect(x: 0, y: CGFloat(Int.max), width: 1, height: 1) | ||
UIApplication.shared.windows.first!.rootViewController?.view.addSubview(controller.view) | ||
|
||
let size = controller.sizeThatFits(in: UIScreen.main.bounds.size) | ||
controller.view.bounds = CGRect(origin: .zero, size: size) | ||
controller.view.sizeToFit() | ||
|
||
let image = controller.view.asUIImage() | ||
controller.view.removeFromSuperview() | ||
return image | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
// | ||
// FaceImageTool.swift | ||
// Mentalist | ||
// | ||
// Created by Enebin on 6/13/24. | ||
// | ||
|
||
import Vision | ||
import CoreImage | ||
import UIKit | ||
|
||
struct FaceImageTool { | ||
func preprocessImage(image: CGImage) -> CGImage? { | ||
// Step 1: Convert to Grayscale | ||
guard let grayscaleImage = convertToGrayscale(image: image) else { return nil } | ||
|
||
// Step 2: Resize | ||
guard let resizedImage = resizeImage(image: grayscaleImage, targetSize: CGSize(width: 48, height: 48)) else { return nil } | ||
|
||
// Step 3: Normalize | ||
guard let normalizedImage = normalizeImage(image: resizedImage) else { return nil } | ||
|
||
return normalizedImage | ||
} | ||
|
||
/// Extracts faces from the given CGImage asynchronously. | ||
/// - Parameter cgImage: The source CGImage from which to extract faces. | ||
/// - Returns: An array of VNFaceObservation objects representing the detected faces. | ||
/// - Throws: An error if face detection fails. | ||
func extractFaces(from cgImage: CGImage) throws -> [VNFaceObservation] { | ||
let request = VNDetectFaceRectanglesRequest() | ||
let handler = VNImageRequestHandler(cgImage: cgImage, options: [:]) | ||
try handler.perform([request]) | ||
let results = request.results | ||
|
||
return results ?? [] | ||
} | ||
|
||
/// Crops a face from the given image using the specified bounding box. | ||
/// - Parameters: | ||
/// - image: The source CGImage from which to crop the face. | ||
/// - boundingBox: The CGRect representing the bounding box of the face. | ||
/// - Returns: A CGImage representing the cropped face, or nil if cropping fails. | ||
func cropFace(from image: CGImage, boundingBox: CGRect) -> CGImage? { let width = boundingBox.width * CGFloat(image.width) | ||
let height = boundingBox.height * CGFloat(image.height) | ||
let x = boundingBox.minX * CGFloat(image.width) | ||
let y = (1 - boundingBox.minY) * CGFloat(image.height) - height | ||
|
||
let cropRect = CGRect(x: x, y: y, width: width, height: height) | ||
|
||
// Extract the cropped CGImage from the CGImage | ||
guard let croppedCGImage = image.cropping(to: cropRect) else { return nil } | ||
|
||
return croppedCGImage | ||
} | ||
} | ||
|
||
private extension FaceImageTool { | ||
func convertToGrayscale(image: CGImage) -> CGImage? { | ||
let ciImage = CIImage(cgImage: image) | ||
|
||
let grayscaleFilter = CIFilter(name: "CIPhotoEffectMono") | ||
grayscaleFilter?.setValue(ciImage, forKey: kCIInputImageKey) | ||
guard let outputCIImage = grayscaleFilter?.outputImage else { return nil } | ||
|
||
let context = CIContext() | ||
guard let cgImage = context.createCGImage(outputCIImage, from: outputCIImage.extent) else { return nil } | ||
|
||
return cgImage | ||
} | ||
|
||
func resizeImage(image: CGImage, targetSize: CGSize) -> CGImage? { | ||
let width = targetSize.width | ||
let height = targetSize.height | ||
let colorSpace = CGColorSpaceCreateDeviceGray() | ||
let context = CGContext(data: nil, width: Int(width), height: Int(height), bitsPerComponent: 8, bytesPerRow: Int(width), space: colorSpace, bitmapInfo: CGImageAlphaInfo.none.rawValue) | ||
|
||
context?.interpolationQuality = .high | ||
context?.draw(image, in: CGRect(x: 0, y: 0, width: width, height: height)) | ||
|
||
return context?.makeImage() | ||
} | ||
|
||
func normalizeImage(image: CGImage) -> CGImage? { | ||
let width = image.width | ||
let height = image.height | ||
let colorSpace = CGColorSpaceCreateDeviceGray() | ||
var pixelData = [UInt8](repeating: 0, count: width * height) | ||
|
||
let context = CGContext(data: &pixelData, width: width, height: height, bitsPerComponent: 8, bytesPerRow: width, space: colorSpace, bitmapInfo: CGImageAlphaInfo.none.rawValue) | ||
context?.draw(image, in: CGRect(x: 0, y: 0, width: width, height: height)) | ||
|
||
// Normalize pixel data | ||
let normalizedPixelData = pixelData.map { Float($0) / 255.0 } | ||
var denormalizedPixelData = normalizedPixelData.map { UInt8($0 * 255.0) } | ||
|
||
// Create new CGImage | ||
let newContext = CGContext(data: &denormalizedPixelData, width: width, height: height, bitsPerComponent: 8, bytesPerRow: width, space: colorSpace, bitmapInfo: CGImageAlphaInfo.none.rawValue) | ||
guard let newCgImage = newContext?.makeImage() else { return nil } | ||
|
||
return newCgImage | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import Foundation | ||
import SwiftUI | ||
import Vision | ||
|
||
@available(iOS 15.0, *) | ||
public struct Mentalist { | ||
static private let core = MentalistCore() | ||
|
||
public static func analyze(cgImage: CGImage) throws -> [EmotionAnalysis] { | ||
let mlModel = try VNCoreMLModel(for: FacialExpressionModel().model) | ||
return try core.analyze(cgImage: cgImage, model: mlModel) | ||
} | ||
|
||
public static func analyze(uiImage: UIImage) throws -> [EmotionAnalysis] { | ||
guard let cgImage = uiImage.cgImage else { | ||
throw NSError(domain: "UIImage to CGImage conversion failed", code: 0) | ||
} | ||
|
||
return try analyze(cgImage: cgImage) | ||
} | ||
|
||
public static func analyze(image: Image) throws -> [EmotionAnalysis] { | ||
guard let cgImage = image.asUIImage().cgImage else { | ||
throw NSError(domain: "Image to CGImage conversion failed", code: 0) | ||
} | ||
|
||
return try analyze(cgImage: cgImage) | ||
} | ||
} |
Oops, something went wrong.