Skip to content

Commit 9dfe233

Browse files
authored
Merge pull request #3477 from cudawarped:cudacodec_add_luma_hist
`cudacodec::VideoReader` return luma hist from `nextFrame` if requested
2 parents d89b2b9 + c49a420 commit 9dfe233

File tree

8 files changed

+231
-69
lines changed

8 files changed

+231
-69
lines changed

modules/cudacodec/include/opencv2/cudacodec.hpp

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ enum ChromaFormat
295295

296296
/** @brief Deinterlacing mode used by decoder.
297297
* @param Weave Weave both fields (no deinterlacing). For progressive content and for content that doesn't need deinterlacing.
298-
* Bob Drop one field.
298+
* @param Bob Drop one field.
299299
* @param Adaptive Adaptive deinterlacing needs more video memory than other deinterlacing modes.
300300
* */
301301
enum DeinterlaceMode
@@ -305,12 +305,22 @@ enum DeinterlaceMode
305305
Adaptive = 2
306306
};
307307

308+
/** @brief Utility function demonstrating how to map the luma histogram when FormatInfo::videoFullRangeFlag == false
309+
@param hist Luma histogram \a hist returned from VideoReader::nextFrame(GpuMat& frame, GpuMat& hist, Stream& stream).
310+
@param histFull Host histogram equivelent to downloading \a hist after calling cuda::calcHist(InputArray frame, OutputArray hist, Stream& stream).
311+
312+
@note
313+
- This function demonstrates how to map the luma histogram back so that it is equivalent to the result obtained from cuda::calcHist()
314+
if the returned frame was colorFormat::GRAY.
315+
*/
316+
CV_EXPORTS_W void MapHist(const GpuMat& hist, CV_OUT Mat& histFull);
317+
308318
/** @brief Struct providing information about video file format. :
309319
*/
310320
struct CV_EXPORTS_W_SIMPLE FormatInfo
311321
{
312-
CV_WRAP FormatInfo() : nBitDepthMinus8(-1), nBitDepthChromaMinus8(-1), ulWidth(0), ulHeight(0), width(0), height(0), ulMaxWidth(0), ulMaxHeight(0), valid(false),
313-
fps(0), ulNumDecodeSurfaces(0), videoFullRangeFlag(false) {};
322+
CV_WRAP FormatInfo() : nBitDepthMinus8(-1), ulWidth(0), ulHeight(0), width(0), height(0), ulMaxWidth(0), ulMaxHeight(0), valid(false),
323+
fps(0), ulNumDecodeSurfaces(0), videoFullRangeFlag(false), enableHistogram(false), nCounterBitDepth(0), nMaxHistogramBins(0){};
314324

315325
CV_PROP_RW Codec codec;
316326
CV_PROP_RW ChromaFormat chromaFormat;
@@ -331,6 +341,9 @@ struct CV_EXPORTS_W_SIMPLE FormatInfo
331341
CV_PROP_RW cv::Rect srcRoi;//!< Region of interest decoded from video source.
332342
CV_PROP_RW cv::Rect targetRoi;//!< Region of interest in the output frame containing the decoded frame.
333343
CV_PROP_RW bool videoFullRangeFlag;//!< Output value indicating if the black level, luma and chroma of the source are represented using the full or limited range (AKA TV or "analogue" range) of values as defined in Annex E of the ITU-T Specification. Internally the conversion from NV12 to BGR obeys ITU 709.
344+
CV_PROP_RW bool enableHistogram;//!< Flag requesting histogram output if supported. Exception will be thrown when requested but not supported.
345+
CV_PROP_RW int nCounterBitDepth;//!< Bit depth of histogram bins if histogram output is requested and supported.
346+
CV_PROP_RW int nMaxHistogramBins;//!< Max number of histogram bins if histogram output is requested and supported.
334347
};
335348

