Swift抠图工具实现思路与代码详解
- 实现思路
- 代码详解
- 1. 图像选择模块
- 2. 抠图核心算法(Vision框架实现)
- 3. 传统抠图算法(色差法)
- 4. UI界面与交互(SwiftUI实现)
- 性能优化技巧
- 高级功能扩展
- 注意事项
实现思路
1. 核心流程
2. 关键技术点
- 图像选择:使用UIImagePickerController或PHPickerViewController
- 图像处理:使用Core Image框架进行图像处理
- 抠图算法:
- 基于Vision框架的人物分割(iOS 15+)
- 基于Core ML的深度学习模型
- 传统算法(色差法、边缘检测)
- 透明背景处理:使用CGContext处理alpha通道
- 性能优化:异步处理、图像压缩
代码详解
1. 图像选择模块
import SwiftUI
import PhotosUIstruct ImagePicker: UIViewControllerRepresentable {@Binding var selectedImage: UIImage?func makeUIViewController(context: Context) -> PHPickerViewController {var config = PHPickerConfiguration()config.filter = .imagesconfig.selectionLimit = 1let picker = PHPickerViewController(configuration: config)picker.delegate = context.coordinatorreturn picker}func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) {}func makeCoordinator() -> Coordinator {Coordinator(self)}class Coordinator: NSObject, PHPickerViewControllerDelegate {let parent: ImagePickerinit(_ parent: ImagePicker) {self.parent = parent}func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {picker.dismiss(animated: true)guard let provider = results.first?.itemProvider else { return }if provider.canLoadObject(ofClass: UIImage.self) {provider.loadObject(ofClass: UIImage.self) { [weak self] image, error inDispatchQueue.main.async {if let image = image as? UIImage {self?.parent.selectedImage = image}}}}}}
}
2. 抠图核心算法(Vision框架实现)
import Visionclass ImageMattingService {func removeBackground(from image: UIImage, completion: @escaping (UIImage?) -> Void) {guard let cgImage = image.cgImage else {completion(nil)return}let request = VNGenerateForegroundInstanceMaskRequest()let handler = VNImageRequestHandler(cgImage: cgImage)DispatchQueue.global(qos: .userInitiated).async {do {try handler.perform([request])guard let result = request.results?.first else {DispatchQueue.main.async { completion(nil) }return}let mask = try result.generateScaledMaskForImage(forInstances: result.allInstances, from: handler)let outputImage = self.applyMask(to: image, mask: mask)DispatchQueue.main.async {completion(outputImage)}} catch {print("Error: $error)")DispatchQueue.main.async { completion(nil) }}}}private func applyMask(to image: UIImage, mask: CVPixelBuffer) -> UIImage? {guard let cgImage = image.cgImage else { return nil }let ciImage = CIImage(cgImage: cgImage)let maskImage = CIImage(cvPixelBuffer: mask)let scaleX = ciImage.extent.width / CGFloat(CVPixelBufferGetWidth(mask))let scaleY = ciImage.extent.height / CGFloat(CVPixelBufferGetHeight(mask))let scaledMask = maskImage.transformed(by: CGAffineTransform(scaleX: scaleX, y: scaleY))guard let filter = CIFilter(name: "CIBlendWithMask") else { return nil }filter.setValue(ciImage, forKey: kCIInputImageKey)filter.setValue(scaledMask, forKey: kCIInputMaskImageKey)guard let outputCIImage = filter.outputImage else { return nil }let context = CIContext()guard let outputCGImage = context.createCGImage(outputCIImage, from: outputCIImage.extent) else {return nil}return UIImage(cgImage: outputCGImage)}
}
3. 传统抠图算法(色差法)
extension UIImage {func removeBackgroundByColorTolerance(tolerance: CGFloat = 0.1) -> UIImage? {guard let cgImage = self.cgImage else { return nil }let width = cgImage.widthlet height = cgImage.heightlet bytesPerPixel = 4let bytesPerRow = bytesPerPixel * widthlet bitsPerComponent = 8let colorSpace = CGColorSpaceCreateDeviceRGB()let rawData = UnsafeMutablePointer<UInt8>.allocate(capacity: height * bytesPerRow)defer { rawData.deallocate() }let context = CGContext(data: rawData,width: width,height: height,bitsPerComponent: bitsPerComponent,bytesPerRow: bytesPerRow,space: colorSpace,bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)context?.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height))let backgroundPixel = (x: 0, y: 0)let byteIndex = (bytesPerRow * backgroundPixel.y) + bytesPerPixel * backgroundPixel.xlet r = CGFloat(rawData[byteIndex]) / 255.0let g = CGFloat(rawData[byteIndex + 1]) / 255.0let b = CGFloat(rawData[byteIndex + 2]) / 255.0for y in 0..<height {for x in 0..<width {let pixelIndex = (bytesPerRow * y) + bytesPerPixel * xlet pixelR = CGFloat(rawData[pixelIndex]) / 255.0let pixelG = CGFloat(rawData[pixelIndex + 1]) / 255.0let pixelB = CGFloat(rawData[pixelIndex + 2]) / 255.0let diffR = abs(pixelR - r)let diffG = abs(pixelG - g)let diffB = abs(pixelB - b)if diffR < tolerance && diffG < tolerance && diffB < tolerance {rawData[pixelIndex + 3] = 0 }}}guard let newCGImage = context?.makeImage() else { return nil }return UIImage(cgImage: newCGImage)}
}
4. UI界面与交互(SwiftUI实现)
struct ContentView: View {@State private var selectedImage: UIImage?@State private var processedImage: UIImage?@State private var isProcessing = false@State private var showImagePicker = falseprivate let mattingService = ImageMattingService()var body: some View {VStack {if let image = processedImage ?? selectedImage {Image(uiImage: image).resizable().scaledToFit().frame(maxHeight: 400).padding()} else {Text("选择图片开始抠图").frame(height: 300)}if isProcessing {ProgressView("处理中...").padding()}HStack {Button("选择图片") {showImagePicker = true}.padding().background(Color.blue).foregroundColor(.white).cornerRadius(8)if selectedImage != nil {Button("开始抠图") {processImage()}.padding().background(Color.green).foregroundColor(.white).cornerRadius(8).disabled(isProcessing)}if processedImage != nil {Button("保存图片") {saveImage()}.padding().background(Color.purple).foregroundColor(.white).cornerRadius(8)}}}.sheet(isPresented: $showImagePicker) {ImagePicker(selectedImage: $selectedImage)}}private func processImage() {guard let image = selectedImage else { return }isProcessing = trueprocessedImage = nilif #available(iOS 15.0, *) {mattingService.removeBackground(from: image) { result inisProcessing = falseprocessedImage = result}} else {DispatchQueue.global(qos: .userInitiated).async {let result = image.removeBackgroundByColorTolerance()DispatchQueue.main.async {isProcessing = falseprocessedImage = result}}}}private func saveImage() {guard let image = processedImage else { return }UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)let alert = UIAlertController(title: "保存成功", message: "图片已保存到相册", preferredStyle: .alert)alert.addAction(UIAlertAction(title: "确定", style: .default))UIApplication.shared.windows.first?.rootViewController?.present(alert, animated: true)}
}
性能优化技巧
- 图像预处理
func resizeImage(_ image: UIImage, maxDimension: CGFloat) -> UIImage {let aspectRatio = image.size.width / image.size.heightvar newSize: CGSizeif aspectRatio > 1 {newSize = CGSize(width: maxDimension, height: maxDimension / aspectRatio)} else {newSize = CGSize(width: maxDimension * aspectRatio, height: maxDimension)}let renderer = UIGraphicsImageRenderer(size: newSize)return renderer.image { _ inimage.draw(in: CGRect(origin: .zero, size: newSize))}
}
- 异步处理
DispatchQueue.global(qos: .userInitiated).async {let result = processImage(image)DispatchQueue.main.async {self.imageView.image = result}
}
- 内存管理
autoreleasepool {
}
高级功能扩展
- 边缘优化
func refineEdges(of image: UIImage) -> UIImage? {guard let ciImage = CIImage(image: image) else { return nil }let blurFilter = CIFilter.gaussianBlur()blurFilter.inputImage = ciImageblurFilter.radius = 1.5let sharpenFilter = CIFilter.sharpenLuminance()sharpenFilter.inputImage = blurFilter.outputImagesharpenFilter.sharpness = 0.5let context = CIContext()guard let outputCIImage = sharpenFilter.outputImage,let cgImage = context.createCGImage(outputCIImage, from: outputCIImage.extent) else {return nil}return UIImage(cgImage: cgImage)
}
- 背景替换
func replaceBackground(with backgroundImage: UIImage, for foreground: UIImage) -> UIImage? {guard let backgroundCG = backgroundImage.cgImage,let foregroundCG = foreground.cgImage else { return nil }let size = CGSize(width: max(backgroundImage.size.width, foreground.size.width),height: max(backgroundImage.size.height, foreground.size.height))let renderer = UIGraphicsImageRenderer(size: size)return renderer.image { context inbackgroundImage.draw(in: CGRect(origin: .zero, size: size))foreground.draw(in: CGRect(origin: .zero, size: size))}
}
注意事项
- 权限处理
<key>NSPhotoLibraryUsageDescription</key>
<string>需要访问相册以保存处理后的图片</string>
- 设备兼容性
- Vision框架需要iOS 15+
- 对于旧设备提供传统算法备选方案
- 性能考虑
- 大图处理可能导致内存峰值,建议添加最大尺寸限制
- 提供进度指示器改善用户体验
- 错误处理
- 添加全面的错误处理机制
- 提供用户友好的错误提示
这个抠图工具实现结合了现代AI技术和传统图像处理算法,提供了良好的用户体验和高效的抠图能力。开发者可以根据目标用户群体和设备要求选择合适的实现方案。