5

私は OpenCV と Apple の Accelerate フレームワークを使用してきましたが、Accelerate のパフォーマンスが遅く、Apple のドキュメントが限られていることがわかりました。たとえば、次のように考えてみましょう。

void equalizeHistogram(const cv::Mat &planar8Image, cv::Mat &equalizedImage)
{
    cv::Size size = planar8Image.size();
    vImage_Buffer planarImageBuffer = {
        .width = static_cast<vImagePixelCount>(size.width),
        .height = static_cast<vImagePixelCount>(size.height),
        .rowBytes = planar8Image.step,
        .data = planar8Image.data
    };

    vImage_Buffer equalizedImageBuffer = {
        .width = static_cast<vImagePixelCount>(size.width),
        .height = static_cast<vImagePixelCount>(size.height),
        .rowBytes = equalizedImage.step,
        .data = equalizedImage.data
    };

    TIME_START(VIMAGE_EQUALIZE_HISTOGRAM);
    vImage_Error error = vImageEqualization_Planar8(&planarImageBuffer, &equalizedImageBuffer, kvImageNoFlags);
    TIME_END(VIMAGE_EQUALIZE_HISTOGRAM);
    if (error != kvImageNoError) {
        NSLog(@"%s, vImage error %zd", __PRETTY_FUNCTION__, error);
    }
}

この呼び出しには約 20 ミリ秒かかります。私のアプリケーションでは使用できないという実際的な意味があります。ヒストグラムの均等化は本質的に遅いかもしれませんが、BGRA->Grayscale もテストしたところ、OpenCV は ~5ms で vImage は ~20ms で処理できることがわかりました。

他の機能のテストでは、ぼかし機能 (要旨)を備えた単純なスライダー アプリを作成するプロジェクトを見つけ、それをクリーンアップしてテストしました。おおよそ〜20msも同様です。

これらの関数を高速化するためのトリックはありますか?

4

3 に答える 3

7

equalizeHistogram 関数を使用して毎秒 30 フレームを取得するには、画像のインターリーブを解除し (ARGBxxxx から PlanarX に変換)、R(ed)G(reen)B(lue) のみをイコライズする必要があります。A(lpha) を均等化すると、フレーム レートは少なくとも 24 に低下します。

これは、まさにあなたが望むことを、あなたが望むのと同じくらい速く行うコードです:

- (CVPixelBufferRef)copyRenderedPixelBuffer:(CVPixelBufferRef)pixelBuffer {

CVPixelBufferLockBaseAddress( pixelBuffer, 0 );

unsigned char *base = (unsigned char *)CVPixelBufferGetBaseAddress( pixelBuffer );
size_t width = CVPixelBufferGetWidth( pixelBuffer );
size_t height = CVPixelBufferGetHeight( pixelBuffer );
size_t stride = CVPixelBufferGetBytesPerRow( pixelBuffer );

vImage_Buffer _img = {
    .data = base,
    .height = height,
    .width = width,
    .rowBytes = stride
};

vImage_Error err;
vImage_Buffer _dstA, _dstR, _dstG, _dstB;

err = vImageBuffer_Init( &_dstA, height, width, 8 * sizeof( uint8_t ), kvImageNoFlags);
if (err != kvImageNoError)
    NSLog(@"vImageBuffer_Init (alpha) error: %ld", err);

err = vImageBuffer_Init( &_dstR, height, width, 8 * sizeof( uint8_t ), kvImageNoFlags);
if (err != kvImageNoError)
    NSLog(@"vImageBuffer_Init (red) error: %ld", err);

err = vImageBuffer_Init( &_dstG, height, width, 8 * sizeof( uint8_t ), kvImageNoFlags);
if (err != kvImageNoError)
    NSLog(@"vImageBuffer_Init (green) error: %ld", err);

err = vImageBuffer_Init( &_dstB, height, width, 8 * sizeof( uint8_t ), kvImageNoFlags);
if (err != kvImageNoError)
    NSLog(@"vImageBuffer_Init (blue) error: %ld", err);

err = vImageConvert_ARGB8888toPlanar8(&_img, &_dstA, &_dstR, &_dstG, &_dstB, kvImageNoFlags);
if (err != kvImageNoError)
    NSLog(@"vImageConvert_ARGB8888toPlanar8 error: %ld", err);

err = vImageEqualization_Planar8(&_dstR, &_dstR, kvImageNoFlags);
if (err != kvImageNoError)
    NSLog(@"vImageEqualization_Planar8 (red) error: %ld", err);

err = vImageEqualization_Planar8(&_dstG, &_dstG, kvImageNoFlags);
if (err != kvImageNoError)
    NSLog(@"vImageEqualization_Planar8 (green) error: %ld", err);

err = vImageEqualization_Planar8(&_dstB, &_dstB, kvImageNoFlags);
if (err != kvImageNoError)
    NSLog(@"vImageEqualization_Planar8 (blue) error: %ld", err);

err = vImageConvert_Planar8toARGB8888(&_dstA, &_dstR, &_dstG, &_dstB, &_img, kvImageNoFlags);
if (err != kvImageNoError)
    NSLog(@"vImageConvert_Planar8toARGB8888 error: %ld", err);

err = vImageContrastStretch_ARGB8888( &_img, &_img, kvImageNoError );
if (err != kvImageNoError)
    NSLog(@"vImageContrastStretch_ARGB8888 error: %ld", err);

free(_dstA.data);
free(_dstR.data);
free(_dstG.data);
free(_dstB.data);

CVPixelBufferUnlockBaseAddress( pixelBuffer, 0 );

return (CVPixelBufferRef)CFRetain( pixelBuffer );

}

