iOS 16에서 가져온 깊이 효과 사진 효과를 달성하는 방법

저자: 페놀

0x01 서문

iOS 16 시스템은 우리에게 놀라운 데스크톱 잠금 화면 효과인 깊이 효과를 가져왔습니다. 일반 사진을 배경으로 사용할 수 있으며 동시에 적절한 위치에서 일부 데스크톱 구성 요소를 덮어 피사계 심도 효과를 형성할 수 있습니다(아래 그림 참조).

그렇다면 우리 자신의 앱에서도 비슷한 효과를 얻을 수 있을까요? 처음에는 iOS 16에 와 같은 몇 줄의 간단한 API 로 구현할 수 있는 UIVisualEffectView새로운 UIKit 컨트롤이 추가되었다고 생각했지만 결국에는 없는 것으로 나타났습니다. 주어진 그림이 레이어로 나누어진 여러 그림인 경우 구현은 단순히 샌드위치 비스킷처럼 중간에 클록 컨트롤을 샌드위치하는 것입니다. 그러나 실제로는 인터넷에서 임의로 다운로드한 한 장의 사진을 잠금화면 배경으로 설정하는 것도 이러한 효과를 얻을 수 있는 것으로 밝혀졌다. 다시 누르면 사진 속 피사체를 직접 분할하고 드래그할 수 있는 iOS 16 시스템 앨범을 연상시키는 어떤 이미지 분할 알고리즘을 사용하여 전경과 배경을 분리하여 다중 레이어 이미지를 얻었을 것입니다.

0x02 이미지 분할(Image Segmentation)

더 고전적인 이미지 분할 알고리즘은 워터셰드 알고리즘(Watershed)으로 분할하는 이미지는 매우 정확하고 엣지 처리가 매우 좋지만 전경과 배경의 대략적인 위치에 수동 스트로크가 필요합니다(한 스트로크만 괜찮습니다) ), 후자의 알고리즘은 전경과 배경을 자동으로 분리합니다.) 이는 이 문서의 완전 자동 요구 사항에 적용되지 않습니다. 최근 몇 년 동안 기계 학습의 많은 성과가 나타났으며 그 중 하나는 완전히 자동화된 이미지 분할입니다. 아니나 다를까 간단한 검색 후 Apple이 사전 훈련된 모델을 제공했음을 알았습니다.

훈련된 모델 DeeplabV3 을 다운로드하려면 Apple의 기계 학습 공식 웹 사이트 developer.apple.com/machine-lea... 를 방문하십시오 . 모델 파일을 Xcode 프로젝트로 드래그하고 선택한 후 이에 대한 몇 가지 정보를 볼 수 있습니다.

영상

사실 여기에서는 주로 모델의 입력과 출력에 초점을 맞추는데 Predictions 탭을 클릭하면 모델이 513x513 이미지의 입력을 요구하고 출력은 멤버 유형이 Int32인 2차원 배열임을 알 수 있습니다. 및 크기 513x513. 각 값은 해당 이미지 픽셀 포인트의 분류를 나타냅니다. 여기서 멤버가 단순한 Bool이 아닌 Int32인 이유는 모델이 전경과 배경뿐만 아니라 이미지를 여러 부분으로 분할할 수 있기 때문입니다. 실제로 우리는 0의 값을 배경으로 간주할 수 있고 0 이외의 값을 전경으로 간주할 수 있음을 발견했습니다.

영상

다음은 샘플 이미지에서 분할을 실행한 후 얻은 결과입니다.

영상

각각 배경과 전경인 0과 15의 두 값으로 나뉩니다.

0x03 연습

모델이 이미 존재하고 구현 계획이 거의 준비되었으며 다음 단계는 구체적인 실행입니다.

모델을 Xcode 프로젝트로 드래그하면 Xcode가 DeepLabV3이라는 클래스를 자동으로 생성합니다. 가져 오기 없이 인스턴스를 직접 만들 수 있습니다 .

    lazy var model = try! DeepLabV3(configuration: {
        let config = MLModelConfiguration()
        config.allowLowPrecisionAccumulationOnGPU = true
        config.computeUnits = .cpuAndNeuralEngine
        return config
    }())
复制代码

그런 다음 이 인스턴스를 사용하여 기계 학습 엔진을 통해 이미지를 분석하는 요청을 만들고 VNCoreMLRequest콜백에서 결과를 가져옵니다.

    lazy var request = VNCoreMLRequest(model: try! VNCoreMLModel(for: model.model)) { [unowned self] request, error in
        if let results = request.results as? [VNCoreMLFeatureValueObservation] {
            // 最终的分割结果在 arrayValue 中
            if let feature = results.first?.featureValue, let arrayValue = feature.multiArrayValue {
                let width = arrayValue.shape[0].intValue
                let height = arrayValue.shape[1].intValue
                let stride = arrayValue.strides[0].intValue
                // ...
            }
            
        }
    }
复制代码

마지막으로 적절한 위치에 요청을 VNImageRequestHandler생성합니다 .

    private func segment() {
        if let image = self.imageView.image {
            imageSize = image.size
            DispatchQueue.global().async { [unowned self] in
                self.request.imageCropAndScaleOption = .scaleFill
                let handler = VNImageRequestHandler(cgImage: image.resize(to: .init(width: 513, height: 513)).cgImage!)
                try? handler.perform([self.request])
            }
        }
    }
复制代码

알아채다:

  1. 요청의 콜백과 핸들러가 요청을 시작하는 코드는 동일한 스레드에 있으며 동기적으로 결과를 기다리고 있으므로 여기에서 하위 스레드 작업으로 디스패치하는 것이 가장 좋습니다.
  2. 요청은 imageCropAndScaleOption을 로 설정해야 합니다 .scallFill. 그렇지 않으면 기본적으로 중간 부분을 자동으로 자르고 예상치 못한 결과를 얻게 됩니다.

다음 샘플 이미지를 입력하십시오.

반환된 결과를 흑백 이미지로 arrayValue처리합니다 .

영상

상당히 정확한 것으로 확인되었습니다. 물론 코드에서 마스크로 사용하려면 배경이 완전히 투명하고 전경이 불투명한 그림으로 취급해야 합니다.

영상

마지막으로 원본 이미지를 맨 아래 레이어에, 다른 컨트롤을 중간에, 원본 이미지 + 마스크 뷰를 맨 위 레이어에 배치하여 최종 효과를 만듭니다.

영상

그 뒤에 숨겨진 실제 원리는 샌드위치 비스킷입니다.

몇 가지 추가 렌더링:

0x04 포스트스크립트

물론 이 모델이 만병통치약은 아니며, 아직은 특정 용도에 한계가 있어 사람이 있는 사진의 경우 더 잘 분할할 수 있지만, 큰 장면과 같은 풍경 사진의 경우 아예 분할이 안될 수도 있습니다. 이 기사의 데모는 Github 에서 찾을 수 있습니다 .

참조

  1. developer.apple.com/documentati…
  2. www.appcoda.com.tw/vision-pers…
  3. enlight.nyc/projects/im…

이 기사는 NetEase 클라우드 음악 기술 팀에서 게시했으며 승인 없이 모든 형태의 전재를 금지합니다. 연중무휴로 다양한 기술직을 모집하고 있습니다. 이직을 준비 중이시거나 클라우드 음악을 좋아하신다면 grp.music-fe(at)corp.netease.com에서 함께하세요!

추천

출처juejin.im/post/7197608023430283319