336349
/** @brief cv::cudacodec::VideoReader generic properties identifier.
@@ -376,6 +389,20 @@ class CV_EXPORTS_W VideoReader
376389
*/
377390
CV_WRAP virtual bool nextFrame(CV_OUT GpuMat& frame, Stream &stream = Stream::Null()) = 0;
378391

392+
/** @brief Grabs, decodes and returns the next video frame and frame luma histogram.
393+
394+
@param [out] frame The video frame.
395+
@param [out] histogram Histogram of the luma component of the encoded frame, see note.
396+
@param stream Stream for the asynchronous version.
397+
@return `false` if no frames have been grabbed.
398+
399+
If no frames have been grabbed (there are no more frames in video file), the methods return false.
400+
The method throws an Exception if error occurs.
401+
402+
@note Histogram data is collected by NVDEC during the decoding process resulting in zero performance penalty. NVDEC computes the histogram data for only the luma component of decoded output, not on post-processed frame(i.e. when scaling, cropping, etc. applied). If the source is encoded using a limited range of luma values (FormatInfo::videoFullRangeFlag == false) then the histogram bin values will correspond to to this limited range of values and will need to be mapped to contain the same output as cuda::calcHist(). The MapHist() utility function can be used to perform this mapping on the host if required.
403+
*/
404+
CV_WRAP_AS(nextFrameWithHist) virtual bool nextFrame(CV_OUT GpuMat& frame, CV_OUT GpuMat& histogram, Stream& stream = Stream::Null()) = 0;
405+
379406
/** @brief Returns information about video file format.
380407
*/
381408
CV_WRAP virtual FormatInfo format() const = 0;
@@ -535,16 +562,18 @@ but it cannot go below the number determined by NVDEC.
535562
@param srcRoi Region of interest (x/width should be multiples of 4 and y/height multiples of 2) decoded from video source, defaults to the full frame.
536563
@param targetRoi Region of interest (x/width should be multiples of 4 and y/height multiples of 2) within the output frame to copy and resize the decoded frame to,
537564
defaults to the full frame.
565+
@param enableHistogram Request output of decoded luma histogram \a hist from VideoReader::nextFrame(GpuMat& frame, GpuMat& hist, Stream& stream), if hardware supported.
538566
*/
539567
struct CV_EXPORTS_W_SIMPLE VideoReaderInitParams {
540-
CV_WRAP VideoReaderInitParams() : udpSource(false), allowFrameDrop(false), minNumDecodeSurfaces(0), rawMode(0) {};
568+
CV_WRAP VideoReaderInitParams() : udpSource(false), allowFrameDrop(false), minNumDecodeSurfaces(0), rawMode(0), enableHistogram(false){};
541569
CV_PROP_RW bool udpSource;
542570
CV_PROP_RW bool allowFrameDrop;
543571
CV_PROP_RW int minNumDecodeSurfaces;
544572
CV_PROP_RW bool rawMode;
545573
CV_PROP_RW cv::Size targetSz;
546574
CV_PROP_RW cv::Rect srcRoi;
547575
CV_PROP_RW cv::Rect targetRoi;
576+
CV_PROP_RW bool enableHistogram;
548577
};
549578

