Cách sử dụng API DALL-E trong Swift — Tạo khuôn mặt
Xây dựng ứng dụng SwiftUI để chỉnh sửa khuôn mặt trong hình ảnh hiện có bằng API của OpenAI. Chúng tôi sẽ sử dụng yêu cầu phát hiện khuôn mặt Vision để che giấu khuôn mặt
![](https://post.nghiatu.com/assets/images/m/max/724/1*D2oOJhZXJZILWmyGEV6ToA.png)
Nhóm OpenAI gần đây đã triển khai API DALL-E trong bản beta công khai. Tại thời điểm viết bài, có ba điểm cuối API sau:
/generations
- Tạo hình ảnh từ dấu nhắc văn bản/edits
— Chỉnh sửa hình ảnh gốc dựa trên lời nhắc văn bản bằng cách tạo mặt nạ cho một vùng/variations
- Tạo các biến thể của một hình ảnh
Để tìm hiểu sâu hơn về API và tìm hiểu về triển khai Python và Node.js của chúng, hãy đọc hướng dẫn tham khảo của OpenAI tại đây . Chúng tôi sẽ sử dụng triển khai CURL để xây dựng URLRequest của chúng tôi trong Swift. Trong khi bạn đang ở đó, hãy tạo khóa API OpenAI của bạn từ bảng điều khiển của họ . Chúng tôi sẽ cần điều đó để chạy ứng dụng của chúng tôi đang hoạt động bằng Swift.
Mục tiêu của chúng tôi
Đây là quy trình xây dựng ứng dụng SwiftUI do OpenAI cung cấp:
- Tạo SwiftUI TabView với nội dung trên hai tab để chạy các API khác nhau — cụ thể là
generation
vàedits
. - Tận dụng khung Vision
VNDetectFaceRectanglesRequest
để phát hiện khuôn mặt và cắt chúng ra để tạo hình ảnh mặt nạ cho/edits
điểm cuối. - Sử dụng Swift
URLSession
với async/await để chạy các yêu cầu API của chúng tôi. Chúng tôi sẽ chuẩn bịMultipartFormDataRequest
để tải lên dữ liệu hình ảnh thông qua các yêu cầu dữ liệu đa phần/biểu mẫu.
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)
}
}
Gọi API thế hệ trong Swift
Hãy thiết lập một ObservableObject
lớp để tìm nạp các câu chuyện từ API và chuyển chúng đến giao diện 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 ?? []
}
}
- Chúng tôi đã đánh dấu
generateImage
chức năngasync throws
này để sử dụng cú pháp async/await mới với URLSession (và bỏ qua các trình xử lý hoàn thành). - Chúng tôi
URLRequest
bao gồm hai trường tiêu đề và phần thân JSON. - Chúng tôi lấy dữ liệu và giải mã nó bằng cách sử dụng
JSONDecoder()
thành mộtDALLEResponse
cấu trúc. Trường dữ liệuDALLEResponse
chứa một mảng các chuỗi URL. Cuối cùng, chúng tôi sẽ trả lại mảng này[Photo]
để điền vào danh sách một hoặc nhiềuAsyncImage
trong 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)
}
}
}
}
Tạo khuôn mặt bằng API chỉnh sửa hình ảnh và Khung tầm nhìn
Khả năng chỉnh sửa hình ảnh gốc và tạo ra các bản phác thảo là API hấp dẫn hơn để sử dụng (tôi nghĩ vậy!). Từ hoán đổi khuôn mặt cho đến tạo ra các bộ trang phục thời trang khác nhau, chúng ta có thể làm được rất nhiều việc bằng cách tạo mặt nạ cho các vùng trong một hình ảnh và sử dụng lời nhắc để mô tả hình ảnh mới.
Trong phần này, chúng ta sẽ sử dụng Vision
khung để chạy yêu cầu phát hiện khuôn mặt, vẽ các hộp giới hạn trên khuôn mặt được phát hiện, xóa khu vực đó để tạo hình ảnh che bằng Core Graphics và chạy yêu cầu nhiều phần để tạo hình ảnh mới.
Hãy bắt đầu bằng cách thiết lập yêu cầu Tầm nhìn của chúng tôi.
Thiết lập yêu cầu phát hiện khuôn mặt tầm nhìn
Đây là mã để bắt đầu và thực hiện một 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)
}
}
}
}
Tôi đã nhận xét @Published
trình bao bọc thuộc tính trên faceRect
. Nhưng lý tưởng nhất là bạn có thể sử dụng nó để quan sát các thay đổi trong chế độ xem SwiftUI của mình và vẽ các hộp giới hạn (sau khi chuyển đổi tọa độ Tầm nhìn sang hệ thống SwiftUI) với overlay
và Rectangle
các hình dạng như sau:
.overlay{
Rectangle()
.stroke(Color.green,lineWidth: 3.0)
.frame(width: rect.width, height: rect.height)
.position(x: rect.midX, y: rect.midY)
}
Vì vậy, chúng tôi có hình ảnh đầu vào và VNFaceObservation
kết quả được truy xuất từ yêu cầu Tầm nhìn. Trước khi chúng tôi chuyển sang cắt xén và tạo hình ảnh được che, hãy chuyển đổi tọa độ Tầm nhìn thành hệ thống UIKit.
Vì tọa độ Tầm nhìn được chuẩn hóa (điều này có nghĩa là góc dưới bên trái là gốc) và không phụ thuộc vào độ phân giải màn hình, chúng tôi sẽ chuyển đổi và chia tỷ lệ hộp bằng mã sau:
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
}
Đây là mã để cắt hộp giới hạn khỏi hình ảnh và thay thế nó bằng một vùng trong suốt:
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
}
Chức năng cập nhật của chúng tôi maskFaceUsingVisionRequest
trông như thế này:
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)
}
}
}
Tất cả các thiết lập! Chúng tôi đã quản lý để tạo ra một hình ảnh đeo mặt nạ. Đã đến lúc chạy API DALL-E của chúng tôi!
Tải hình ảnh lên bằng cách sử dụng URLSession tới API DALL-E
API yêu cầu hình ảnh phải có hình bình phương, nhỏ hơn <4 MB, với hình ảnh được che có kênh alpha.
Đây là mã để thực hiện yêu cầu mạng của chúng tôi bằng cách sử dụng 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 ?? []
}
Cấu MultiPartFormDataRequest
trúc được sử dụng để chuẩn bị một yêu cầu nhiều phần với boundary
, Content-Disposation
và các nội dung ưa thích khác. Xem cách tải hình ảnh và biểu mẫu lên máy chủ của Donny Wals bằng cách sử dụng URLSession
hướng dẫn để tìm hiểu những điểm phức tạp của việc xây dựng yêu cầu dữ liệu biểu mẫu nhiều phần.
Với tính URLRequest
năng tải lên hình ảnh và dữ liệu biểu mẫu đã sẵn sàng, chúng ta sẽ gọi generateEditedImage
hàm từ chế độ xem SwiftUI theo cách sau:
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)
}
}
}
})
}
//....
}
}
![](https://post.nghiatu.com/assets/images/m/max/724/1*3L4Z4k_3TcnUMZ0CkrrqRg.png)
![](https://post.nghiatu.com/assets/images/m/max/724/1*St44LFBJoiu2GiB7vatmqA.png)
![](https://post.nghiatu.com/assets/images/m/max/724/1*qyATTnucm5byYjFlUroDNg.png)
Điều đó kết thúc hướng dẫn này. Dự án có sẵn trong kho lưu trữ GitHub của tôi . Hãy thử thử với nhiều khuôn mặt trong một bức ảnh nhóm, lặp đi lặp lại việc tạo mặt nạ cho chúng hoặc sử dụng variations
điểm cuối để tạo các hình ảnh khác nhau.
Cảm ơn vì đã đọc.