1

I have a OpenCV C++ application. I have segmented an image with pyrMeanShiftFiltering function. Now I need to count the pixel in a segment and the number of pixel having the most frequent value in the same segment in order to compute a ratio between them. How could I do that?

I am using Tsukuba image and the code is.
Tsukuba

Mat image, segmented;
image = imread("TsukubaL.jpg", 1 );
pyrMeanShiftFiltering(image, segmented, 16, 32);

The segmented image is:
Segmented tsukuba
If I consider a pixel in a single segment, the part where I count the pixel in that segment is:

int cont=0;
Vec3b x = segmented.at<Vec3b>(160, 136);
for(int i = 160; i < segmented.rows; ++i) { //check right-down
    for(int j = 136; j < segmented.cols; ++j) {
        if(segmented.at<Vec3b>(i, j) == x)
            cont++;
        else
            continue;
    }
}
for(int i = 160; i > 0; --i) {  //check right-up
    for(int j = 136; j < segmented.cols; ++j) {
        if(segmented.at<Vec3b>(i, j) == x)
            cont++;
        else
            continue;
    }
}
for(int i = 160; i < segmented.rows; ++i) { //check down-left
    for(int j = 136; j > 0; --j) {
        if(segmented.at<Vec3b>(i, j) == x)
            cont++;
        else
            continue;
    }
}
for(int i = 160; i > 0; --i) {  //check up-left
    for(int j = 136; j > 0; --j) {
        if(segmented.at<Vec3b>(i, j) == x)
            cont++;
        else
            continue;
    }
}
cout<<"Pixel "<<x<<"cont = "<<cont<<endl;  

In this example, I consider a white pixel in position (160, 136) and count the same pixel to the central one in the four direction starting from it, and the output is:

Pixel [206, 222, 240]cont = 127

Could it be a possible good way to do it?

Miki
  • 40,887
  • 13
  • 123
  • 202
Franktrt
  • 373
  • 1
  • 8
  • 18

2 Answers2

2

First you need to define a mask with pixels having the same color of your initial point (called seed here). You can use inRange with a given tolerance. Assuming a seed on the head, you'll get something like:

enter image description here

Now you need to find the connected component that contains your seed. You can do this in many ways. Here I modified a generative labeling algorithm (the can be found here). You get the list of points of the blob that contains the seed. You can then make a mask with these points:

enter image description here

Now that you have all points it's trivial to find the number of points in the segment. To find the most frequent color you can make an histogram with the BGR values contained in the segment. Since an histogram with all RGB values will have 256*256*256 bins, it's more practical to use a map. I modified the code found here to make an histogram with a given mask.

Now you just need to find the color value with higher frequency. For this example, I got:

# points in segment: 2860
Most frequent color: [209, 226, 244]    #: 168

Take a look at the code:

#include <opencv2/opencv.hpp>
#include <vector>
#include <stack>
#include <map>
using namespace cv;
using namespace std;

vector<Point> connected_components(const Mat1b& img, Point seed)
{
    Mat1b src = img > 0;

    int label = 0;
    int w = src.cols;
    int h = src.rows;
    int i;

    cv::Point point;

    // Start from seed
    std::stack<int, std::vector<int>> stack2;
    i = seed.x + seed.y*w;
    stack2.push(i);

    // Current component
    std::vector<cv::Point> comp;

    while (!stack2.empty())
    {
        i = stack2.top();
        stack2.pop();

        int x2 = i%w;
        int y2 = i / w;

        src(y2, x2) = 0;

        point.x = x2;
        point.y = y2;
        comp.push_back(point);

        // 4 connected
        if (x2 > 0 && (src(y2, x2 - 1) != 0))
        {
            stack2.push(i - 1);
            src(y2, x2 - 1) = 0;
        }
        if (y2 > 0 && (src(y2 - 1, x2) != 0))
        {
            stack2.push(i - w);
            src(y2 - 1, x2) = 0;
        }
        if (y2 < h - 1 && (src(y2 + 1, x2) != 0))
        {
            stack2.push(i + w);
            src(y2 + 1, x2) = 0;
        }
        if (x2 < w - 1 && (src(y2, x2 + 1) != 0))
        {
            stack2.push(i + 1);
            src(y2, x2 + 1) = 0;
        }

        // 8 connected
        if (x2 > 0 && y2 > 0 && (src(y2 - 1, x2 - 1) != 0))
        {
            stack2.push(i - w - 1);
            src(y2 - 1, x2 - 1) = 0;
        }
        if (x2 > 0 && y2 < h - 1 && (src(y2 + 1, x2 - 1) != 0))
        {
            stack2.push(i + w - 1);
            src(y2 + 1, x2 - 1) = 0;
        }
        if (x2 < w - 1 && y2>0 && (src(y2 - 1, x2 + 1) != 0))
        {
            stack2.push(i - w + 1);
            src(y2 - 1, x2 + 1) = 0;
        }
        if (x2 < w - 1 && y2 < h - 1 && (src(y2 + 1, x2 + 1) != 0))
        {
            stack2.push(i + w + 1);
            src(y2 + 1, x2 + 1) = 0;
        }
    }

    return comp;
}


