-
-
Notifications
You must be signed in to change notification settings - Fork 56.2k
fix #26276: cv::VideoWriter fails writing colorless images #27153
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: 4.x
Are you sure you want to change the base?
fix #26276: cv::VideoWriter fails writing colorless images #27153
Conversation
2f81a81
to
aea7f0f
Compare
The internal return values of the FFmpeg library are now correctly handled in the FFmpeg backend. The writeFrame function now considers that the FFmpeg return code AVERROR(EAGAIN) indicates the encoder needs more input, so a new frame must be sent instead of treating it as a real error. Additionally, the return value of CvVideoWriter_FFMPEG::writeFrame is now properly propagated back to the write function in the cap_ffmpeg.cpp file, which displays a warning when writeFrame returns false. Additionally, a conversion of the input to grayscale was implemented before entering FFmpeg backend, as the encoder expects grayscale frames when isColor=false. This requires converting from CV_8UC3 to grayscale (a simple color conversion is done here using the cvtColor function). Finally, a new test case has been implemented to verify that the grayscale conversion is correctly applied.
aea7f0f
to
30a5ad1
Compare
@mshabunin @opencv-alalek Could you take a look? |
If I remember correctly there might be issues with building FFmpeg plugin on Windows because of dependency on OpenCV. |
Looks like VideoWriter behaviour is not consistent here:
|
@asmorkalov @mshabunin @opencv-alalek Thank you for the feedback. I agree that consistency across backends is important, and I think the best approach is to convert data/input to the desired format, as done in MS media foundation and Android. Key changes:FFmpeg:
GStreamer:
What the conversions would look like in both backends:cv::Mat mat(height, width, type, (void*)data, step);
if (input_pix_fmt == AV_PIX_FMT_BGR24) {
if (type != CV_8UC3) {
if (type == CV_16UC1) // CV_16UC1 -> CV_8UC1
mat.convertTo(mat, CV_8UC1, 1.0 / 256); // scale down 16-bit to 8-bit
cv::cvtColor(mat, mat, COLOR_GRAY2BGR); // CV_8UC1 -> CV_8UC3
data = mat.data;
step = (int)mat.step;
type = CV_8UC3;
}
}
else if (input_pix_fmt == AV_PIX_FMT_GRAY8) {
if (type != CV_8UC1) {
if (type == CV_8UC3) // CV_8UC3 -> CV_8UC1
cv::cvtColor(mat, mat, COLOR_BGR2GRAY);
else // CV_16UC1 -> CV_8UC1
mat.convertTo(mat, CV_8UC1, 1.0 / 256); // scale down 16-bit to 8-bit
data = mat.data;
step = (int)mat.step;
type = CV_8UC1;
}
}
else if (input_pix_fmt == AV_PIX_FMT_GRAY16LE) {
if (type != CV_16UC1) {
if (type == CV_8UC3)
cv::cvtColor(mat, mat, COLOR_BGR2GRAY); // CV_8UC3 -> CV_8UC1
mat.convertTo(mat, CV_16UC1); // CV_8UC1 -> CV_16UC1
data = mat.data;
step = (int)mat.step;
type = CV_16UC1;
}
}
else {
CV_LOG_WARNING(NULL, "Input data does not match selected pixel format: "
<< av_get_pix_fmt_name(input_pix_fmt)
<< ", depth: " << type);
CV_Assert(false);
} Test Cases to Add:
Does this approach seem reasonable? I'm ready to implement these changes. |
Hi @asmorkalov, is there anything I should address or clarify to help move this PR forward? In my last message, I suggested aligning the behavior of the FFmpeg and GStreamer backends with that of MS Media Foundation and Android — handling input conversion internally. Specifically, the idea is to perform the necessary conversions inside the I also plan to add test cases to cover these scenarios and make sure the conversions behave as expected. Please let me know if anything is unclear or if you’d prefer a different approach. I’m ready to make any needed adjustments and move forward with the implementation. |
Aligns FFmpeg backend behavior with MS Media Foundation and Android, by moving the internal conversion logic to writeFrame(). This now handles all relevant format conversions: - CV_8UC3 -> CV_8UC1 or CV_16UC1 - CV_8UC1 -> CV_8UC3 or CV_16UC1 - CV_16UC1 -> CV_8UC1 or CV_8UC3 It still corrects AVERROR(EAGAIN) handling, which was previously misinterpreting it as an error and displays a warning when writeFrame returns false. Adds more test cases ensuring all possible conversions are properly tested.
Hi @asmorkalov @mshabunin @opencv-alalek, Since I didn’t hear back, I decided to move forward and implement the changes I previously proposed. The update improves consistency between the FFmpeg backend and others like MS Media Foundation and Android. Specifically:
Please let me know if you’d like any changes or have further feedback. |
@asmorkalov @opencv-alalek @mshabunin friendly reminder |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Silent ignorance of the error is a design bug.
But implicit data manipulation is an evil too.
There are no safe operations, except may be COLOR_GRAY2BGR
.
So required conversions should be left on the user side.
.write()
should fail properly instead if input is not expected.
{ | ||
if (!encode_video) { | ||
CV_Assert(cn == 1 && ((width > 0 && height == 1) || (width == 1 && height > 0 && step == 1))); | ||
CV_Assert((type == CV_16UC1 || type == CV_8UC1) && ((width > 0 && height == 1) || (width == 1 && height > 0 && step == 1))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In case of encode_video = False
we should accept only raw bytes arrays.
So we should have CV_8UC1 data type only.
Expected layout is 1xN or Nx1.
This change must be reverted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for pointing that out! The original logic only checked if cn == 1, so I mistakenly assumed both CV_8UC1 and CV_16UC1 were acceptable. I’ll revert this logic in the next commit.
convertedMat = inputMat; | ||
else { | ||
if (type == CV_16UC1) // CV_16UC1 -> CV_8UC1 | ||
inputMat.convertTo(inputMat, CV_8UC1, 1.0 / 256); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
inputMat ... inputMat
"In-place" ops should not be used in general (causes extra .clone()
call).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the heads-up, I’ll avoid in-place operations moving forward.
if (type == CV_16UC1) // CV_16UC1 -> CV_8UC1 | ||
inputMat.convertTo(inputMat, CV_8UC1, 1.0 / 256); | ||
cv::cvtColor(inputMat, convertedMat, cv::COLOR_GRAY2BGR); // CV_8UC1 -> CV_8UC3 | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is about other cases?
16UC3?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. I had focused only on the explicitly supported types (CV_8UC1, CV_16UC1, CV_8UC3) and overlooked the possibility of unsupported inputs like CV_16UC3. I’ll address this as well.
Given that there are no safe operations, the best approach is indeed to leave format handling to the user. Since .write() is a void function, changing its return type would require a broader change (though it could be beneficial). Instead, this will be addressed through clearer warnings:
|
Fixes a minor typo in the warning message for type mismatches in the GStreamer backend. ("... expected CV_16UC3" -> "... expected CV_16UC1") This change is unrelated to the FFmpeg logic but improves consistency across backends.
Replaces implicit data manipulation with a warning-based approach. Following reviewer feedback, data conversion is now explicitly left to the user, and no automatic transformations are performed in writeFrame().
Since no frames should be written, simply checking if video capture fails to open is enough. Also reduced the frames to be written from 90 to 30
Test cases are skipped when writer fails to open. Trailing whitespaces were also removed
I've pushed a new version addressing @opencv-alalek’s feedback. All required conversions should now be handled by the user. I kept the However, on Win64, one test case fails: when the input is I would appreciate your input, as the other two test cases behave as expected by failing due to the mismatch. Is this platform-specific behaviour expected, or am I missing something? Thanks! Edit: My System Information: |
When writing grayscale images (
isColor=false
),cv::VideoWriter
failed as FFmpeg backend requires input frames to be in a grayscale format. Unlike other backends, FFmpeg doesn't perform this conversion internally and expects the input to be pre-converted. To fix this, I inserted a check in theCvVideoWriter_FFMPEG_proxy::write
to convert input frames to grayscale when the input has more than 1 channel. If this is true, then the input is converted to a gray image usingcv::cvtColor
(withcv::COLOR_BGR2GRAY
).Additionally, as suggested in the issue comments, I have correctly propagated the return value of
CvVideoWriter_FFMPEG::writeFrame
back to theCvVideoWriter_FFMPEG_proxy::write
. This return value wasn't being used, andwriteFrame
was always returning false since the FFmeg's return codeAVERROR(EAGAIN)
was mistakenly being treated as an error. Now it's handled as a signal for additional input (current input was successfully written, and a new frame should be sent. See FFmpeg documentation foravcodec_receive_packet
). A warning is displayed ifCvVideoWriter_FFMPEG::writeFrame
returns false. Alternatively, this could be propagated back up to the user, making cv::VideoWriter::write a boolean.Finally, I added a test case to verify if the grayscale conversion is being done correctly.
Fixes #26276
Pull Request Readiness Checklist
See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request
Patch to opencv_extra has the same branch name.