zedaoreia
zedaoreia

Reputation: 35

Detect the coin and from there measure the leaf in the image

Friends, I am trying to detect the coin first, since I already know its size is 1.011cm2. And then measure the leaves in the image. I am using findContours, but I am not always able to distinguish the currency first, I have also tried to use hougCircles but it is not working in my case. Would anyone have any ideas?

OpenCv 4.5.0 C++

Image

My code

//variables for segmentation image
cv::Mat imagem_original, imagem_gray, imagem_binaria, imagem_inRange, imagem_threshold, dst, src;
vector<Vec3f> circles;
cv::Scalar min_color = Scalar(50, 50, 50);
cv::Scalar max_color = Scalar(90, 120, 180);
imagem_original = load_image("IMG_1845.jpg");
//imshow("Imagem Original", imagem_original);

cv::cvtColor(imagem_original, imagem_gray, COLOR_BGR2GRAY);
//imshow("imagem_gray", imagem_gray);

//cv::inRange(imagem_gray, min_color, max_color, imagem_inRange);
cv::threshold(imagem_gray, imagem_threshold, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);
imshow(" Threshold", imagem_threshold);


// find outer-contours in the image these should be the circles!
cv::Mat conts = imagem_threshold.clone();
std::vector<std::vector<cv::Point> > contours;
std::vector<cv::Vec4i> hierarchy;

cv::findContours(conts, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, cv::Point(0, 0));


int total_IAF = 0;
cout << "\n\n";
cout << contours.size() << "\n\n";
for (int i = 0; i < contours.size(); i++) {

    int area = contourArea(contours[i]);
    if (area <= 10) {
        cv::drawContours(imagem_original, contours, i, Scalar(0, 0, 255));
    }
    else {
        cout << area << "\n";
        cv::drawContours(imagem_original, contours, i, Scalar(255, 0, 0));
    }
    if (area > 5000) {
        total_IAF += contourArea(contours[i]);
    }
}
imshow(" ORIGINAL ", imagem_original);

double iAF_cm2 = total_IAF / 4658;

cout << "\n\n TOTAL AREA IAF: " << total_IAF;
cout << "\n IAF em cm2: " << iAF_cm2 << " cm2\n\n";

Upvotes: 0

Views: 341

Answers (2)

HansHirse
HansHirse

Reputation: 18895

If your setup has constant white-ish/gray-ish background and green leaves, I'd use the HSV color space to detect all objects using the S channel (the green leaves and the golden part of the coin will have significantly more saturation than the background) and then distinguish between the coin and the leaves using the H channel (the green leaves will have hue values around 45). The remainder is to determine the image areas of all contours, and set the coin's image area as some kind of reference area to calculate the object areas w.r.t. the coin's object area of 1.011.

That's the saturation channel of the given image:

S channel

The saturation channel thresholded at 64:

S channel thresholded

That's the hue channel of the image:

H channel

Here's some code executing the above idea:

int main()
{
    // Read image
    cv::Mat img = cv::imread("Wcj1R.jpg", cv::IMREAD_COLOR);

    // Convert image to HSV color space, and split H, S, V channels
    cv::Mat img_hsv;
    cv::cvtColor(img, img_hsv, cv::COLOR_BGR2HSV);
    std::vector<cv::Mat> hsv;
    cv::split(img_hsv, hsv);

    // Binary threshold S channel at fixed threshold
    cv::Mat img_thr;
    cv::threshold(hsv[1], img_thr, 64, 255, cv::THRESH_BINARY);

    // Find most outer contours only
    std::vector<std::vector<cv::Point>> cnts;
    std::vector<cv::Vec4i> hier;
    cv::findContours(img_thr.clone(), cnts, hier, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE);

    // Iterate found contours
    std::vector<cv::Point> cnt_centers;
    std::vector<double> cnt_areas;
    double ref_area = -1;
    for (int i = 0; i < cnts.size(); i++)
    {
        // Current contour
        std::vector<cv::Point> cnt = cnts[i];

        // If contour is too small, discard
        if (cnt.size() < 100)
            continue;

        // Calculate and store center (just for visualization) and area of contour
        cv::Moments m = cv::moments(cnt);
        cnt_centers.push_back(cv::Point(m.m10 / m.m00 - 30, m.m01 / m.m00));
        cnt_areas.push_back(cv::contourArea(cnt));

        // Check H channel, whether the contour's image parts are mostly green
        cv::Mat mask = hsv[0].clone().setTo(cv::Scalar(0));
        cv::drawContours(mask, cnts, i, cv::Scalar(255), cv::FILLED);
        double h_mean = cv::mean(hsv[0], mask)[0];

        // If it's not mostly green, that's the coin, thus the reference area
        if (h_mean < 40 || h_mean > 50)
            ref_area = cv::contourArea(cnt);
    }

    // Iterate all contours again
    for (int i = 0; i < cnt_centers.size(); i++)
    {
        // Calculate actual object area
        double area = cnt_areas[i] / ref_area * 1.011;

        // Put area on image w.r.t. the contour's center
        cv::putText(img, std::to_string(area), cnt_centers[i], cv::FONT_HERSHEY_COMPLEX_SMALL, 1, cv::Scalar(255, 255, 255));
    }

    return 0;
}

And, that'd be the output:

Output

Upvotes: 3

HansLee
HansLee

Reputation: 43

Your code finds all contours in a image and shows them. So I'm confused about the meaning of "detect the coin first".

If you want to draw the contour of the coin first, sort contours vector by size. The coin is the smallest object so it would be the first element of the vector after sorting.(Of course, some unwanted contours should removed before sorting.)

Upvotes: 0

Related Questions