简体   繁体   中英

How to count pixels in color segment in OpenCV

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.
筑波

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

The segmented image is:
分段筑波
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?

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:

在此处输入图片说明

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:

在此处输入图片说明

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;
}

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM