2

I am building a program in objective C/C++ and openCV. I am pretty skilled in Objective C but new to C++.
I am building custom RGB2HSV algorithm. My algorithm is slightly different from the openCV library cvtColor(in, out, CV_RGB2HSV).
The one I try to translate form Matlab to opencV/C++ produces so clear HSV image that no additional filtering is needed before further processing. Code below – Matlab code is self-explanatory.

I try to translate it to C++/openCV function out of it but I hit the wall trying to access pixel values of the image. I am new to C++. I read a lot on the ways how to access Mat structure but usually I obtain either bunch of letters in a place of zeros or a number typically something like this “\202 k g”. When I try to do any multiplication operations on the say \202 the result has nothing to do with math.

Please help me to properly access the pixel values. Also in current version using uchar won’t work because some values are outside 0-255 range. The algorithm is not mine. I cannot even point the source but it gives clearly better results than stock RGB2HSV.

Also the algorithm below is for one pixel. It needs to be applied each pixel in the image so in final version it need to wrapped with for { for {}} loops.

I also wish to share this method with community so everyone can benefit from it and saving on pre-filtering.

Please help me translate it to C++ / openCV. If possible with the best practices speed wise. Or at least how to clearly access the pixel value so it is workable with range of mathematical equations. Thanks in advance.

function[H, S, V] = rgb2hsvPixel(R,G,B)

% Algorithm:

% In case of 8-bit and 16-bit images, `R`, `G`, and `B` are converted to the
% floating-point format and scaled to fit the 0 to 1 range.
%
%    V = max(R,G,B)
%    S = / (V - min(R,G,B)) / V               if V != 0
%        \ 0                                  otherwise
%        / 60*(G-B) / (V - min(R,G,B))        if V=R
%    H = | 120 + 60*(B-R) / (V - min(R,G,B))  if V=G
%        \ 240 + 60*(R-G) / (V - min(R,G,B))  if V=B
%
% If `H<0` then `H=H+360`. On output `0<=V<=1`, `0<=S<=1`, `0<=H<=360`.



        red = (double(R)-16)*255/224;                 % \  
        green = (double(G)-16)*255/224;               %  }- R,G,B  (0 <-> 255) ->  (-18.2143 <-> 272.0759)
        blue = (min(double(B)*2,240)-16)*255/224;     % /
        minV = min(red,min(green,blue));
        value = max(red,max(green,blue));

        delta = value - minV;
        if(value~=0)
            sat = (delta*255) / value;% s
            if (delta ~= 0) 
                if( red == value )
                    hue = 60*( green - blue ) / delta;      % between yellow & magenta
                elseif( green == value )
                    hue = 120 + 60*( blue - red ) / delta;  % between cyan & yellow
                else
                    hue = 240 + 60*( red - green ) / delta; % between magenta & cyan
                end
                if( hue < 0 )
                    hue = hue + 360;
                end
            else 
                hue = 0;
                sat = 0;
            end
        else 
            % r = g = b = 0
            sat = 0;
            hue = 0;
        end
        H = max(min(floor(((hue*255)/360)),255),0);
        S = max(min(floor(sat),255),0);
        V = max(min(floor(value),255),0);
    end
Jad Gift
  • 305
  • 4
  • 15
  • What are you asking exactly ? How to access the value of one pixel in a Mat object in C++ ? Or you want a complete, working code for converting RGB to HSV ? – Sunreef May 27 '16 at 06:15
  • At least how to get to the workable value of a pixel. If someone can help build the entire function that would be beneficial for community. I can build it with simple mathematical logic but I am aware that it won't me the most effective way in C++ – Jad Gift May 27 '16 at 06:22

3 Answers3

1

To access the value of a pixel in a 3-channel, 8-bit precision image (type CV_8UC3) you have to do it like this:

cv::Mat image;
cv::Vec3b BGR = image.at<cv::Vec3b>(i,j);

If, as you say, 8-bit precision and range are not enough, you can declare a cv::Mat of type CV_32F to store floating point 32-bit numbers.

cv::Mat image(height, width, CV_32FC3);
//fill your image with data
for(int i = 0; i < image.rows; i++) {
    for(int j = 0; j < image.cols; j++) {
        cv::Vec3f BGR = image.at<cv::Vec3f>(i,j)
        //process your pixel
        cv::Vec3f HSV; //your calculated HSV values
        image.at<cv::Vec3f>(i,j) = HSV;
    }
}

Be aware that OpenCV stores rgb values in the BGR order and not RGB. Take a look at OpenCV docs to learn more about it.

