Cómo usar la API DALL-E en Swift: generar caras
Cree una aplicación SwiftUI para editar rostros en imágenes existentes con la API de OpenAI. Usaremos una solicitud de detección de rostros de Vision para enmascarar rostros

El equipo de OpenAI lanzó recientemente la API DALL-E en una versión beta pública. En el momento de escribir este artículo, están disponibles los siguientes tres puntos finales de la API:
/generations
— Generación de imágenes a partir de indicaciones de texto/edits
— Edición de imágenes originales en función de un mensaje de texto mediante el enmascaramiento de una región/variations
— Generación de variaciones de una imagen
Para profundizar en las API y conocer sus implementaciones de Python y Node.js, lea la guía de referencia de OpenAI aquí . Usaremos las implementaciones de CURL para construir nuestra URLRequest en Swift. Mientras lo hace, genere su clave API de OpenAI desde su consola . Lo necesitaremos para ejecutar nuestra aplicación en acción usando Swift.
Nuestro objetivo
Este es el proceso para crear una aplicación SwiftUI basada en OpenAI:
- Cree un SwiftUI TabView con contenido en dos pestañas para ejecutar las diferentes API, a saber
generation
yedits
. - Aproveche el marco de Vision
VNDetectFaceRectanglesRequest
para detectar caras y recortarlas para generar una imagen de máscara para el/edits
punto final. - Use Swift
URLSession
con async/await para ejecutar nuestras solicitudes de API. Prepararemos un formularioMultipartFormDataRequest
para cargar datos de imagen a través de solicitudes de datos de formulario/varias partes.
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)
}
}
Invocar la API de generaciones en Swift
Configuremos una ObservableObject
clase para obtener historias de la API y pasarlas a la interfaz de 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 ?? []
}
}
- Hemos marcado la
generateImage
función conasync throws
para usar la nueva sintaxis async/await con URLSession (y omitir los controladores de finalización). - Nuestro
URLRequest
se compone de dos campos de encabezado y un cuerpo JSON. - Tomamos los datos y los decodificamos
JSONDecoder()
en unaDALLEResponse
estructura. El campo de datos deDALLEResponse
contiene una matriz de cadenas de URL. Eventualmente devolveremos esta matriz[Photo]
para completar una lista de uno o másAsyncImage
en 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)
}
}
}
}
Genere caras utilizando la API de edición de imágenes y el marco de trabajo de Vision
La capacidad de editar imágenes originales y crear pinturas exteriores es la API más intrigante para usar (¡creo!). Desde intercambiar rostros hasta generar diferentes atuendos de moda, hay mucho que podemos hacer al enmascarar regiones en una imagen y usar indicaciones para describir la nueva imagen.
En esta sección, usaremos el Vision
marco para ejecutar una solicitud de detección de rostros, dibujar cuadros delimitadores en el rostro detectado, borrar esa área para crear una imagen enmascarada usando Core Graphics y ejecutar la solicitud de varias partes para generar una nueva imagen.
Empecemos configurando nuestra solicitud Vision.
Configuración de una solicitud de detector de visión facial
Aquí está el código para iniciar y realizar 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)
}
}
}
}
He comentado el @Published
contenedor de propiedades en faceRect
. Pero idealmente podría usarlo para observar cambios en su vista SwiftUI y dibujar cuadros delimitadores (después de convertir las coordenadas de Vision al sistema SwiftUI) con formas como esta overlay
:Rectangle
.overlay{
Rectangle()
.stroke(Color.green,lineWidth: 3.0)
.frame(width: rect.width, height: rect.height)
.position(x: rect.midX, y: rect.midY)
}
Entonces, tenemos la imagen de entrada y VNFaceObservation
el resultado obtenido de la solicitud de Vision. Antes de pasar a recortar y crear una imagen enmascarada, conviertamos las coordenadas de Vision al sistema UIKit.
Dado que las coordenadas de Vision están normalizadas (esto significa que la esquina inferior izquierda es el origen) e independientemente de la resolución de la pantalla, transformaremos y escalaremos el cuadro usando el siguiente código:
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
}
Aquí está el código para cortar el cuadro delimitador de la imagen y reemplazarlo con una región 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
}
Nuestra función actualizada maskFaceUsingVisionRequest
se ve así:
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)
}
}
}
¡Todo listo! Hemos logrado generar una imagen enmascarada. ¡Es hora de ejecutar nuestra API DALL-E!
Cargue imágenes usando URLSession a la API de DALL-E
La API requiere que las imágenes tengan forma cuadrada, menos de <4 MB, y que la imagen enmascarada tenga un canal alfa.
Aquí está el código para ejecutar nuestra solicitud de red usando async/await:
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
estructura se usa para preparar una solicitud de varias partes con boundary
, Content-Disposation
y otras cosas sofisticadas. Consulte la guía de carga de imágenes y formularios de Donny Wals a un servidor para conocer las complejidades de la creación de una solicitud de datos de formulario de varias partes.URLSession
Con la URLRequest
carga de imágenes y datos de formulario listos, invocaremos la generateEditedImage
función desde nuestra vista de SwiftUI de la siguiente manera:
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)
}
}
}
})
}
//....
}
}



Eso concluye este tutorial. El proyecto está disponible en mi repositorio de GitHub . Intente jugar con varias caras en una foto de grupo, enmascarándolas iterativamente o usando el variations
punto final para generar diferentes imágenes.
Gracias por leer.