diff --git a/modules/videoio/include/opencv2/videoio.hpp b/modules/videoio/include/opencv2/videoio.hpp index c42de666ae..b917028042 100644 --- a/modules/videoio/include/opencv2/videoio.hpp +++ b/modules/videoio/include/opencv2/videoio.hpp @@ -145,7 +145,7 @@ enum VideoCaptureProperties { CAP_PROP_FOURCC =6, //!< 4-character code of codec. see VideoWriter::fourcc . CAP_PROP_FRAME_COUNT =7, //!< Number of frames in the video file. CAP_PROP_FORMAT =8, //!< Format of the %Mat objects (see Mat::type()) returned by VideoCapture::retrieve(). - //!< Set value -1 to fetch undecoded RAW video streams (as Mat 8UC1). + //!< Set value -1 to fetch undecoded RAW video streams (as Mat 8UC1). Default is 8UC3. FFmpeg backend supports 8UC4 with alpha, if it's available. CAP_PROP_MODE =9, //!< Backend-specific value indicating the current capture mode. CAP_PROP_BRIGHTNESS =10, //!< Brightness of the image (only for those cameras that support). CAP_PROP_CONTRAST =11, //!< Contrast of the image (only for cameras). @@ -234,6 +234,7 @@ enum VideoWriterProperties { VIDEOWRITER_PROP_PTS = 12, //!< Specifies the frame presentation timestamp for each frame using the FPS time base. This property is **only** necessary when encapsulating **externally** encoded video where the decoding order differs from the presentation order, such as in GOP patterns with bi-directional B-frames. The value should be provided by your external encoder and for video sources with fixed frame rates it is equivalent to dividing the current frame's presentation time (\ref CAP_PROP_POS_MSEC) by the frame duration (1000.0 / VideoCapture::get(\ref CAP_PROP_FPS)). It can be queried from the resulting encapsulated video file using VideoCapture::get(\ref CAP_PROP_PTS). FFmpeg back-end only. VIDEOWRITER_PROP_DTS_DELAY = 13, //!< Specifies the maximum difference between presentation (pts) and decompression timestamps (dts) using the FPS time base. This property is necessary **only** when encapsulating **externally** encoded video where the decoding order differs from the presentation order, such as in GOP patterns with bi-directional B-frames. The value should be calculated based on the specific GOP pattern used during encoding. For example, in a GOP with presentation order IBP and decoding order IPB, this value would be 1, as the B-frame is the second frame presented but the third to be decoded. It can be queried from the resulting encapsulated video file using VideoCapture::get(\ref CAP_PROP_DTS_DELAY). Non-zero values usually imply the stream is encoded using B-frames. FFmpeg back-end only. VIDEOWRITER_PROP_COLOR_SPACE = 14, //!< (**open-only**) GStreamer backend only. Pixel format for the encoding profile. Default is "I420". Other values: "NV12", "BGRx". See GStreamer raw video formats for more options. + VIDEOWRITER_PROP_ENABLE_ALPHA = 15, //!< (**open-only**) FFmpeg backend only. Defines that input frames contain alpha channel. #ifndef CV_DOXYGEN CV__VIDEOWRITER_PROP_LATEST #endif diff --git a/modules/videoio/src/cap_ffmpeg_impl.hpp b/modules/videoio/src/cap_ffmpeg_impl.hpp index afd8fd4098..33f6eecc70 100644 --- a/modules/videoio/src/cap_ffmpeg_impl.hpp +++ b/modules/videoio/src/cap_ffmpeg_impl.hpp @@ -612,6 +612,7 @@ struct CvCapture_FFMPEG bool rawModeInitialized; bool rawSeek; bool convertRGB; + bool enableAlpha; AVPacket packet_filtered; #if LIBAVFORMAT_BUILD >= CALC_FFMPEG_VERSION(58, 20, 100) AVBSFContext* bsfc; @@ -669,6 +670,7 @@ void CvCapture_FFMPEG::init() rawModeInitialized = false; rawSeek = false; convertRGB = true; + enableAlpha = false; memset(&packet_filtered, 0, sizeof(packet_filtered)); av_init_packet(&packet_filtered); bsfc = NULL; @@ -1085,9 +1087,10 @@ bool CvCapture_FFMPEG::open(const char* _filename, int index, const Ptr(CAP_PROP_FORMAT); @@ -1098,8 +1101,19 @@ bool CvCapture_FFMPEG::open(const char* _filename, int index, const Ptrcolor_primaries) << ", transfer: " << av_color_transfer_name(sw_picture->color_trc) ); - const AVPixelFormat result_format = convertRGB ? AV_PIX_FMT_BGR24 : (AVPixelFormat)sw_picture->format; + + const AVPixelFormat color_format = enableAlpha ? AV_PIX_FMT_BGRA : AV_PIX_FMT_BGR24; + const AVPixelFormat result_format = convertRGB ? color_format : (AVPixelFormat)sw_picture->format; switch (result_format) { + case AV_PIX_FMT_BGRA: *depth = CV_8U; *cn = 4; break; case AV_PIX_FMT_BGR24: *depth = CV_8U; *cn = 3; break; case AV_PIX_FMT_GRAY8: *depth = CV_8U; *cn = 1; break; case AV_PIX_FMT_GRAY16LE: *depth = CV_16U; *cn = 1; break; @@ -2122,6 +2139,12 @@ double CvCapture_FFMPEG::getProperty( int property_id ) const case CAP_PROP_FORMAT: if (rawMode) return -1; + else if (!convertRGB) + return CV_8UC1; + else if (enableAlpha) + return CV_8UC4; + else + return CV_8UC3; break; case CAP_PROP_CONVERT_RGB: return convertRGB; @@ -2365,8 +2388,20 @@ bool CvCapture_FFMPEG::setProperty( int property_id, double value ) seek((int64_t)(value*ic->duration)); return true; case CAP_PROP_FORMAT: + if (!convertRGB) + return false; if (value == -1) return setRaw(); + else if (value == CV_8UC3) + { + enableAlpha = false; + return true; + } + else if (value == CV_8UC4) + { + enableAlpha = true; + return true; + } return false; case CAP_PROP_CONVERT_RGB: convertRGB = (value != 0); @@ -2775,6 +2810,12 @@ bool CvVideoWriter_FFMPEG::writeFrame( const unsigned char* data, int step, int return false; } } + else if (input_pix_fmt == AV_PIX_FMT_BGRA) { + if (cn != 4) { + CV_LOG_WARNING(NULL, "write frame skipped - expected 4 channels but got " << cn); + return false; + } + } else if (input_pix_fmt == AV_PIX_FMT_GRAY8 || input_pix_fmt == AV_PIX_FMT_GRAY16LE) { if (cn != 1) { CV_LOG_WARNING(NULL, "write frame skipped - expected 1 channel but got " << cn); @@ -3151,6 +3192,8 @@ bool CvVideoWriter_FFMPEG::open( const char * filename, int fourcc, use_opencl = params.get(VIDEOWRITER_PROP_HW_ACCELERATION_USE_OPENCL); } + bool enable_alpha = params.get(VIDEOWRITER_PROP_ENABLE_ALPHA, false); + if (params.warnUnusedParameters()) { CV_LOG_ERROR(NULL, "VIDEOIO/FFMPEG: unsupported parameters in VideoWriter, see logger INFO channel for details"); @@ -3185,7 +3228,7 @@ bool CvVideoWriter_FFMPEG::open( const char * filename, int fourcc, { switch (depth) { - case CV_8U: input_pix_fmt = AV_PIX_FMT_BGR24; break; + case CV_8U: input_pix_fmt = enable_alpha ? AV_PIX_FMT_BGRA : AV_PIX_FMT_BGR24; break; default: CV_LOG_WARNING(NULL, "Unsupported input depth for color image: " << depth); return false; @@ -3387,7 +3430,7 @@ bool CvVideoWriter_FFMPEG::open( const char * filename, int fourcc, break; default: // good for lossy formats, MPEG, etc. - codec_pix_fmt = AV_PIX_FMT_YUV420P; + codec_pix_fmt = enable_alpha ? AV_PIX_FMT_YUVA420P : AV_PIX_FMT_YUV420P; break; } diff --git a/modules/videoio/test/test_ffmpeg.cpp b/modules/videoio/test/test_ffmpeg.cpp index 85c1cd5457..a0b53219d6 100644 --- a/modules/videoio/test/test_ffmpeg.cpp +++ b/modules/videoio/test/test_ffmpeg.cpp @@ -1030,6 +1030,75 @@ inline static std::string videoio_ffmpeg_mismatch_name_printer(const testing::Te INSTANTIATE_TEST_CASE_P(/**/, videoio_ffmpeg_channel_mismatch, testing::ValuesIn(mismatch_cases), videoio_ffmpeg_mismatch_name_printer); +#ifndef _WIN32 + +typedef tuple AlphaChannelParams; +typedef testing::TestWithParam< AlphaChannelParams > videoio_ffmpeg_alpha_channel; + +// New feature in https://github.com/opencv/opencv/pull/28751 requires FFmpeg wrapper rebuild on Windows +TEST_P(videoio_ffmpeg_alpha_channel, write_read) +{ + if (!videoio_registry::hasBackend(CAP_FFMPEG)) + throw SkipTestException("FFmpeg backend was not found"); + + const int fourcc = fourccFromString(get<0>(GetParam())); + const string filename = "video_with_alpha_channel." + get<1>(GetParam()); + cv::VideoWriter writer(filename, cv::CAP_FFMPEG, fourcc, 1, Size(320, 240), + {VIDEOWRITER_PROP_IS_COLOR, 1, + VIDEOWRITER_PROP_ENABLE_ALPHA, 1}); + + ASSERT_TRUE(writer.isOpened()); + + for (int i = 0; i < 10; i ++) + { + cv::Mat frame; + cv::Mat gray_frame(240, 320, CV_8UC1, cv::Scalar::all(0)); + gray_frame(Rect(i*10, i*10, i*10, i*10)).setTo(255); + cv::Mat channels[4] = {gray_frame, gray_frame, gray_frame, gray_frame}; + cv::merge(channels, 4, frame); + writer.write(frame); + } + + writer.release(); + + cv::VideoCapture cap(filename, cv::CAP_FFMPEG, {cv::CAP_PROP_FORMAT, CV_8UC4}); + ASSERT_TRUE(cap.isOpened()); + ASSERT_EQ(10, cap.get(cv::CAP_PROP_FRAME_COUNT)); + + for (int i = 0; i < 10; i++) + { + cv::Mat frame; + cap >> frame; + EXPECT_EQ(4, frame.channels()); + EXPECT_EQ(320, frame.cols); + EXPECT_EQ(240, frame.rows); + EXPECT_EQ(0, frame.data[0]); + EXPECT_EQ(0, frame.data[1]); + EXPECT_EQ(0, frame.data[2]); + EXPECT_EQ(0, frame.data[3]); + + cv::Mat channels[4]; + cv::split(frame, channels); + int g_non_zero = cv::countNonZero(channels[1]); + int alpha_non_zero = cv::countNonZero(channels[3]); + + EXPECT_EQ(g_non_zero, alpha_non_zero); + } + remove(filename.c_str()); +} + +AlphaChannelParams alpha_params[] = +{ + make_tuple("FFV1", "mkv"), + make_tuple("FFV1", "avi") + // webm and hevc formats are disable as require fresh FFmpeg + //make_tuple("VP90", "webm") + //make_tuple("hevc", "mp4") +}; + +INSTANTIATE_TEST_CASE_P(/**/, videoio_ffmpeg_alpha_channel, testing::ValuesIn(alpha_params)); +#endif + // related issue: https://github.com/opencv/opencv/issues/23088 TEST(ffmpeg_cap_properties, set_pos_get_msec) {