550579
/** @brief Creates video reader.

modules/cudacodec/misc/python/test/test_cudacodec.py

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,43 +14,61 @@ def setUp(self):
1414
@unittest.skipIf('OPENCV_TEST_DATA_PATH' not in os.environ,
1515
"OPENCV_TEST_DATA_PATH is not defined")
1616
def test_reader(self):
17-
#Test the functionality but not the results of the video reader
17+
# Test the functionality but not the results of the VideoReader
1818

19-
vid_path = os.environ['OPENCV_TEST_DATA_PATH'] + '/cv/video/1920x1080.avi'
19+
vid_path = os.environ['OPENCV_TEST_DATA_PATH'] + '/highgui/video/big_buck_bunny.h264'
2020
try:
2121
reader = cv.cudacodec.createVideoReader(vid_path)
2222
format_info = reader.format()
2323
ret, gpu_mat = reader.nextFrame()
2424
self.assertTrue(ret)
25-
self.assertTrue('GpuMat' in str(type(gpu_mat)), msg=type(gpu_mat))
25+
self.assertTrue(isinstance(gpu_mat, cv.cuda.GpuMat), msg=type(gpu_mat))
2626
#TODO: print(cv.utils.dumpInputArray(gpu_mat)) # - no support for GpuMat
2727

28+
# Retrieve format info
2829
if(not format_info.valid):
2930
format_info = reader.format()
3031
sz = gpu_mat.size()
3132
self.assertTrue(sz[0] == format_info.width and sz[1] == format_info.height)
3233

3334
# not checking output, therefore sepearate tests for different signatures is unecessary
34-
ret, _gpu_mat2 = reader.nextFrame(gpu_mat)
35-
#TODO: self.assertTrue(gpu_mat == gpu_mat2)
36-
self.assertTrue(ret)
35+
ret, gpu_mat_ = reader.nextFrame(gpu_mat)
36+
self.assertTrue(ret and gpu_mat_.cudaPtr() == gpu_mat.cudaPtr())
3737

38+
# Pass VideoReaderInitParams to the decoder and initialization params to the source (cv::VideoCapture)
3839
params = cv.cudacodec.VideoReaderInitParams()
3940
params.rawMode = True
41+
params.enableHistogramOutput = True
4042
ms_gs = 1234
43+
post_processed_sz = (gpu_mat.size()[0]*2, gpu_mat.size()[1]*2)
44+
params.targetSz = post_processed_sz
4145
reader = cv.cudacodec.createVideoReader(vid_path,[cv.CAP_PROP_OPEN_TIMEOUT_MSEC, ms_gs], params)
4246
ret, ms = reader.get(cv.CAP_PROP_OPEN_TIMEOUT_MSEC)
4347
self.assertTrue(ret and ms == ms_gs)
4448
ret, raw_mode = reader.getVideoReaderProps(cv.cudacodec.VideoReaderProps_PROP_RAW_MODE)
4549
self.assertTrue(ret and raw_mode)
4650

51+
# Retrieve image histogram
52+
ret, gpu_mat, hist = reader.nextFrameWithHist()
53+
self.assertTrue(ret and not gpu_mat.empty() and hist.size() == (256,1))
54+
ret, gpu_mat_, hist_ = reader.nextFrameWithHist(gpu_mat, hist)
55+
self.assertTrue(ret and not gpu_mat.empty() and hist.size() == (256,1))
56+
self.assertTrue(gpu_mat_.cudaPtr() == gpu_mat.cudaPtr() and hist_.cudaPtr() == hist.cudaPtr())
57+
hist_host = cv.cudacodec.MapHist(hist)
58+
self.assertTrue(hist_host.shape == (1,256) and isinstance(hist_host, np.ndarray))
59+
60+
# Check post processing applied
61+
self.assertTrue(gpu_mat.size() == post_processed_sz)
62+
63+
# Change color format
4764
ret, colour_code = reader.getVideoReaderProps(cv.cudacodec.VideoReaderProps_PROP_COLOR_FORMAT)
4865
self.assertTrue(ret and colour_code == cv.cudacodec.ColorFormat_BGRA)
4966
colour_code_gs = cv.cudacodec.ColorFormat_GRAY
5067
reader.set(colour_code_gs)
5168
ret, colour_code = reader.getVideoReaderProps(cv.cudacodec.VideoReaderProps_PROP_COLOR_FORMAT)
5269
self.assertTrue(ret and colour_code == colour_code_gs)
5370

71+
# Read raw encoded bitstream
5472
ret, i_base = reader.getVideoReaderProps(cv.cudacodec.VideoReaderProps_PROP_RAW_PACKAGES_BASE_INDEX)
5573
self.assertTrue(ret and i_base == 2.0)
5674
self.assertTrue(reader.grab())
@@ -75,8 +93,8 @@ def test_reader(self):
7593
else:
7694
self.skipTest(e.err)
7795

78-
def test_writer_existence(self):
79-
#Test at least the existence of wrapped functions for now
96+
def test_writer(self):
97+
# Test the functionality but not the results of the VideoWriter
8098

8199
try:
82100
fd, fname = tempfile.mkstemp(suffix=".h264")
@@ -91,11 +109,12 @@ def test_writer_existence(self):
91109
writer.write(blankFrameIn)
92110
writer.release()
93111
encoder_params_out = writer.getEncoderParams()
94-
self.assert_true(encoder_params_in.gopLength == encoder_params_out.gopLength)
112+
self.assertTrue(encoder_params_in.gopLength == encoder_params_out.gopLength)
95113
cap = cv.VideoCapture(fname,cv.CAP_FFMPEG)
96-
self.assert_true(cap.isOpened())
114+
self.assertTrue(cap.isOpened())
97115
ret, blankFrameOut = cap.read()
98-
self.assert_true(ret and blankFrameOut.shape == blankFrameIn.download().shape)
116+
self.assertTrue(ret and blankFrameOut.shape == blankFrameIn.download().shape)
117+
cap.release()
99118
except cv.error as e:
100119
self.assertEqual(e.code, cv.Error.StsNotImplemented)
101120
self.skipTest("Either NVCUVENC or a GPU hardware encoder is missing or the encoding profile is not supported.")

modules/cudacodec/src/video_decoder.cpp

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -96,18 +96,18 @@ void cv::cudacodec::detail::VideoDecoder::create(const FormatInfo& videoFormat)
9696
cudaVideoCodec_YUYV == _codec ||
9797
cudaVideoCodec_UYVY == _codec;
9898

99-
#if defined (HAVE_CUDA)
10099
#if (CUDART_VERSION >= 6050)
101-
codecSupported |= cudaVideoCodec_HEVC == _codec;
100+
codecSupported |= cudaVideoCodec_HEVC == _codec;
101+
#endif
102+
#if (CUDART_VERSION >= 7050)
103+
codecSupported |= cudaVideoCodec_YUV420 == _codec;
102104
#endif
103105
#if ((CUDART_VERSION == 7050) || (CUDART_VERSION >= 9000))
104-
codecSupported |= cudaVideoCodec_VP8 == _codec ||
105-
cudaVideoCodec_VP9 == _codec ||
106-
cudaVideoCodec_AV1 == _codec ||
107-
cudaVideoCodec_YUV420 == _codec;
106+
codecSupported |= cudaVideoCodec_VP8 == _codec || cudaVideoCodec_VP9 == _codec;
108107
#endif
108+
#if (CUDART_VERSION >= 9000)
109+
codecSupported |= cudaVideoCodec_AV1;
109110
#endif
110-
111111
CV_Assert(codecSupported);
112112
CV_Assert( cudaVideoChromaFormat_Monochrome == _chromaFormat ||
113113
cudaVideoChromaFormat_420 == _chromaFormat ||
@@ -123,31 +123,55 @@ void cv::cudacodec::detail::VideoDecoder::create(const FormatInfo& videoFormat)
123123
cuSafeCall(cuCtxPushCurrent(ctx_));
124124
cuSafeCall(cuvidGetDecoderCaps(&decodeCaps));
125125
cuSafeCall(cuCtxPopCurrent(NULL));
126-
if (!(decodeCaps.bIsSupported && (decodeCaps.nOutputFormatMask & (1 << cudaVideoSurfaceFormat_NV12)))){
127-
CV_LOG_ERROR(NULL, "Video source is not supported by hardware video decoder.");
128-
CV_Error(Error::StsUnsupportedFormat, "Video source is not supported by hardware video decoder");
126+
if (!(decodeCaps.bIsSupported && (decodeCaps.nOutputFormatMask & (1 << cudaVideoSurfaceFormat_NV12)))) {
127+
CV_Error(Error::StsUnsupportedFormat, "Video source is not supported by hardware video decoder refer to Nvidia's GPU Support Matrix to confirm your GPU supports hardware decoding of the video source's codec.");
128+
}
129+
130+
if (videoFormat.enableHistogram) {
131+
if (!decodeCaps.bIsHistogramSupported) {
132+
CV_Error(Error::StsBadArg, "Luma histogram output is not supported for current codec and/or on current device.");
133+
}
134+
135+
if (decodeCaps.nCounterBitDepth != 32) {
136+
std::ostringstream error;
137+
error << "Luma histogram output disabled due to current device using " << decodeCaps.nCounterBitDepth << " bit bins. Histogram output only supports 32 bit bins.";
138+
CV_Error(Error::StsBadArg, error.str());
139+
}
140+
else {
141+
videoFormat_.nCounterBitDepth = decodeCaps.nCounterBitDepth;
142+
videoFormat_.nMaxHistogramBins = decodeCaps.nMaxHistogramBins;
143+
}
129144
}
145+
130146
CV_Assert(videoFormat.ulWidth >= decodeCaps.nMinWidth &&
131147
videoFormat.ulHeight >= decodeCaps.nMinHeight &&
132148
videoFormat.ulWidth <= decodeCaps.nMaxWidth &&
133149
videoFormat.ulHeight <= decodeCaps.nMaxHeight);
134150

135-
CV_Assert((videoFormat.width >> 4)* (videoFormat.height >> 4) <= decodeCaps.nMaxMBCount);
151+
CV_Assert((videoFormat.width >> 4) * (videoFormat.height >> 4) <= decodeCaps.nMaxMBCount);
152+
#else
153+
if (videoFormat.enableHistogram) {
154+
CV_Error(Error::StsBadArg, "Luma histogram output is not supported when CUDA Toolkit version <= 9.0.");
155+
}
136156
#endif
157+
137158
// Create video decoder
138159
CUVIDDECODECREATEINFO createInfo_ = {};
160+
#if (CUDART_VERSION >= 9000)
161+
createInfo_.enableHistogram = videoFormat.enableHistogram;
162+
createInfo_.bitDepthMinus8 = videoFormat.nBitDepthMinus8;
163+
createInfo_.ulMaxWidth = videoFormat.ulMaxWidth;
164+
createInfo_.ulMaxHeight = videoFormat.ulMaxHeight;
165+
#endif
139166
createInfo_.CodecType = _codec;
140167
createInfo_.ulWidth = videoFormat.ulWidth;
141168
createInfo_.ulHeight = videoFormat.ulHeight;
142169
createInfo_.ulNumDecodeSurfaces = videoFormat.ulNumDecodeSurfaces;
143170
createInfo_.ChromaFormat = _chromaFormat;
144-
createInfo_.bitDepthMinus8 = videoFormat.nBitDepthMinus8;
145171
createInfo_.OutputFormat = cudaVideoSurfaceFormat_NV12;
146172
createInfo_.DeinterlaceMode = static_cast<cudaVideoDeinterlaceMode>(videoFormat.deinterlaceMode);
147173
createInfo_.ulTargetWidth = videoFormat.width;
148174
createInfo_.ulTargetHeight = videoFormat.height;
149-
createInfo_.ulMaxWidth = videoFormat.ulMaxWidth;
150-
createInfo_.ulMaxHeight = videoFormat.ulMaxHeight;
151175
createInfo_.display_area.left = videoFormat.displayArea.x;
152176
createInfo_.display_area.right = videoFormat.displayArea.x + videoFormat.displayArea.width;
153177
createInfo_.display_area.top = videoFormat.displayArea.y;
@@ -169,12 +193,10 @@ void cv::cudacodec::detail::VideoDecoder::create(const FormatInfo& videoFormat)
169193

170194
int cv::cudacodec::detail::VideoDecoder::reconfigure(const FormatInfo& videoFormat) {
171195
if (videoFormat.nBitDepthMinus8 != videoFormat_.nBitDepthMinus8 || videoFormat.nBitDepthChromaMinus8 != videoFormat_.nBitDepthChromaMinus8) {
172-
CV_LOG_ERROR(NULL, "Reconfigure Not supported for bit depth change");
173196
CV_Error(Error::StsUnsupportedFormat, "Reconfigure Not supported for bit depth change");
174197
}
175198

176199
if (videoFormat.chromaFormat != videoFormat_.chromaFormat) {
177-
CV_LOG_ERROR(NULL, "Reconfigure Not supported for chroma format change");
178200
CV_Error(Error::StsUnsupportedFormat, "Reconfigure Not supported for chroma format change");
179201
}
180202

@@ -183,7 +205,6 @@ int cv::cudacodec::detail::VideoDecoder::reconfigure(const FormatInfo& videoForm
183205
if ((videoFormat.ulWidth > videoFormat_.ulMaxWidth) || (videoFormat.ulHeight > videoFormat_.ulMaxHeight)) {
184206
// For VP9, let driver handle the change if new width/height > maxwidth/maxheight
185207
if (videoFormat.codec != Codec::VP9) {
186-
CV_LOG_ERROR(NULL, "Reconfigure Not supported when width/height > maxwidth/maxheight");
187208
CV_Error(Error::StsUnsupportedFormat, "Reconfigure Not supported when width/height > maxwidth/maxheight");
188209
}
189210
}

modules/cudacodec/src/video_decoder.hpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,12 @@ namespace cv { namespace cudacodec { namespace detail {
4949
class VideoDecoder
5050
{
5151
public:
52-
VideoDecoder(const Codec& codec, const int minNumDecodeSurfaces, cv::Size targetSz, cv::Rect srcRoi, cv::Rect targetRoi, CUcontext ctx, CUvideoctxlock lock) :
52+
VideoDecoder(const Codec& codec, const int minNumDecodeSurfaces, cv::Size targetSz, cv::Rect srcRoi, cv::Rect targetRoi, const bool enableHistogram, CUcontext ctx, CUvideoctxlock lock) :
5353
ctx_(ctx), lock_(lock), decoder_(0)
5454
{
5555
videoFormat_.codec = codec;
5656
videoFormat_.ulNumDecodeSurfaces = minNumDecodeSurfaces;
57+
videoFormat_.enableHistogram = enableHistogram;
5758
// alignment enforced by nvcuvid, likely due to chroma subsampling
5859
videoFormat_.targetSz.width = targetSz.width - targetSz.width % 2; videoFormat_.targetSz.height = targetSz.height - targetSz.height % 2;
5960
videoFormat_.srcRoi.x = srcRoi.x - srcRoi.x % 4; videoFormat_.srcRoi.width = srcRoi.width - srcRoi.width % 4;
@@ -88,13 +89,14 @@ class VideoDecoder
8889

8990
cudaVideoChromaFormat chromaFormat() const { return static_cast<cudaVideoChromaFormat>(videoFormat_.chromaFormat); }
9091
int nBitDepthMinus8() const { return videoFormat_.nBitDepthMinus8; }
92+
bool enableHistogram() const { return videoFormat_.enableHistogram; }
9193

9294
bool decodePicture(CUVIDPICPARAMS* picParams)
9395
{
9496
return cuvidDecodePicture(decoder_, picParams) == CUDA_SUCCESS;
9597
}
9698

97-
cuda::GpuMat mapFrame(int picIdx, CUVIDPROCPARAMS& videoProcParams)
99+
GpuMat mapFrame(int picIdx, CUVIDPROCPARAMS& videoProcParams)
98100
{
99101
CUdeviceptr ptr;
100102
unsigned int pitch;

0 commit comments

Comments
 (0)