diff --git a/modules/xphoto/include/opencv2/xphoto.hpp b/modules/xphoto/include/opencv2/xphoto.hpp index 5313466b9..844ef4107 100644 --- a/modules/xphoto/include/opencv2/xphoto.hpp +++ b/modules/xphoto/include/opencv2/xphoto.hpp @@ -47,7 +47,6 @@ */ #include "xphoto/inpainting.hpp" -#include "xphoto/simple_color_balance.hpp" +#include "xphoto/white_balance.hpp" #include "xphoto/dct_image_denoising.hpp" -#include "xphoto/grayworld_white_balance.hpp" #endif diff --git a/modules/xphoto/include/opencv2/xphoto/grayworld_white_balance.hpp b/modules/xphoto/include/opencv2/xphoto/grayworld_white_balance.hpp deleted file mode 100644 index ad63ba61f..000000000 --- a/modules/xphoto/include/opencv2/xphoto/grayworld_white_balance.hpp +++ /dev/null @@ -1,94 +0,0 @@ -/*M/////////////////////////////////////////////////////////////////////////////////////// -// -// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. -// -// By downloading, copying, installing or using the software you agree to this license. -// If you do not agree to this license, do not download, install, -// copy or use the software. -// -// -// License Agreement -// For Open Source Computer Vision Library -// -// Copyright (C) 2000-2008, Intel Corporation, all rights reserved. -// Copyright (C) 2009-2011, Willow Garage Inc., all rights reserved. -// Third party copyrights are property of their respective owners. -// -// Redistribution and use in source and binary forms, with or without modification, -// are permitted provided that the following conditions are met: -// -// * Redistribution's of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// * Redistribution's in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// * The name of the copyright holders may not be used to endorse or promote products -// derived from this software without specific prior written permission. -// -// This software is provided by the copyright holders and contributors "as is" and -// any express or implied warranties, including, but not limited to, the implied -// warranties of merchantability and fitness for a particular purpose are disclaimed. -// In no event shall the Intel Corporation or contributors be liable for any direct, -// indirect, incidental, special, exemplary, or consequential damages -// (including, but not limited to, procurement of substitute goods or services; -// loss of use, data, or profits; or business interruption) however caused -// and on any theory of liability, whether in contract, strict liability, -// or tort (including negligence or otherwise) arising in any way out of -// the use of this software, even if advised of the possibility of such damage. -// -//M*/ - -#ifndef __OPENCV_GRAYWORLD_WHITE_BALANCE_HPP__ -#define __OPENCV_GRAYWORLD_WHITE_BALANCE_HPP__ - -/** @file -@date Jun 3, 2015 -@author Seon-Wook Park -*/ - -#include - -namespace cv { namespace xphoto { - -//! @addtogroup xphoto -//! @{ - - /** @brief Implements a simple grayworld white balance algorithm. - - The function autowbGrayworld scales the values of pixels based on a - gray-world assumption which states that the average of all channels - should result in a gray image. - - This function adds a modification which thresholds pixels based on their - saturation value and only uses pixels below the provided threshold in - finding average pixel values. - - Saturation is calculated using the following for a 3-channel RGB image per - pixel I and is in the range [0, 1]: - - \f[ \texttt{Saturation} [I] = \frac{\textrm{max}(R,G,B) - \textrm{min}(R,G,B) - }{\textrm{max}(R,G,B)} \f] - - A threshold of 1 means that all pixels are used to white-balance, while a - threshold of 0 means no pixels are used. Lower thresholds are useful in - white-balancing saturated images. - - Currently only works on images of type @ref CV_8UC3. - - @param src Input array. - @param dst Output array of the same size and type as src. - @param thresh Maximum saturation for a pixel to be included in the - gray-world assumption. - - @sa balanceWhite - */ - CV_EXPORTS_W void autowbGrayworld(InputArray src, OutputArray dst, - const float thresh = 0.5f); - -//! @} - -}} - -#endif // __OPENCV_GRAYWORLD_WHITE_BALANCE_HPP__ diff --git a/modules/xphoto/include/opencv2/xphoto/simple_color_balance.hpp b/modules/xphoto/include/opencv2/xphoto/white_balance.hpp similarity index 73% rename from modules/xphoto/include/opencv2/xphoto/simple_color_balance.hpp rename to modules/xphoto/include/opencv2/xphoto/white_balance.hpp index 416d76e21..d4d68eac8 100644 --- a/modules/xphoto/include/opencv2/xphoto/simple_color_balance.hpp +++ b/modules/xphoto/include/opencv2/xphoto/white_balance.hpp @@ -85,6 +85,38 @@ namespace xphoto const float inputMin = 0.0f, const float inputMax = 255.0f, const float outputMin = 0.0f, const float outputMax = 255.0f); + /** @brief Implements a simple grayworld white balance algorithm. + + The function autowbGrayworld scales the values of pixels based on a + gray-world assumption which states that the average of all channels + should result in a gray image. + + This function adds a modification which thresholds pixels based on their + saturation value and only uses pixels below the provided threshold in + finding average pixel values. + + Saturation is calculated using the following for a 3-channel RGB image per + pixel I and is in the range [0, 1]: + + \f[ \texttt{Saturation} [I] = \frac{\textrm{max}(R,G,B) - \textrm{min}(R,G,B) + }{\textrm{max}(R,G,B)} \f] + + A threshold of 1 means that all pixels are used to white-balance, while a + threshold of 0 means no pixels are used. Lower thresholds are useful in + white-balancing saturated images. + + Currently only works on images of type @ref CV_8UC3. + + @param src Input array. + @param dst Output array of the same size and type as src. + @param thresh Maximum saturation for a pixel to be included in the + gray-world assumption. + + @sa balanceWhite + */ + CV_EXPORTS_W void autowbGrayworld(InputArray src, OutputArray dst, + float thresh = 0.5f); + //! @} } diff --git a/modules/xphoto/perf/perf_grayworld.cpp b/modules/xphoto/perf/perf_grayworld.cpp index 5019d2d42..80c034c58 100644 --- a/modules/xphoto/perf/perf_grayworld.cpp +++ b/modules/xphoto/perf/perf_grayworld.cpp @@ -9,7 +9,7 @@ typedef perf::TestBaseWithParam Size_WBThresh; PERF_TEST_P( Size_WBThresh, autowbGrayworld, testing::Combine( - testing::Values( TYPICAL_MAT_SIZES ), + SZ_ALL_HD, testing::Values( 0.1, 0.5, 1.0 ) ) ) diff --git a/modules/xphoto/samples/grayworld_color_balance.cpp b/modules/xphoto/samples/grayworld_color_balance.cpp index 1597ddc81..caaa0a454 100644 --- a/modules/xphoto/samples/grayworld_color_balance.cpp +++ b/modules/xphoto/samples/grayworld_color_balance.cpp @@ -5,6 +5,9 @@ #include "opencv2/core/utility.hpp" +using namespace cv; +using namespace std; + const char* keys = { "{i || input image name}" @@ -14,8 +17,8 @@ const char* keys = int main( int argc, const char** argv ) { bool printHelp = ( argc == 1 ); - printHelp = printHelp || ( argc == 2 && std::string(argv[1]) == "--help" ); - printHelp = printHelp || ( argc == 2 && std::string(argv[1]) == "-h" ); + printHelp = printHelp || ( argc == 2 && string(argv[1]) == "--help" ); + printHelp = printHelp || ( argc == 2 && string(argv[1]) == "-h" ); if ( printHelp ) { @@ -25,35 +28,35 @@ int main( int argc, const char** argv ) return 0; } - cv::CommandLineParser parser(argc, argv, keys); + CommandLineParser parser(argc, argv, keys); if ( !parser.check() ) { parser.printErrors(); return -1; } - std::string inFilename = parser.get("i"); - std::string outFilename = parser.get("o"); + string inFilename = parser.get("i"); + string outFilename = parser.get("o"); - cv::Mat src = cv::imread(inFilename, 1); + Mat src = imread(inFilename, 1); if ( src.empty() ) { printf("Cannot read image file: %s\n", inFilename.c_str()); return -1; } - cv::Mat res(src.size(), src.type()); - cv::xphoto::autowbGrayworld(src, res); + Mat res(src.size(), src.type()); + xphoto::autowbGrayworld(src, res); if ( outFilename == "" ) { - cv::namedWindow("after white balance", 1); - cv::imshow("after white balance", res); + namedWindow("after white balance", 1); + imshow("after white balance", res); - cv::waitKey(0); + waitKey(0); } else - cv::imwrite(outFilename, res); + imwrite(outFilename, res); return 0; } diff --git a/modules/xphoto/src/grayworld_white_balance.cpp b/modules/xphoto/src/grayworld_white_balance.cpp index 686702e64..9cab28cc1 100644 --- a/modules/xphoto/src/grayworld_white_balance.cpp +++ b/modules/xphoto/src/grayworld_white_balance.cpp @@ -44,7 +44,7 @@ namespace cv { namespace xphoto { - void autowbGrayworld(InputArray _src, OutputArray _dst, const float thresh) + void autowbGrayworld(InputArray _src, OutputArray _dst, float thresh) { Mat src = _src.getMat(); @@ -57,6 +57,7 @@ namespace cv { namespace xphoto { _dst.create(src.size(), src.type()); Mat dst = _dst.getMat(); + CV_Assert(dst.isContinuous()); int width = src.cols, height = src.rows, @@ -66,6 +67,7 @@ namespace cv { namespace xphoto { // Calculate sum of pixel values of each channel const uchar* src_data = src.ptr(0); unsigned long sum1 = 0, sum2 = 0, sum3 = 0; + unsigned int thresh255 = round(thresh * 255); int i = 0; #if CV_SIMD128 v_uint8x16 v_inB, v_inG, v_inR; @@ -73,14 +75,14 @@ namespace cv { namespace xphoto { v_uint32x4 v_iB1, v_iB2, v_iB3, v_iB4, v_iG1, v_iG2, v_iG3, v_iG4, v_iR1, v_iR2, v_iR3, v_iR4, + v_255 = v_setall_u32(255), + v_thresh = v_setall_u32(thresh255), + v_min1, v_min2, v_min3, v_min4, + v_max1, v_max2, v_max3, v_max4, + v_m1, v_m2, v_m3, v_m4, v_SB = v_setzero_u32(), v_SG = v_setzero_u32(), - v_SR = v_setzero_u32(), - v_m1, v_m2, v_m3, v_m4; - v_float32x4 v_thresh = v_setall_f32(thresh), - v_min1, v_min2, v_min3, v_min4, - v_max1, v_max2, v_max3, v_max4, - v_sat1, v_sat2, v_sat3, v_sat4; + v_SR = v_setzero_u32(); for ( ; i < N3 - 47; i += 48 ) { @@ -102,27 +104,22 @@ namespace cv { namespace xphoto { v_expand(v_s1, v_iR1, v_iR2); v_expand(v_s2, v_iR3, v_iR4); - // Get saturation - v_min1 = v_cvt_f32(v_reinterpret_as_s32(v_min(v_iB1, v_min(v_iG1, v_iR1)))); - v_min2 = v_cvt_f32(v_reinterpret_as_s32(v_min(v_iB2, v_min(v_iG2, v_iR2)))); - v_min3 = v_cvt_f32(v_reinterpret_as_s32(v_min(v_iB3, v_min(v_iG3, v_iR3)))); - v_min4 = v_cvt_f32(v_reinterpret_as_s32(v_min(v_iB4, v_min(v_iG4, v_iR4)))); + // Get mins and maxs + v_min1 = v_min(v_iB1, v_min(v_iG1, v_iR1)); + v_min2 = v_min(v_iB2, v_min(v_iG2, v_iR2)); + v_min3 = v_min(v_iB3, v_min(v_iG3, v_iR3)); + v_min4 = v_min(v_iB4, v_min(v_iG4, v_iR4)); - v_max1 = v_cvt_f32(v_reinterpret_as_s32(v_max(v_iB1, v_max(v_iG1, v_iR1)))); - v_max2 = v_cvt_f32(v_reinterpret_as_s32(v_max(v_iB2, v_max(v_iG2, v_iR2)))); - v_max3 = v_cvt_f32(v_reinterpret_as_s32(v_max(v_iB3, v_max(v_iG3, v_iR3)))); - v_max4 = v_cvt_f32(v_reinterpret_as_s32(v_max(v_iB4, v_max(v_iG4, v_iR4)))); - - v_sat1 = (v_max1 - v_min1) / v_max1; - v_sat2 = (v_max2 - v_min2) / v_max2; - v_sat3 = (v_max3 - v_min3) / v_max3; - v_sat4 = (v_max4 - v_min4) / v_max4; + v_max1 = v_max(v_iB1, v_max(v_iG1, v_iR1)); + v_max2 = v_max(v_iB2, v_max(v_iG2, v_iR2)); + v_max3 = v_max(v_iB3, v_max(v_iG3, v_iR3)); + v_max4 = v_max(v_iB4, v_max(v_iG4, v_iR4)); // Calculate masks - v_m1 = v_reinterpret_as_u32(v_sat1 <= v_thresh); - v_m2 = v_reinterpret_as_u32(v_sat2 <= v_thresh); - v_m3 = v_reinterpret_as_u32(v_sat3 <= v_thresh); - v_m4 = v_reinterpret_as_u32(v_sat4 <= v_thresh); + v_m1 = ~((v_max1 - v_min1) * v_255 > v_thresh * v_max1); + v_m2 = ~((v_max2 - v_min2) * v_255 > v_thresh * v_max2); + v_m3 = ~((v_max3 - v_min3) * v_255 > v_thresh * v_max3); + v_m4 = ~((v_max4 - v_min4) * v_255 > v_thresh * v_max4); // Apply mask v_SB += (v_iB1 & v_m1) + (v_iB2 & v_m2) + (v_iB3 & v_m3) + (v_iB4 & v_m4); @@ -135,13 +132,12 @@ namespace cv { namespace xphoto { sum2 = v_reduce_sum(v_SG); sum3 = v_reduce_sum(v_SR); #endif - double minRGB, maxRGB, satur; + unsigned int minRGB, maxRGB; for ( ; i < N3; i += 3 ) { minRGB = min(src_data[i], min(src_data[i + 1], src_data[i + 2])); maxRGB = max(src_data[i], max(src_data[i + 1], src_data[i + 2])); - satur = (maxRGB - minRGB) / maxRGB; - if ( satur > thresh ) continue; + if ( (maxRGB - minRGB) * 255 > thresh255 * maxRGB ) continue; sum1 += src_data[i]; sum2 += src_data[i + 1]; sum3 += src_data[i + 2]; @@ -168,64 +164,49 @@ namespace cv { namespace xphoto { inv3 = (float)((double)inv3 / inv_max); } + // Fixed point arithmetic, mul by 2^8 then shift back 8 bits + unsigned int i_inv1 = inv1 * (1 << 8), + i_inv2 = inv2 * (1 << 8), + i_inv3 = inv3 * (1 << 8); + // Scale input pixel values uchar* dst_data = dst.ptr(0); i = 0; #if CV_SIMD128 - v_uint8x16 v_in, v_out; - v_uint32x4 v_i1, v_i2, v_i3, v_i4; - v_float32x4 v_f1, v_f2, v_f3, v_f4, - scal1(inv1, inv2, inv3, inv1), - scal2(inv2, inv3, inv1, inv2), - scal3(inv3, inv1, inv2, inv3), - scal4(inv1, inv2, inv3, 0.f); - - for ( ; i < N3 - 14; i += 15 ) + v_uint8x16 v_outB, v_outG, v_outR; + v_uint16x8 v_sB1, v_sB2, v_sG1, v_sG2, v_sR1, v_sR2, + v_invB = v_setall_u16(i_inv1), + v_invG = v_setall_u16(i_inv2), + v_invR = v_setall_u16(i_inv3); + + for ( ; i < N3 - 47; i += 48 ) { // Load 16 x 8bit uchars - v_in = v_load(&src_data[i]); - - // Split into two vectors of 8 ushorts - v_expand(v_in, v_s1, v_s2); - - // Split into four vectors of 4 uints - v_expand(v_s1, v_i1, v_i2); - v_expand(v_s2, v_i3, v_i4); + v_load_deinterleave(&src_data[i], v_inB, v_inG, v_inR); - // Convert into four vectors of 4 floats - v_f1 = v_cvt_f32(v_reinterpret_as_s32(v_i1)); - v_f2 = v_cvt_f32(v_reinterpret_as_s32(v_i2)); - v_f3 = v_cvt_f32(v_reinterpret_as_s32(v_i3)); - v_f4 = v_cvt_f32(v_reinterpret_as_s32(v_i4)); + // Split into four int vectors per channel + v_expand(v_inB, v_sB1, v_sB2); + v_expand(v_inG, v_sG1, v_sG2); + v_expand(v_inR, v_sR1, v_sR2); // Multiply by scaling factors - v_f1 *= scal1; - v_f2 *= scal2; - v_f3 *= scal3; - v_f4 *= scal4; - - // Convert back into four vectors of 4 uints - v_i1 = v_reinterpret_as_u32(v_round(v_f1)); - v_i2 = v_reinterpret_as_u32(v_round(v_f2)); - v_i3 = v_reinterpret_as_u32(v_round(v_f3)); - v_i4 = v_reinterpret_as_u32(v_round(v_f4)); - - // Pack into two vectors of 8 ushorts - v_s1 = v_pack(v_i1, v_i2); - v_s2 = v_pack(v_i3, v_i4); - - // Pack into vector of 16 uchars - v_out = v_pack(v_s1, v_s2); - - // Store - v_store(&dst_data[i], v_out); + v_sB1 = (v_sB1 * v_invB) >> 8; + v_sB2 = (v_sB2 * v_invB) >> 8; + v_sG1 = (v_sG1 * v_invG) >> 8; + v_sG2 = (v_sG2 * v_invG) >> 8; + v_sR1 = (v_sR1 * v_invR) >> 8; + v_sR2 = (v_sR2 * v_invR) >> 8; + + // Pack into vectors of v_uint8x16 + v_store_interleave(&dst_data[i], v_pack(v_sB1, v_sB2), + v_pack(v_sG1, v_sG2), v_pack(v_sR1, v_sR2)); } #endif for ( ; i < N3; i += 3 ) { - dst_data[i + 0] = (uchar)(src_data[i + 0] * inv1); - dst_data[i + 1] = (uchar)(src_data[i + 1] * inv2); - dst_data[i + 2] = (uchar)(src_data[i + 2] * inv3); + dst_data[i] = (uchar)((src_data[i] * i_inv1) >> 8); + dst_data[i + 1] = (uchar)((src_data[i + 1] * i_inv2) >> 8); + dst_data[i + 2] = (uchar)((src_data[i + 2] * i_inv3) >> 8); } } diff --git a/modules/xphoto/test/test_grayworld.cpp b/modules/xphoto/test/test_grayworld.cpp index 5b2f6ef20..2e604f67d 100644 --- a/modules/xphoto/test/test_grayworld.cpp +++ b/modules/xphoto/test/test_grayworld.cpp @@ -4,7 +4,7 @@ namespace cvtest { using namespace cv; - void ref_autowbGrayworld(InputArray _src, OutputArray _dst, const float thresh) + void ref_autowbGrayworld(InputArray _src, OutputArray _dst, float thresh) { Mat src = _src.getMat(); @@ -20,13 +20,12 @@ namespace cvtest { const uchar* src_data = src.ptr(0); unsigned long sum1 = 0, sum2 = 0, sum3 = 0; int i = 0; - double minRGB, maxRGB, satur; + unsigned int minRGB, maxRGB, thresh255 = round(thresh * 255); for ( ; i < N3; i += 3 ) { minRGB = std::min(src_data[i], std::min(src_data[i + 1], src_data[i + 2])); maxRGB = std::max(src_data[i], std::max(src_data[i + 1], src_data[i + 2])); - satur = (maxRGB - minRGB) / maxRGB; - if ( satur > thresh ) continue; + if ( (maxRGB - minRGB) * 255 > thresh255 * maxRGB ) continue; sum1 += src_data[i]; sum2 += src_data[i + 1]; sum3 += src_data[i + 2]; @@ -48,14 +47,19 @@ namespace cvtest { inv3 = (double) inv3 / inv_max; } + // Fixed point arithmetic, mul by 2^8 then shift back 8 bits + unsigned int i_inv1 = inv1 * (1 << 8), + i_inv2 = inv2 * (1 << 8), + i_inv3 = inv3 * (1 << 8); + // Scale input pixel values uchar* dst_data = dst.ptr(0); i = 0; for ( ; i < N3; i += 3 ) { - dst_data[i] = (uchar)(src_data[i] * inv1); - dst_data[i + 1] = (uchar)(src_data[i + 1] * inv2); - dst_data[i + 2] = (uchar)(src_data[i + 2] * inv3); + dst_data[i] = (uchar)((src_data[i] * i_inv1) >> 8); + dst_data[i + 1] = (uchar)((src_data[i + 1] * i_inv2) >> 8); + dst_data[i + 2] = (uchar)((src_data[i + 2] * i_inv3) >> 8); } }