วิธีใช้ DALL-E API ใน Swift — สร้างใบหน้า
สร้างแอป SwiftUI เพื่อแก้ไขใบหน้าในภาพที่มีอยู่ด้วย API ของ OpenAI เราจะใช้คำขอการตรวจจับใบหน้าด้วยการมองเห็นเพื่อปกปิดใบหน้า
ทีมงาน OpenAI เพิ่งเปิดตัว DALL-E API ในรุ่นเบต้าสาธารณะ ณ เวลาที่เขียน มีจุดสิ้นสุด API สามรายการต่อไปนี้:
/generations
— การสร้างรูปภาพจากข้อความแจ้ง/edits
— การแก้ไขภาพต้นฉบับตามข้อความแจ้งโดยการปิดบังขอบเขต/variations
— การสร้างรูปแบบต่างๆ ของรูปภาพ
หากต้องการเจาะลึกลงไปใน API และเรียนรู้เกี่ยวกับการใช้งาน Python และ Node.js โปรดอ่านคู่มืออ้างอิงของ OpenAI ที่นี่ เราจะใช้งาน CURL เพื่อสร้าง URLRequest ใน Swift ขณะที่คุณดำเนินการอยู่ ให้สร้างคีย์ OpenAI API ของคุณจากคอนโซล เราต้องการสิ่งนั้นเพื่อเรียกใช้แอปพลิเคชันของเราโดยใช้ Swift
เป้าหมายของพวกเรา
ขั้นตอนการสร้างแอปพลิเคชัน SwiftUI ที่ขับเคลื่อนด้วย OpenAI มีดังนี้
- สร้าง SwiftUI TabView ที่มีเนื้อหาในสองแท็บเพื่อเรียกใช้ API ที่แตกต่าง กันได้แก่
generation
และedits
- ใช้ประโยชน์จากเฟรมเวิร์กการมองเห็น
VNDetectFaceRectanglesRequest
เพื่อตรวจจับใบหน้าและครอบตัดใบหน้าเพื่อสร้างอิมเมจมาสก์สำหรับ/edits
จุดสิ้นสุด - ใช้ Swift
URLSession
กับ async/await เพื่อเรียกใช้คำขอ API ของเรา เราจะเตรียมการMultipartFormDataRequest
เพื่ออัปโหลดข้อมูลรูปภาพผ่านคำขอข้อมูลหลายส่วน/แบบฟอร์ม
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)
}
}
เรียกใช้ Generations API ใน Swift
มาตั้งค่าObservableObject
คลาสเพื่อดึงเรื่องราวจาก API และส่งต่อไปยังอินเทอร์เฟซ 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 ?? []
}
}
- เราได้ทำเครื่องหมาย
generateImage
ฟังก์ชันasync throws
เพื่อใช้ไวยากรณ์ async/await ใหม่กับ URLSession (และข้ามตัวจัดการการทำให้เสร็จสมบูรณ์) - ของเรา
URLRequest
ประกอบด้วยฟิลด์ส่วนหัวสองฟิลด์และเนื้อหา JSON - เราจับข้อมูลและถอดรหัสโดยใช้
JSONDecoder()
โครงสร้างDALLEResponse
ช่องข้อมูลของDALLEResponse
อาร์เรย์ของสตริง URL ในที่สุดเราจะส่งคืนอาร์เรย์นี้[Photo]
เพื่อเติมรายการหนึ่งรายการขึ้นไปAsyncImage
ใน 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)
}
}
}
}
สร้างใบหน้าโดยใช้ Edit Image API และ Vision Framework
ความสามารถในการแก้ไขภาพต้นฉบับและสร้างภาพระบายสีเป็น API ที่น่าสนใจมากกว่าที่จะใช้ (ฉันคิดว่า!) ตั้งแต่การสลับใบหน้าไปจนถึงการสร้างชุดแฟชั่นต่างๆ มีอะไรมากมายที่เราสามารถทำได้โดยการปิดบังบริเวณในภาพและใช้ข้อความแจ้งเพื่ออธิบายภาพใหม่
ในส่วนนี้ เราจะใช้Vision
เฟรมเวิร์กเพื่อเรียกใช้คำขอการตรวจจับใบหน้า วาดกรอบขอบเขตบนใบหน้าที่ตรวจพบ ลบพื้นที่นั้นเพื่อสร้างภาพมาสก์โดยใช้กราฟิกหลัก และเรียกใช้คำขอหลายส่วนเพื่อสร้างภาพใหม่
เริ่มต้นด้วยการตั้งค่าคำขอวิสัยทัศน์ของเรา
การตั้งค่าคำขอตรวจจับใบหน้าด้วยการมองเห็น
นี่คือรหัสเพื่อเริ่มต้นและดำเนินการ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)
}
}
}
}
ฉันได้แสดงความคิดเห็นใน@Published
wrapper คุณสมบัติในfaceRect
. แต่คุณสามารถใช้มันเพื่อสังเกตการเปลี่ยนแปลงในมุมมอง SwiftUI และวาดกล่องขอบเขต (หลังจากแปลงพิกัดการมองเห็นเป็นระบบ SwiftUI) ด้วยoverlay
และRectangle
รูปร่างดังนี้:
.overlay{
Rectangle()
.stroke(Color.green,lineWidth: 3.0)
.frame(width: rect.width, height: rect.height)
.position(x: rect.midX, y: rect.midY)
}
ดังนั้นเราจึงมีภาพอินพุตและดึงVNFaceObservation
ผลลัพธ์จากคำขอวิสัยทัศน์ ก่อนที่เราจะไปยังการครอบตัดและสร้างภาพมาสก์ เรามาแปลงพิกัดการมองเห็นเป็นระบบ UIKit กันก่อน
เนื่องจากพิกัดการมองเห็นถูกทำให้เป็นมาตรฐาน (ซึ่งหมายถึงมุมซ้ายล่างเป็นจุดกำเนิด) และไม่ขึ้นกับความละเอียดของหน้าจอ เราจะแปลงและปรับขนาดกล่องโดยใช้รหัสต่อไปนี้:
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
}
นี่คือโค้ดสำหรับตัดขอบกล่องออกจากภาพและแทนที่ด้วยพื้นที่โปร่งใส:
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
}
ฟังก์ชัน ที่อัปเดตของเราmaskFaceUsingVisionRequest
มีลักษณะดังนี้:
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)
}
}
}
ทุกชุด! เราสามารถสร้างภาพมาสก์ได้แล้ว ได้เวลาเรียกใช้ DALL-E API ของเราแล้ว!
อัปโหลดรูปภาพโดยใช้ URLSession ไปยัง DALL-E API
API กำหนดให้รูปภาพมีรูปร่างเป็นสี่เหลี่ยมจัตุรัส น้อยกว่า <4MB โดยรูปภาพที่สวมหน้ากากมีช่องอัลฟา
นี่คือรหัสเพื่อดำเนินการคำขอเครือข่ายของเราโดยใช้ 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 ?? []
}
โครงสร้างMultiPartFormDataRequest
ใช้เพื่อเตรียมคำขอหลายส่วนด้วยboundary
, Content-Disposation
, และสิ่งแฟนซีอื่นๆ ดูการอัปโหลดรูปภาพและแบบฟอร์มของ Donny Wals ไปยังเซิร์ฟเวอร์โดยใช้URLSession
คำแนะนำเพื่อเรียนรู้ความซับซ้อนของการสร้างคำขอข้อมูลแบบฟอร์มหลายส่วน
เมื่อพร้อมURLRequest
สำหรับการอัปโหลดรูปภาพและข้อมูลแบบฟอร์ม เราจะเรียกใช้generateEditedImage
ฟังก์ชันจากมุมมอง SwiftUI ด้วยวิธีต่อไปนี้:
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)
}
}
}
})
}
//....
}
}
สรุปบทช่วยสอนนี้ โครงการมีอยู่ในที่เก็บ GitHubของ ฉัน ลองเล่นหลายๆ ใบหน้าในรูปภาพกลุ่ม พรางใบหน้าซ้ำๆ หรือใช้variations
จุดสิ้นสุดเพื่อสร้างภาพต่างๆ
ขอบคุณที่อ่าน.