何も実行していませんが、アルファ チャネルを割り当てていることに注意してください。これは単純に、ARGB8888 と Planar8 の間で相互に変換するには、アルファ チャネル バッファーの割り当てと参照が必要だからです。関係なく、同じパフォーマンスと品質の向上。

また、Planar8 バッファーを単一の ARGB8888 バッファーに変換した後にコントラスト ストレッチングを実行していることにも注意してください。これは、ヒストグラム均等化関数で行ったように、チャネルごとに関数を適用するよりも高速であり、個別に実行した場合と同じ結果が得られるためです (コントラスト ストレッチ関数は、ヒストグラム均等化と同じアルファ チャネルの歪みを引き起こしません)。 .

于 2015-05-25T22:57:21.887 に答える
5

OpenCV で vImage を使用するには、OpenCV マトリックスへの参照を次のようなメソッドに渡します。

long contrastStretch_Accelerate(const Mat& src, Mat& dst) {
    vImagePixelCount rows = static_cast<vImagePixelCount>(src.rows);
    vImagePixelCount cols = static_cast<vImagePixelCount>(src.cols);

    vImage_Buffer _src = { src.data, rows, cols, src.step };
    vImage_Buffer _dst = { dst.data, rows, cols, dst.step };

    vImage_Error err;

    err = vImageContrastStretch_ARGB8888( &_src, &_dst, 0 );
    return err;
}

OpenCV コード ブロックからのこのメソッドの呼び出しは、次のようになります。

- (void)processImage:(Mat&)image;
{
    contrastStretch_Accelerate(image, image);
}

とてもシンプルで、これらはすべてポインター参照であるため、「ディープ コピー」は一切ありません。可能な限り高速で効率的であり、コンテキストやその他の関連するパフォーマンスの考慮事項は別として (それらについてもお手伝いできます)。

補足: OpenCV と vImage を混在させる場合、チャネル順列を変更する必要があることをご存知でしたか? そうでない場合は、OpenCV マトリックスで vImage 関数を呼び出す前に、次を呼び出します。

const uint8_t map[4] = { 3, 2, 1, 0 };
err = vImagePermuteChannels_ARGB8888(&_img, &_img, map, kvImageNoFlags);
if (err != kvImageNoError)
    NSLog(@"vImagePermuteChannels_ARGB8888 error: %ld", err);

同じ呼び出し、マップ、およびすべてを実行して、イメージを OpenCV マトリックスに適切なチャネル順序に戻します。

于 2015-05-25T23:38:31.077 に答える