7

I am capturing raw output from a decoder which is YUV420. I have got three pointers: Y(1920*1080), U(960*540) and V(960*540) separately.

I want to save the image as JPEG using OpenCV. I tried using cvtcolor of opencv

cv::Mat i_image(cv::Size(columns, rows), CV_8UC3, dataBuffer);
cv::Mat i_image_BGR(cv::Size(columns, rows), CV_8UC3);
cvtColor(i_image, i_image_BGR, cv::COLOR_YCrCb2BGR);
cv::imwrite("/data/data/org.myproject.debug/files/pic1.jpg", i_image_BGR);

But, here is the output image which is saved:

image

Can someone please suggest what is the proper way of saving the image?

YUV Binary files for reference

sgarizvi
  • 16,623
  • 9
  • 64
  • 98
  • Can you share the complete code? Seems like you are not converting YUV420 to YUV444 properly. – sgarizvi May 08 '18 at 09:34
  • we directly tried to convert from YUV420 to BGR. the above snippet is the whole thing used inside a function. Can you please share more information on how to convert YUV420 to YUV444 ? – kakumanu-sudhir May 08 '18 at 09:40
  • You said you've 3 pointers but you're only providing one pointer to `i_image`. – zindarod May 08 '18 at 09:52
  • It looks like the U and V components have to be replicated for 4 Y components. If you can share the individual input images (or the values of `dataBuffer`), I will be able to test a sample code. – sgarizvi May 08 '18 at 09:52
  • @zindarod... Yeah, I think OP is mis-interpreting the pixel values in the `dataBuffer`. – sgarizvi May 08 '18 at 09:53
  • @zindarod... dataBuffer is the Y(1920*1080) plane[0] output of decoder. I am not sure how to utilize the U(960*540) plane[1] and V(960*540) plane[2] in OpenCV. – kakumanu-sudhir May 08 '18 at 10:01
  • @sgarizvi since i have three data buffers, is it okay if i can share the data in binary format files and share it to you? – kakumanu-sudhir May 08 '18 at 10:03
  • Read this [page](https://en.wikipedia.org/wiki/YUV#Y%E2%80%B2UV420p_(and_Y%E2%80%B2V12_or_YV12)_to_RGB888_conversion), notice the total size of buffer, the position of Y, U and V components. Create a new buffer and copy the data from the 3 pointers to the correct positions in the new pointer. – zindarod May 08 '18 at 10:09
  • @sgarizvi Yeah, he just needs to understand the YUV data layout. – zindarod May 08 '18 at 10:09
  • @kakumanu-sudhir... Yes, please share the binary file. – sgarizvi May 08 '18 at 10:18
  • @sgarizvi, i have edited the post with link for uploaded YUV binary files. thanks. – kakumanu-sudhir May 08 '18 at 12:13
  • Does this answer your question? [How to read a frame from YUV file in OpenCV?](https://stackoverflow.com/questions/2231518/how-to-read-a-frame-from-yuv-file-in-opencv) – Antonio Feb 22 '23 at 21:10

3 Answers3

9

Following is the process to convert the provided YUV files into RGB image.

  • Read Y, U and V binary files into byte buffers
  • Create OpenCV Mat object from the created buffers.
  • Resize U and V Mats to the size of Y.
  • Merge Y and resized U and V.
  • Convert from YUV to BGR

Be advised that the resizing step is just an optimized way of repeating the values of U and V. This is only valid in the case where Y has twice the resolution of U and V in both dimensions. This approach should be invalid for arbitrary size images (not tested).

Here is the code for the above-mentioned process.

#include <iostream>
#include <vector>
#include <opencv2/opencv.hpp>

std::vector<unsigned char> readBytesFromFile(const char* filename)
{
    std::vector<unsigned char> result;

    FILE* f = fopen(filename, "rb");

    fseek(f, 0, SEEK_END);  // Jump to the end of the file
    long length = ftell(f); // Get the current byte offset in the file
    rewind(f);              // Jump back to the beginning of the file

    result.resize(length);

    char* ptr = reinterpret_cast<char*>(&(result[0]));
    fread(ptr, length, 1, f); // Read in the entire file
    fclose(f); // Close the file

    return result;
}

int main(int argc, char** argv)
{
    cv::Size actual_size(1920, 1080);
    cv::Size half_size(960, 540);

    //Read y, u and v in bytes arrays
    auto y_buffer = readBytesFromFile("ypixel.bin");
    auto u_buffer = readBytesFromFile("upixel.bin");
    auto v_buffer = readBytesFromFile("vpixel.bin");


    cv::Mat y(actual_size, CV_8UC1, y_buffer.data());
    cv::Mat u(half_size, CV_8UC1, u_buffer.data());
    cv::Mat v(half_size, CV_8UC1, v_buffer.data());

    cv::Mat u_resized, v_resized;
    cv::resize(u, u_resized, actual_size, 0, 0, cv::INTER_NEAREST); //repeat u values 4 times
    cv::resize(v, v_resized, actual_size, 0, 0, cv::INTER_NEAREST); //repeat v values 4 times

    cv::Mat yuv;

    std::vector<cv::Mat> yuv_channels = { y, u_resized, v_resized };
    cv::merge(yuv_channels, yuv);

    cv::Mat bgr;
    cv::cvtColor(yuv, bgr, cv::COLOR_YUV2BGR);
    cv::imwrite("bgr.jpg", bgr);

    return 0;
}

Compiled and tested with following command:

g++ -o yuv2rgb -std=c++11 yuv2rgb.cpp -L/usr/local/lib -lopencv_core -lopencv_imgcodecs -lopencv_highgui -lopencv_imgproc

Following output image is generated by executing the above code:

YUV To BGR Output

sgarizvi
  • 16,623
  • 9
  • 64
  • 98
  • 1
    Works like a charm. Thanks a lot sgarizvi. – kakumanu-sudhir May 08 '18 at 13:18
  • This is inefficient, the other answer point to the right OpenCV function and approach – Antonio Feb 22 '23 at 21:09
  • @Antonio... Yeah, the efficient answer is 4 years late. Unfortunately at the time of my answer, the latest stable version of OpenCV was `3.4.1` which did not have an implementation of `COLOR_YUV2BGR_I420`. :) – sgarizvi Feb 23 '23 at 09:54
  • That doesn't seem to be the case, the method was already available in 2015 https://stackoverflow.com/a/30539306/2436175 and referred to in the other answer given the day you gave yours. Nonetheless, good answers must be kept up to date and if new information comes around, they should have a preliminary section referencing it. – Antonio Feb 23 '23 at 11:12
3

I think OpenCV matrix for your input yuv420 planar image should have 1 channel format instead of 3 channel. Place there Y channel, then U, then V. I found a very similar question HERE Planar YUV420 and NV12 is the same

3

For those who came here for YUV 420 to BGR/RGB Conversion (First Google search Result), Please refer to this code:

#include "opencv2/opencv.hpp"
#include "opencv2/opencv_modules.hpp"
#include <iostream>
#include <fstream>

using namespace cv;
using namespace std;

int main(int argc, char** argv)
{
    Size iSize(512,512);
    int iYUV_Size = iSize.width * (iSize.height + iSize.height / 2);
    
    Mat mSrc_YUV420(cv::Size(iSize.width,iSize.height+ iSize.height / 2),CV_8UC1);

    ifstream FileIn;
    FileIn.open("Sample_YUV420_512x512.yuv", ios::binary | ios::in);

    if (FileIn.is_open())
    {
        FileIn.read((char*)mSrc_YUV420.data, iYUV_Size);
        FileIn.close();
    }
    else
    {
        printf("[Error] Unable to Read the Input File! \n");
    }

    Mat mSrc_BGR(cv::Size(iSize.width, iSize.height), CV_8UC1);

    cv::cvtColor(mSrc_YUV420, mSrc_BGR, COLOR_YUV2BGR_I420);

    imshow("Input YUV Image", mSrc_YUV420);

enter image description here

    imshow("Input RGB Image", mSrc_BGR);

enter image description here

    cv::waitKey();

    return 0;
}
Balaji R
  • 1,805
  • 22
  • 41
  • 1
    This is the most correct answer. It shows for example how to use the `COLOR_YUV2BGR_I420` with cv::cvtColor. Moreover, it shows how the input yuv image has to be stored in a _single channel_ image with height/2 extra rows. The principle is explained here https://stackoverflow.com/a/30539306/2436175 – Antonio Feb 22 '23 at 21:08