Last active
November 5, 2024 08:11
-
-
Save enpe/8634ce7f200fb554f0e5 to your computer and use it in GitHub Desktop.
OpenCV chroma key
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
cmake_minimum_required( VERSION 3.0 ) | |
project( so-opencv-calibration ) | |
find_package( OpenCV 3.0.0 EXACT REQUIRED ) | |
set( INPUT_FILENAME "${CMAKE_CURRENT_LIST_DIR}/input_00.png" ) # Input file, e.g. http://i.stack.imgur.com/WjER0.png | |
add_executable( mwe mwe.cpp ) | |
target_compile_definitions( mwe PRIVATE -DINPUT_FILENAME="${INPUT_FILENAME}" ) | |
target_include_directories( mwe PRIVATE ${OpenCV_INCLUDE_DIRS} ) | |
target_link_libraries( mwe ${OpenCV_LIBS} ) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <opencv2/core.hpp> | |
#include <opencv2/imgproc.hpp> | |
#include <opencv2/highgui.hpp> | |
#include <cassert> | |
#include <cmath> | |
#include <iostream> | |
#include <string> | |
namespace { | |
/** | |
* @brief Convert BGR color to YCrCb color space. | |
* @param bgr Color (channel order BGR, range: [0, 255]) | |
* | |
* Details can be found in the [documentation of OpenCV][1] | |
* | |
* [1]: http://docs.opencv.org/trunk/de/d25/imgproc_color_conversions.html | |
*/ | |
cv::Scalar bgr2ycrcb( cv::Scalar bgr ) | |
{ | |
double R = bgr[ 2 ]; | |
double G = bgr[ 1 ]; | |
double B = bgr[ 0 ]; | |
double delta = 128; // Note: change this value if image type isn't CV_8U. | |
double Y = 0.299 * R + 0.587 * G + 0.114 * B; | |
double Cr = ( R - Y ) * 0.713 + delta; | |
double Cb = ( B - Y ) * 0.564 + delta; | |
return cv::Scalar( Y, Cr, Cb, 0 /* ignored */ ); | |
} | |
/** | |
* @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; | |
} | |
cv::Mat3b replaceBackground( const cv::Mat3b & image, const cv::Mat1b & mask, cv::Scalar bgColor ) | |
{ | |
cv::Size imageSize = image.size(); | |
const cv::Vec3b bgColorVec( bgColor[ 0 ], bgColor[ 1 ], bgColor[ 2 ] ); | |
cv::Mat3b newImage( image.size() ); | |
for ( int y = 0; y < imageSize.height; ++y ) | |
{ | |
for ( int x = 0; x < imageSize.width; ++x ) | |
{ | |
uint8_t maskValue = mask( y, x ); | |
if ( maskValue >= 255 ) | |
{ | |
newImage( y, x ) = image( y, x ); | |
} | |
else if ( maskValue <= 0 ) | |
{ | |
newImage( y, x ) = bgColorVec; | |
} | |
else | |
{ | |
double alpha = 1. / static_cast< double >( maskValue ); | |
newImage( y, x ) = alpha * image( y, x ) + ( 1. - alpha ) * bgColorVec; | |
} | |
} | |
} | |
return newImage; | |
} | |
} // namespace | |
int main() | |
{ | |
std::string inputFilename = INPUT_FILENAME; | |
std::string maskFilename = "./mask.png"; | |
std::string newBackgroundFilename = "./newBackground.png"; | |
// Load the input image. | |
cv::Mat3b input = cv::imread( inputFilename, cv::IMREAD_COLOR ); | |
if ( input.empty() ) | |
{ | |
std::cerr << "Input file <" << inputFilename << "> could not be loaded ... " << std::endl; | |
return 1; | |
} | |
// Apply the chroma keying and save the output. | |
cv::Scalar chroma( 0, 0, 0, 0 ); | |
double tInner = 100.; | |
double tOuter = 170.; | |
cv::Mat1b mask = chromaKey( input, chroma, tInner, tOuter ); | |
cv::Mat3b newBackground = replaceBackground( input, mask, cv::Scalar( 0, 255, 0, 0 ) ); | |
cv::imwrite( maskFilename, mask ); | |
cv::imwrite( newBackgroundFilename, newBackground ); | |
cv::namedWindow( "input" ); | |
cv::imshow( "input", input ); | |
cv::namedWindow( "mask" ); | |
cv::imshow( "mask", mask ); | |
cv::namedWindow( "new background" ); | |
cv::imshow( "new background", newBackground ); | |
cv::waitKey( 0 ); | |
return 0; | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Can you please guide me how can i use it with xcode and i am new to this CMake!
How can i configure?