一般に、経験則として、背景色が他のすべての色と異なるほど、画像を前景と背景に分割しやすくなります。このような場合、@Chris が既に提案したように、単純なクロマ キーの実装を使用することができます。以下は、 Wikipediaで説明されているキーイングの簡単な実装です(C++ で記述されていますが、Objective-C に変換するのは簡単なはずです)。
/**
* @brief Separate foreground from background using simple chroma keying.
*
* @param imageBGR Image with monochrome background
* @param chromaBGR Color of the background (using channel order BGR and range [0, 255])
* @param tInner Inner threshold, color distances below this value will be counted as foreground
* @param tOuter Outer threshold, color distances above this value will be counted as background
*
* @return Mask (0 - background, 255 - foreground, [1, 255] - partially fore- and background)
*
* Details can be found on [Wikipedia][1].
*
* [1]: https://en.wikipedia.org/wiki/Chroma_key#Programming
*/
cv::Mat1b chromaKey( const cv::Mat3b & imageBGR, cv::Scalar chromaBGR, double tInner, double tOuter )
{
// Basic outline:
//
// 1. Convert the image to YCrCb.
// 2. Measure Euclidean distances of color in YCrBr to chroma value.
// 3. Categorize pixels:
// * color distances below inner threshold count as foreground; mask value = 0
// * color distances above outer threshold count as background; mask value = 255
// * color distances between inner and outer threshold a linearly interpolated; mask value = [0, 255]
assert( tInner <= tOuter );
// Convert to YCrCb.
assert( ! imageBGR.empty() );
cv::Size imageSize = imageBGR.size();
cv::Mat3b imageYCrCb;
cv::cvtColor( imageBGR, imageYCrCb, cv::COLOR_BGR2YCrCb );
cv::Scalar chromaYCrCb = bgr2ycrcb( chromaBGR ); // Convert a single BGR value to YCrCb.
// Build the mask.
cv::Mat1b mask = cv::Mat1b::zeros( imageSize );
const cv::Vec3d key( chromaYCrCb[ 0 ], chromaYCrCb[ 1 ], chromaYCrCb[ 2 ] );
for ( int y = 0; y < imageSize.height; ++y )
{
for ( int x = 0; x < imageSize.width; ++x )
{
const cv::Vec3d color( imageYCrCb( y, x )[ 0 ], imageYCrCb( y, x )[ 1 ], imageYCrCb( y, x )[ 2 ] );
double distance = cv::norm( key - color );
if ( distance < tInner )
{
// Current pixel is fully part of the background.
mask( y, x ) = 0;
}
else if ( distance > tOuter )
{
// Current pixel is fully part of the foreground.
mask( y, x ) = 255;
}
else
{
// Current pixel is partially part both, fore- and background; interpolate linearly.
// Compute the interpolation factor and clip its value to the range [0, 255].
double d1 = distance - tInner;
double d2 = tOuter - tInner;
uint8_t alpha = static_cast< uint8_t >( 255. * ( d1 / d2 ) );
mask( y, x ) = alpha;
}
}
}
return mask;
}
完全に機能するコード例は、このGithub Gistにあります。
残念ながら、あなたの例はその経験則に固執していません。前景と背景は強度のみが異なるため、良好な分離のための単一のグローバル パラメータ セットを見つけることは困難 (または不可能) です。
オブジェクトの周りに黒い線があるが、オブジェクトの内側に穴がない ( tInner=50
、tOuter=90
)

オブジェクトの周りに黒い線はありませんが、オブジェクトの内側に穴があります ( tInner=100
、tOuter=170
)

そのため、画像の背景を変更できない場合は、より複雑なアプローチが必要になります。ただし、迅速で単純な実装例は範囲外ですが、画像のセグメンテーションと
アルファ マッティングの関連領域を調べたい場合があります。