• Accelerate 개요

    • 기초 수학 연산들을 모든 애플 플랫폼에 동일하게 제공하기 위함
    • 성능과 효율성을 최우선으로 해서 개발됨
    • 기초 연산은 어마어마하게 많기 때문에 도메인 별로 제공
      • vDSP
      • vImage
      • vForce
      • BLAS, LAPACL, LinearAlgebra
      • Sparse BLAS, Sparse Solvers
      • BNNS
      • simd
      • Compression
  • vDSP: 신호 처리라이브러리

    • 배열에 대한 기초 연산(Add, subtract, multiply, conversion 등)

    • 이산 푸리에 코사인 변환

      • 1차 DFT, DCT, FFT(고속 푸리에 변환)
      • 2차 FFT
    • convolution(합성곱), correlation(상관성)

    • 예시 - 노이즈 제거

      • 특정 주파수 이하의 값들을 지우는 것
        • 이를 위해서는 신호를 분석해서 어떤 것이 노이즈인지 파악해야 한다.
      // 신호 변환
      let dct_Setup_FORWARD: vDSP_DFT_Setup = {
      	guard let dctSetup = vDSP_DCT_CreateSetup)
      			nil, vDSP_Length(numSamples), .II) else {
      				fatalError("can't create FORWORD vDSP_DFT_Setup")
      		}
      
      	return dctSetup
      }()
      
      var forwardDCT = [Float](repeating: 0,
      													count: numSamples)
      vDSP_DCT_Execute(dctSetup_FORWARD, noisySignalReal, &forwardDCT)
      
      // threshold 기준으로 필터링
      vDSP_vthres(forwardDCT, stride, &threshold, &forwardDCT, stride, count)
      
      // 신호 복원
      let dctSetup_INVERSE: vDSP_DFT_Setup = {
      		guard let dctSetup = vDSP_DCT_CreateSetup)
      			nil, vDSP_Length(numSamples), .III) else {
      				fatalError("can't create INVERSE vDSP_DFT_Setup")
      		}
      
      	return dctSetup
      }()
      
      vDSP_DCT_Execute(dctSetup_INVERSE, forwardDCT, &inverseDCT)
      
      // 정규화
      var divisor = Float(count)
      
      vDSP_vsdiv(inverseDCT, stride, &divisor, &inverseDCT, stride, count)* 
      
    • 예시-halftone-descreening

      • halftone으로 된 이미지를 2D FFT로 변환한다.
      • threshold 이상인 component를 제거한다.
      • 이미지를 continuous tone으로 복원한다.
      let fftSetUp: FFTSetup = {
      	let log2n = vDSP_Length(log2(1024.0 * 1024.0))
      	guard let fftSetUp = vDSP_create_fftsetup(log2n, FFTRadix(kFFTRadix2)) else {
      			fatalError("can't create FFT Setup")
      	}
      	
      	return fftSetUp
      } ()
      
      let sourceImage_floatPixels_frequency = DSPSplitComplex(
      	realp: &sourceImage_floatPixelsReal_spatial,
      	imagp: &sourceImage_floatPixelsImag_frequency)
      
      vDSP_fft2d_zrop(fftSetup, &sourceImageSplitComplex, vDSP_Stride(1), vDSP_Stride(0),
      								&sourceImage_floatPixels_frequency, vDSP_Stride(1), vDSP_Stride(0),
      								vDSP_Length(log2(Float(width))),
      								vDSP_Length(log2(Float(height))),
      								FFTDirection(kFFTDirection_Forward))
      
      • 복잡하니 자세한 건 샘플과 문서를 확인할 것. 그래도 고수준에서 뭘하는지 살펴보면
        • zvmags - frequency component의 크기(magnitude)를 확인
        • vthrsc - threshold 기준으로 값 필터링
        • vclip - mask 생성
        • zrvmul - 이미지에 마스크 적용
      • 이후 다시 복원한다.
      // 짝수번째 픽셀은 실수, 홀수번쨰 픽셀은 가수
      var floatPixels_spatial = DSPSplitComplex(realp: &floatPixelsReal_spatial,
      																					imagp: &floatPixelsImage_spatial)
      vDSP_fft2d_zrop(fftSetUp, &sourceImage_floatPixels_frequency,
      								stride, 0,
      								&floatPixels_spatial,
      								stride, 0,
      								vDSP_Length(log2(Float(width))),
      								vDSP_Length(log2(Float(height))),
      								FFTDirection(kFFTDirectional_Inverse))
      
  • simd

    • 단순화된 벡터 프로그래밍
    • 고정 크기의 작은 벡터와 행렬 타입 제공
      • 벡터: 2, 3,4, 8 , 16, 32, 64
      • 행렬: 2, 3, 4
    • 아키텍쳐마다 다른 타입과 내장식을 추상화해놓음 → 아키텍처에 대해 독립적으로 돌아감
    let x = simd_float4(1, 2, 3, 4)
    let y = simd_float4(3, 3, 3 ,3)
    
    let z = 0.5 * (x + y)
    
    • 벡터와 스칼라에 대한 사칙 연산 모두 제공
    • 공통적인 벡터 연산과 기하 연산들 제공(dot, length, clamp)
    • 초월 함수 제공
    • 사원수 제공
    let original = simd_float3(0, 0, 1)
    
    let quaternion = simd_quatf(angle: .pi / -3,
    														axis: simd_float3(1,0,0))
    let quaternion2 = simd_quatf(angle: .pi / 3,
    															axis: simd_float3(0,1,0))
    
    let quaternion3 = quaternion2 * quaternion // 교환법칙 미성립
    
    let rotatedVector = simd_act(quaternion3, original)
    
    // Slerp Interpolation -> 날카로운 방향전환
    let blue = simd_quatf(...)
    let green = simd_quatf(...)
    let red = simd_quatf(...)
    
    for t: Float in stride(from: 0, to: 1, by: 0.001) {
    		let q = simd_slerp(blue, green, t)
    		// q.act(original)의 결과로 얻은 지점에 Line Segment 추가. 
    }
    
    for t: Float in stride(from: 0, to: 1, by: 0.001) {
    		let q = simd_slerp_longest(green, red, t)
    		// q.act(original)의 결과로 얻은 지점에 Line Segment 추가. 
    }
    
    // Spline Interpolation -> 부드러운 방향전환
    
    let original = simd_float3(0, 0, 1)
    let rotations: [simd_quatf] = ...
    
    for i in 1 ... rotations.count - 3 {
    	for t: Float in stride(from: 0, to: 1, by: 0.001) {
    		let q = simd_spline(rotations[i-1],
    												rotations[i],
    												rotations[i+1],
    												rotations[i+2],
    												t)
    		// q.act(original)의 결과로 얻은 지점에 Line Segment 추가. 
    	}
    }
    
  • vImage

    • 하위 컴포넌트
      • Conversion: 포맷 전환 (RGB ↔ YCbCr)
      • Geometry: 이미지의 크기나 방향 변경
      • Convolution: blur이펙트를 위해 사용
      • Transform: 행렬 곱으로 픽셀 단위로 변경
      • Morphology: 이미지 자체가 이미지 안의 물체의 크기 등을 변경(erode, dilate)
    • 예제: 실시간 이미지 처리
      • 워크플로우

        • 이미지를 카메라로부터 받아옴
        • vImage의 input / output 버퍼 준비
        • vImage 함수로 이펙트 적용
        • 결과 이미지를 디스플레이에 뿌림
      • 이펙트 예시 - color saturation

        • Cb = ((Cb - 128) * saturation) + 128
        • Cr = ((Cr - 128) * saturation) + 128
        var preBias: Int16 = -128
        
        // Fixed-Point로 전환(Q12)
        let divisor: Int32 = 0x1000 // 2^12
        var postBias: Int32 = 128 * divisor
        
        var matrix = [ Int16(saturation * Float(divisor)) ] 
        
        vImageMatrixMultiply_Planar8(&source, &destinations, 1, 1,
        														&matrix, divisor, &preBias, &postBias,
        														vImage_Flags(kvImageNoFlags))
        
        • 이미지 가져오기
        // AVCaptureVideoDataOutputSampleBufferDelegate
        func captureOutput(_ output: AVCaptureOutput,
        									didOutput sampleBuffer: CMSampleBuffer,
        									from connection: AVCaptureConnection) {
        	// Get CVImageBuffer from CMSampleBuffer
        	let pixelBuffer = sampleBuffer.imageBuffer
        
        	// CPU가 이 메모리 영역에 접근할 수 있도록 함
        	CVPixelBufferLockBaseAddress(pixelBuffer, .readOnly)
        
        	// vImage 적용
        	...
        	
        	// 메모리를 다시 카메라에게 돌려줌
        	CVPixelBufferUnlockBaseAddress(pixelBuffer, .readOnly)
        }
        
        • buffer 준비
        // Luminalce를 위한 vImage InputBuffer 준비
        let lumaBaseAddress = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0)
        let lumaWidth = CVPixelBufferGetWidthOfPlane(pixelBuffer, 0)
        let lumaHeight = CVPixelBufferGetHeightOfPlane(pixelBuffer, 0)
        let lumaRowBytes = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0)
        var sourceLumaBuffer = vImage_Buffer(data: lumaBaseAddress,
        																		height: vImagePixelCount(lumaHeight),
        																		width: vImagePixelCount(lumaWidth),
        																		rowBytes: lumaRowBytes)
        // Chrominance를 위한 vImage Input Buffer 준비
        // ..
        
        // output buffer 준비
        var destinationBuffer = vImage_Buffer()
        
        // 메모리를 미리 준비
        vImageBuffer_Init(&destinationBuffer,
        									sourceLumaBuffer.height, sourceLumaBuffer.width,
        									cgImageFormat.bitsPerPixel, vImage_Flags(kvImageNoFlags))
        
        • 처리된 이미지를 화면에 표시
        vImageConvert_420Yp8_CbCr8ToARGB8888(&sourceLumaBuffer, &sourceChromaBuffer,
        																		&destinationBuffer, &infoYpCbCrToARGB,
        																		nil, 255, vImage_Flags(kvImageNoFlags))
        
        // 이미지 버퍼를 복사하지 않는다.
        let cgImage = vImageCreateCGImageFromBuffer(&destinationBuffer, &cgImageFormat,
        																						nil, nil, vImage_Flags(kvImageNoFlags),
        																						&error)
        
        if let cgImage = cgImage, error == kvImageNoError {
        	DispatchQueue.main.async {
        		self.imageView.image = UIImage(cgImage: cgImage.takeRetainedValue())
        	}
        }
        
        • 그 외 이펙트
          • Rotation

            let backColor: [UInt8] = [255, 255, 255, 255]
            vImageRotate_ARGB8888(&destinationBuffer, &destinationBuffer, nil,
            											fxValue, backColor, vImage_Flags(kvImageBackgroundColorFill))
            
          • Blur

            vImageTentConvolve_ARGB8888(&tmpBuffer, &destinationBuffer, nil,
            														0, 0, kernelSize, kernelSize, nil,
            														vImage_Flags(kvImageEdgeExtend))
            
          • Dither

            vImageConvert_Planar8toPlanar1(&sourceLumaBuffer,
            															&ditheredLuma,
            															nil,
            															Int32(kvImageConvert_DitherAtkinson),
            															vImage_Flags(kvImageNoFlags))
            
          • Color quantization

            var lookUpTable = (0...255).map {
            	return Pixel_8(($0/qualtizationLevel) * qualtizationLevel)
            }
            
            vImageTableLookUp_ARGB8888(&destinationBuffer, &destinationBuffer,
            													nil, &lookUpTable, &lookUpTable, &lookUpTable,
            													vImage_Flags(kvImageNoFlags))
            
  • LINPACK 벤치마크

    • 연립방정식을 얼마나 빨리 풀 수 있는 지 확인하기 위한 벤치마크 툴
    • 3가지 케이스의 벤치마크 사용
      • 100 by 100
      • 1000 by 1000
      • (기계가 허용하는 선까지) 무제한
    • iPhone을 세대별로 테스트 했을때, 성능이 세대별로 높아졌다.
      • 이때 아키텍쳐가 조금씩 다르기 때문에 이런 부분을 잘 활용해야 성능 향상이 많이 된다.
      • 이 부분은 Accelerate가 해주기 때문에 코드 변화없이 성능 향상을 얻을 수 있다.
    • OS가 새로 나와도, Accelerate를 쓴다면 호환 걱정은 없다.