struct lessVec3b
{
    bool operator()(const Vec3b& lhs, const Vec3b& rhs) {
        return (lhs[0] != rhs[0]) ? (lhs[0] < rhs[0]) : ((lhs[1] != rhs[1]) ? (lhs[1] < rhs[1]) : (lhs[2] < rhs[2]));
    }
};

map<Vec3b, int, lessVec3b> getPalette(const Mat3b& src, const Mat1b& mask)
{
    map<Vec3b, int, lessVec3b> palette;
    for (int r = 0; r < src.rows; ++r)
    {
        for (int c = 0; c < src.cols; ++c)
        {
            if (mask(r, c))
            {
                Vec3b color = src(r, c);
                if (palette.count(color) == 0)
                {
                    palette[color] = 1;
                }
                else
                {
                    palette[color] = palette[color] + 1;
                }
            }
        }
    }
    return palette;
}


int main()
{
    // Read the image
    Mat3b image = imread("tsukuba.jpg");

    // Segment
    Mat3b segmented;
    pyrMeanShiftFiltering(image, segmented, 16, 32);

    // Seed
    Point seed(140, 160);

    // Define a tolerance
    Vec3b tol(10,10,10);

    // Extract mask of pixels with same value as seed
    Mat1b mask;
    inRange(segmented, segmented(seed) - tol, segmented(seed) + tol, mask);

    // Find the connected component containing the seed
    vector<Point> pts = connected_components(mask, seed);

    // Number of pixels in the segment
    int n_of_pixels_in_segment = pts.size();

    Mat1b mask_segment(image.rows, image.cols, uchar(0));
    for (const auto& pt : pts)
    {
        mask_segment(pt) = uchar(255);
    }

    // Get palette
    map<Vec3b, int, lessVec3b> palette = getPalette(segmented, mask_segment);

    // Get most frequent color 
    Vec3b most_frequent_color;
    int freq = 0;

    for (const auto& pal : palette)
    {
        if (pal.second > freq)
        {
            most_frequent_color = pal.first;
            freq = pal.second;
        }
    }

    cout << "# points in segment: " << n_of_pixels_in_segment << endl;
    cout << "Most frequent color: " << most_frequent_color <<  " \t#: " << freq << endl;


    return 0;
}
Community
  • 1
  • 1
Miki
  • 40,887
  • 13
  • 123
  • 202
0

After creating the required mask as shown in previous answer or by any other means, you can create a contour around the mask image. This will give allow you to directly count the number of pixels within segment by using contourArea function.

You can segment out the selected area into a new submat and calculate histogram on it get most frequent values. If you are concerned with color values only and not the intensity values, you should also convert your image into HSV, LAB, or YCbCr color space as per requirement.

Gaurav Raj
  • 699
  • 6
  • 21
  • 1) If you are able to create the mask, you already know the number of pixels in the segment (or you can use `findNonZero` as last resort). There's no need to compute contours and retrieve contours area. 2) If you just put the selected area into a new submat, you'll compute the histogram also for some background pixels. You need a mask of the selected area. 3) Meanshift filtering has been done in BGR color space, so you probably need to remain in BGR color space – Miki Apr 06 '16 at 10:04