Comment utiliser l'API DALL-E dans Swift - Générer des visages
Créez une application SwiftUI pour modifier les visages dans les images existantes avec l'API d'OpenAI. Nous utiliserons une requête de détection de visage Vision pour masquer les visages

L'équipe OpenAI a récemment déployé l'API DALL-E dans une version bêta publique. Au moment de la rédaction, les trois points de terminaison d'API suivants sont disponibles :
/generations
- Génération d'images à partir d'une invite de texte/edits
— Modification d'images originales basées sur une invite de texte en masquant une région/variations
— Génération de variations d'une image
Pour approfondir les API et en savoir plus sur leurs implémentations Python et Node.js, lisez le guide de référence d'OpenAI ici . Nous utiliserons les implémentations CURL pour créer notre URLRequest dans Swift. Pendant que vous y êtes, générez votre clé API OpenAI à partir de leur console . Nous en aurons besoin pour exécuter notre application en action avec Swift.
Notre objectif
Voici le processus de création d'une application SwiftUI basée sur OpenAI :
- Créez un SwiftUI TabView avec du contenu sur deux onglets pour exécuter les différentes API, à savoir
generation
etedits
. - Tirez parti du cadre Vision
VNDetectFaceRectanglesRequest
pour détecter les visages et rognez-les pour générer une image de masque pour le point de/edits
terminaison. - Utilisez Swift
URLSession
avec async/wait pour exécuter nos requêtes API. Nous préparerons unMultipartFormDataRequest
pour télécharger des données d'image via des demandes de données en plusieurs parties/formulaires.
enum OpenAIEndpoint: String{
private var baseURL: String { return "https://api.openai.com/v1/images/" }
case generations
case edits
var url: URL {
guard let url = URL(string: baseURL) else {
preconditionFailure("The url is not valid")
}
return url.appendingPathComponent(self.rawValue)
}
}
Appeler l'API Generations dans Swift
Configurons une ObservableObject
classe pour récupérer les histoires de l'API et les transmettre à l'interface SwiftUI :
class OpenAIService: ObservableObject{
let api_key_free = "<INSERT_API_KEY_HERE>"
func generateImage(from prompt: String) async throws -> [Photo]{
var request = URLRequest(url: OpenAIEndpoint.generations.url)
request.setValue("Bearer \(api_key_free)", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
let parameters: [String: Any] = [
"prompt": prompt,
"n": 1,
"size": "256x256"
]
let jsonData = try? JSONSerialization.data(withJSONObject: parameters)
request.httpBody = jsonData
let (data, response) = try await URLSession.shared.data(for: request)
let dalleResponse = try? JSONDecoder().decode(DALLEResponse.self, from: data)
return dalleResponse?.data ?? []
}
}
- Nous avons marqué la
generateImage
fonction avecasync throws
pour utiliser la nouvelle syntaxe async/wait avec URLSession (et ignorer les gestionnaires d'achèvement). - Notre
URLRequest
comprend deux champs d'en-tête et un corps JSON. - Nous récupérons les données et les décodons en utilisant
JSONDecoder()
uneDALLEResponse
structure. Le champ de données duDALLEResponse
contient un tableau de chaînes d'URL. Nous renverrons éventuellement ce tableau[Photo]
pour remplir une liste d'un ou plusieursAsyncImage
dans SwiftUI.
struct DALLEResponse: Decodable {
let created: Int
let data: [Photo]
}
struct Photo: Decodable {
let url: String
}
struct ContentView: View {
@ObservedObject var fetcher = OpenAIService()
@State var photos : [Photo] = []
@State private var textPrompt: String = ""
var body: some View {
VStack {
List {
ForEach(photos, id: \.url) { photo in
AsyncImage(url: URL(string: photo.url)) { image in
image
.resizable()
.aspectRatio(contentMode: .fit)
} placeholder: {
Image(systemName: "photo.fill")
}.frame(maxWidth: .infinity, maxHeight: 500)
.listRowInsets(.init(.zero))
}
}
TextField("Enter the prompt", text: $textPrompt)
.textFieldStyle(RoundedTextFieldStyle())
.padding()
Button(action: runOpenAIGeneration, label: {Text("Generate Text From Prompt")})
}
.padding()
}
func runOpenAIGeneration(){
Task{
do{
self.photos = try await fetcher.generateImage(from: textPrompt)
}catch(let error){
print(error)
}
}
}
}
Générer des visages à l'aide de l'API Edit Image et de Vision Framework
La possibilité d'éditer des images originales et de créer des peintures est l'API la plus intrigante à utiliser (je pense !). De l'échange de visages à la génération de différentes tenues de mode, nous pouvons faire tellement de choses en masquant des régions dans une image et en utilisant des invites pour décrire la nouvelle image.
Dans cette section, nous utiliserons le Vision
framework pour exécuter une requête de détection de visage, dessiner des cadres de délimitation sur le visage détecté, effacer cette zone pour créer une image masquée à l'aide de Core Graphics et exécuter la requête en plusieurs parties pour générer une nouvelle image.
Commençons par configurer notre requête Vision.
Mise en place d'une demande de détecteur visuel de visage
Voici le code pour initier et effectuer un VNDetectFaceRectanglesRequest
:
class VisionRequester: ObservableObject{
@Published var maskedImage: UIImage?
//1 - @Published
var faceRect: VNFaceObservation?
func maskFaceUsingVisionRequest(inputImage: UIImage){
let request = VNDetectFaceRectanglesRequest()
let handler = VNImageRequestHandler(cgImage: inputImage.cgImage!, options: [:])
Task{
do{
try handler.perform([request])
guard let results = request.results else {return}
faceRect = results.first //2
guard let faceRect else {return}
//3 - convert bounding boxes to UIKit coordinate system and erase face.
}catch{
print(error)
}
}
}
}
J'ai commenté le @Published
wrapper de propriété sur faceRect
. Mais vous pourriez idéalement l'utiliser pour observer les changements dans votre vue SwiftUI et dessiner des cadres de délimitation (après avoir converti les coordonnées Vision au système SwiftUI) avec overlay
et Rectangle
des formes comme celle-ci :
.overlay{
Rectangle()
.stroke(Color.green,lineWidth: 3.0)
.frame(width: rect.width, height: rect.height)
.position(x: rect.midX, y: rect.midY)
}
Nous avons donc l'image d'entrée et le VNFaceObservation
résultat récupéré de la requête Vision. Avant de passer au recadrage et à la création d'une image masquée, convertissons les coordonnées Vision au système UIKit.
Étant donné que les coordonnées Vision sont normalisées (cela signifie que le coin inférieur gauche est l'origine) et indépendantes de la résolution de l'écran, nous allons transformer et mettre à l'échelle la boîte à l'aide du code suivant :
func getBoundingBoxes(rect : VNFaceObservation, on imageSize: CGSize) -> CGRect {
let transform = CGAffineTransform(scaleX: 1, y: -1).translatedBy(x: 0, y: -imageSize.height)
let scale = CGAffineTransform.identity.scaledBy(x: imageSize.width, y: imageSize.height)
let bounds = rect.boundingBox.applying(scale).applying(transform)
return bounds
}
Voici le code pour découper la boîte englobante de l'image et la remplacer par une région transparente :
func erase(region: CGRect, from image: UIImage) -> UIImage? {
UIGraphicsBeginImageContext(image.size)
image.draw(at: CGPoint.zero)
let context = UIGraphicsGetCurrentContext()!
let bez = UIBezierPath(rect: region)
context.addPath(bez.cgPath)
context.clip()
context.clear(CGRect(x:0,y:0,width: image.size.width,height: image.size.height))
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage
}
Notre maskFaceUsingVisionRequest
fonction mise à jour ressemble à ceci :
func maskFaceUsingVisionRequest(inputImage: UIImage){
let request = VNDetectFaceRectanglesRequest()
let handler = VNImageRequestHandler(cgImage: inputImage.cgImage!, options: [:])
Task{
do{
try handler.perform([request])
guard let results = request.results else {return}
faceRect = results.first
guard let faceRect else {return}
let box = getBoundingBoxes(rect: faceRect, on: inputImage.size)
await MainActor.run{
self.maskedImage = erase(region: box, from: inputImage)
}
}catch{
print(error)
}
}
}
Tout est prêt ! Nous avons réussi à générer une image masquée. Il est temps d'exécuter notre API DALL-E !
Télécharger des images à l'aide d'URLSession vers l'API DALL-E
L'API exige que les images soient de forme carrée, inférieure à <4 Mo, l'image masquée ayant un canal alpha.
Voici le code pour exécuter notre requête réseau en utilisant async/wait :
func generateEditedImage(from image: UIImage, with mask: UIImage) async throws -> [Photo] {
guard let imageData = image.pngData() else{return []}
guard let maskData = mask.pngData() else{return []}
let formFields: [String: String] = [
"prompt": "A woman wearing a red dress with an angry face",
"size": "256x256"
]
let multipart = MultipartFormDataRequest(url: OpenAIEndpoint.edits.url)
multipart.addDataField(fieldName: "image", fileName: "image.png", data: imageData, mimeType: "image/png")
multipart.addDataField(fieldName: "mask", fileName: "mask.png", data: maskData, mimeType: "image/png")
for (key, value) in formFields {
multipart.addTextField(named: key, value: value)
}
var request = multipart.asURLRequest()
request.setValue("Bearer \(api_key_free)", forHTTPHeaderField: "Authorization")
let (data, response) = try await URLSession.shared.data(for: request)
let dalleResponse = try? JSONDecoder().decode(DALLEResponse.self, from: data)
return dalleResponse?.data ?? []
}
La MultiPartFormDataRequest
structure est utilisée pour préparer une requête en plusieurs parties avec boundary
, Content-Disposation
, et d'autres trucs fantaisistes. Consultez le guide de téléchargement d'images et de formulaires de Donny Wals sur un serveur à l'aide du URLSession
guide pour apprendre les subtilités de la création d'une demande de données de formulaire en plusieurs parties.
Avec le URLRequest
téléchargement d'images et de données de formulaire prêt, nous appellerons la generateEditedImage
fonction à partir de notre vue SwiftUI de la manière suivante :
struct EditImageView: View {
@StateObject var visionProcessing = VisionRequester()
@StateObject var fetcher = OpenAIService()
@State var inputImage : UIImage = UIImage(named: "sample")!
var body: some View {
//....
if let outputImage = self.visionProcessing.maskedImage{
Image(uiImage: outputImage)
.resizable()
.aspectRatio(contentMode:.fit)
.onReceive(self.visionProcessing.$maskedImage,
perform: { updated in
if let _ = updated?.size{
Task{
do{
self.photos = try await fetcher.generateEditedImage(from: inputImage, with: outputImage)
}catch(let error){
print(error)
}
}
}
})
}
//....
}
}



Cela conclut ce tutoriel. Le projet est disponible dans mon dépôt GitHub . Essayez de jouer avec plusieurs visages dans une photo de groupe, en les masquant de manière itérative ou en utilisant le variations
point de terminaison pour générer différentes images.
Merci d'avoir lu.