Sunreef
  • 4,452
  • 21
  • 33
  • 1
    I think that it should be: `cv::Mat image(height, width, CV_32FC3);` instead of `cv::Mat image(height, width, CV_32F);`? – Catree May 27 '16 at 15:22
  • @Catree You're right. My bad, I didn't pay attention. I'll correct that. – Sunreef May 27 '16 at 18:21
  • I am testing ways to access pixel value: cout << " "<< fixed; cv::Vec3f BGR = image.at(x,y); cout << BGR << endl; cout << image.at(x,y)[0] << endl; cout << image.at(x,y) << endl; cout << image.at(x,y) << endl; in the terminal window, the same point has all different values according to use of Vec3b Vec3f Vec3s here they are: [4087829327384284871262208.000000, 0.000000, 0.000000] z [122, 120, 119] [28272, 26989, 26215] [15648626369888256.000000, 1024100729431785472.000000] **am I missing something?** – Jad Gift May 27 '16 at 20:00
  • No, it's a normal result. By specifying Vec3b, you are telling OpenCV that the matrix is supposed to contain 8-bit data. With Vec3f, it will be 32-bit floating point data. And Vec3s will be 16-bit short data. This affects how OpenCV accesses the underlying raw data. And in case you are wondering why you are getting a letter z, that's because `image.at(x,y)[0]` is a `uchar` that `cout` treats as a letter. – Sunreef May 27 '16 at 20:31
  • Ok, I just tested it, you are right its just different numerical representations of the same pixel intensity in different numerical ranges. – Jad Gift May 27 '16 at 21:14
  • 1
    In fact, you are not getting the same pixel by using Vec3b or Vec3f. Let me explain: you have a 2x2 matrix [[a,b],[c,d]]. Let's say that each letter represents a Vec3b. image.at(0,0) will get you the value of a. Whereas image.at(0,0) will get you a Vec3s whose coordinates are 16-bit shorts made from the concatenation of the bits in a and b. And image.at(1,1) will result with an error because you are going out of range of the raw data in the matrix. – Sunreef May 27 '16 at 21:16
0

If you are concerned by performance and fairly comfortable with pixel indexes, you can use directly the Mat ptr.

For example:

  cv::Mat img = cv::Mat::zeros(4, 8, CV_8UC3);

  uchar *ptr_row_img;
  int cpt = 0;
  for(int i = 0; i < img.rows; i++) {
    ptr_row_img = img.ptr<uchar>(i);

    for(int j = 0; j < img.cols; j++) {
      for(int c = 0; c < img.channels(); c++, cpt++, ++ptr_row_img) {
        *ptr_row_img = cpt;
      }
    }
  }

  std::cout << "img=\n" << img << std::endl;

The previous code should print:

img= [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23; 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47; 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71; 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95]

The at access should be enough for most of the cases and is much more readable / less likely to make a mistake than using the ptr access.

References:

Catree
  • 2,477
  • 1
  • 17
  • 24
  • For efficient access, I think the best is still to use a raw pointer to the data `Vec3b* imageData = (Vec3b*) image.data;` – Sunreef May 27 '16 at 21:25
0

enter image description hereThanks everybody for help. Thanks to your hints I constructed the custom rgb2hsv function C++/openCV.

From the top left respectively, edges after bgr->gray->edges, bgr->HSV->edges, bgr->customHSV->edges Below each of them corresponding settings of the filters to achieve approximately the same clear results. The bigger the radius of a filter the more complex and time consuming computations.

It produces clearer edges in next steps of image processing. It can be tweaked further experimenting with parameters in r g b channels:

red = (red-16)*1.1384; //255/244=1.1384 here 16 – the bigger the number the clearer V becomes 255/244 – also affect the outcome extending it beyond ranges 0-255, later to be clipped. This numbers here seem to be golden ratio but anyone can adjust for specific needs.

With this function translating BGR to RGB can be avoided by directly connecting colors to proper channels in raw image.

Probably it is a little clumsy performance wise. In my case it serves in first step of color balance and histogram adjustment so speed is not that critical.

To use in constant processing video stream it need speed optimization, I think by using pointers and reducing loop complexity. Optimization is not exactly my cup of tea. So if someone helped to optimize it for the community that would be great. Here it is ready to use:

Mat bgr2hsvCustom ( Mat& image )
{
    //smallParam = 16;
    for(int x = 0; x < image.rows; x++)
    {
        for(int y = 0; y<image.cols; y++)
        {
            //assigning vector to individual float BGR values
            float blue  = image.at<cv::Vec3b>(x,y)[0];
            float green = image.at<cv::Vec3b>(x,y)[1];
            float red   = image.at<cv::Vec3b>(x,y)[2];

            float sat, hue, minValue, maxValue, delta;

            float const ang0    = 0; // func min and max don't accept varaible and number
            float const ang240  = 240;
            float const ang255  = 255;

            red = (red-16)*1.1384; //255/244
            green = (green-16)*1.1384;
            blue = (min(blue*2,ang240)-16)*1.1384;
            minValue = min(red,min(green,blue));
            maxValue = max(red,max(green,blue));
            delta = maxValue - minValue;

            if (maxValue != 0)
            {
                sat = (delta*255) / maxValue;
                if ( delta != 0)
                {
                    if (red == maxValue){
                        hue  =      60*(green - blue)/delta;
                    }
                    else if( green == maxValue ) {
                        hue = 120 + 60*( blue - red )/delta;
                    }
                    else{
                        hue = 240 + 60*( red - green )/delta;
                    }
                    if( hue < 0 ){
                        hue = hue + 360;
                    }
                }
                else{
                    sat = 0;
                    hue = 0;
                }
            }
            else{
                hue = 0;
                sat = 0;
            }
            image.at<cv::Vec3b>(x,y)[0] = max(min(floor(maxValue),ang255),ang0);         //V
            image.at<cv::Vec3b>(x,y)[1] = max(min(floor(sat),ang255),ang0);              //S
            image.at<cv::Vec3b>(x,y)[2] = max(min(floor(((hue*255)/360)),ang255),ang0);  //H
        }
    }
    return image;
}
Jad Gift
  • 305
  • 4
  • 15