Ask Your Question

Revision history [back]

click to hide/show revision 1
initial version

As I said, I would not use hough but simpler blob detection or contours with stats. Below is my implementation, this is my result:

Console output

BISCUIT STATS:
    Area [px^2]: 12386
    Axis[px]: 125.292/126.988
    Axis Ratio: 0.986644
    Circularity: 0.870751
    Abs(eccentricity): 0.621218
Cracks Found! Biggest size: 100.896px (79.4533%)

Internal processing

Interal Blobs

Result

Result

The code (maybe I'll come back with some explains)

struct contourStats{
    double area, perimeter, axisMin, axisMax, axisAvg, eccentricity, axisRatio, circularity;
    Point2d center;
    RotatedRect rr;
    contourStats(){}
    contourStats(const vector<Point> &contour)
    {
        calculateStats(contour);
    }
    void calculateStats(const vector<Point> &contour)
    {
        Moments m = cv::moments(contour, true);
        area = m.m00;
        center = Point2f(-1, -1);
        if (area > 0){
            center.x = cvRound(m.m10 / m.m00);
            center.y = cvRound(m.m01 / m.m00);
        }
        eccentricity = DBL_MAX;
        if ((m.m20 + m.m02) > 0)
            eccentricity = (pow((m.m20 - m.m02), 2) - 4 * m.m11 * m.m11) / pow((m.m20 + m.m02), 2);
        // axis ratio:circles have ratio=1 lines have ratio->0
        rr = minAreaRect(contour);
        axisMin = min(rr.size.height, rr.size.width);
        axisMax = max(rr.size.height, rr.size.width);
        axisAvg = (axisMin+axisMax) / 2.0;
        axisRatio = axisMax > 0 ? axisMin / axisMax : 0;
        perimeter = arcLength(contour, false);
        circularity = perimeter > 0 ? 4 * CV_PI * area / pow(perimeter, 2) : 0;
    }
    void printStats(const string &title)
    {
        cout << endl << title << endl
            << "\tArea [px^2]: " << area << endl
            << "\tAxis[px]: " << axisMin << "/" << axisMax << endl
            << "\tAxis Ratio: " << axisRatio << endl
            << "\tCircularity: " << circularity << endl
            << "\tAbs(eccentricity): " << abs(eccentricity) << endl;
    }
};

int main(int argc, char** argv)
{
    Mat src, gray, edges, cracks,tmp;
    Mat biscuitMask;
    //src = imread("../img/biscotto_rotated.jpg");
    src = imread("../img/biscotto.jpg");
    cvtColor(src, gray, CV_BGR2GRAY);
    // GET ALL EDGES (CRACKS AND BISCUIT)
    Canny(gray, edges, 100, 200, 3);
    Morph(edges, edges, MORPH_CLOSE, MORPH_ELLIPSE, 5);
    // GET THE BISCUIT ONLY
    GaussianBlur(gray, gray, Size(7, 7), 0);
    threshold(gray, biscuitMask, 0, 255, THRESH_OTSU);
    // VALIDATE THE BISCUIT
    vector<vector<Point> > contours;
    biscuitMask.copyTo(tmp);
    findContours(tmp, contours, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE);
    if ((contours.size() > 1) || (contours[0].size() < 6)) {
        cerr << "ERROR: invalid mask for the biscuit!";
        return 1;
    }
    vector<Point> biscuitContour(contours[0]);
    contourStats theBiscuit(biscuitContour);
    theBiscuit.printStats("BISCUIT STATS:");
    if (theBiscuit.circularity < 0.85) {
        cout << "WARNING: invalid biscuit circularity!";
    }
    drawMarker(src, theBiscuit.center, CL_BLU, MARKER_CROSS, theBiscuit.axisAvg / 8);
    circle(src, theBiscuit.center, theBiscuit.axisAvg / 2, CL_BLU, 2);
    //polylines(src, biscuitContour, true, CL_GREEN); 

    // GET ONLY THE CRACKS INSIDE THE BISCUIT
    Morph(biscuitMask, biscuitMask, MORPH_ERODE,MORPH_ELLIPSE, 5);
    cracks = (edges & biscuitMask);
    imshow("cracks", cracks);

    findContours(cracks, contours, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE);

    double minLenght = theBiscuit.axisAvg / 2;
    double maxLenght = 0;
    int biggest = -1;
    vector<contourStats> crackStats(contours.size());
    for (size_t i = 0; i < contours.size(); i++)
    {
        drawContours(src, contours, i, CL_GREEN, 1); //all contours in GREEN
        crackStats[i].calculateStats(contours[i]);   //analize potential cracks
        if (crackStats[i].axisMax < minLenght)       //ignore short contours
            continue;
        drawContours(src, contours, i, CL_RED, 1);   //longer contours are cracks
        if (crackStats[i].axisMax > maxLenght)       // get longest crack
            biggest = i;
    }
    // DRAW AROUND BIGGER CRACKS
    if (biggest > 0)  {
        cout << "Cracks Found! Biggest size: " << crackStats[biggest].axisMax <<
            "px (" << 100 * crackStats[biggest].axisMax / theBiscuit.axisMax << "%)" 
            << endl;
        RotatedRect rr = crackStats[biggest].rr;
        Point2f rect_points[4];
        rr.points(rect_points);
        //draw enclosing rectangle
        for (int j = 0; j < 4; j++) 
            line(src, rect_points[j], rect_points[(j + 1) % 4], CL_RED, 1, 8);
        //draw major axis
        Point2f pt0 = (rect_points[0] + rect_points[3]) / 2.0;
        Point2f pt1 = (rect_points[1] + rect_points[2]) / 2.0;
        Point2f pt2 = (rect_points[0] + rect_points[1]) / 2.0;
        Point2f pt3 = (rect_points[2] + rect_points[3]) / 2.0;
        double axis1 = norm(pt0 - pt1);
        double axis2 = norm(pt2 - pt3);
        if (axis1>axis2)
            line(src, pt0, pt1, CL_BLACK, 2);
        else
            line(src, pt2, pt3, CL_BLACK, 2);
    }
    imshow("Src", src);
    waitKey(0);
    return 0;
}

As I said, I would not use hough but simpler blob detection or contours with stats. Below is my implementation, this is my result:

Console output

BISCUIT STATS:
    Area [px^2]: 12386
    Axis[px]: 125.292/126.988
    Axis Ratio: 0.986644
    Circularity: 0.870751
    Abs(eccentricity): 0.621218
Cracks Found! Biggest size: 100.896px (79.4533%)

Internal processing

Interal Blobs

Result

Result

The code (maybe I'll come back with some explains)explains) EDIT: Added few missing code

#include <opencv2\opencv.hpp>
using namespace std;
using namespace cv;
#define CL_GREEN  Scalar(0  , 255, 0  )
#define CL_RED    Scalar(0  , 0  , 255)
#define CL_BLU    Scalar(255, 0  , 0  )
#define CL_BLACK  Scalar(0  , 0  , 0  )
void Morph(const cv::Mat &src, cv::Mat &dst, int operation = cv::MORPH_OPEN, 
           int kernel_type = cv::MORPH_RECT, int size = 1);
struct contourStats{
    double area, perimeter, axisMin, axisMax, axisAvg, eccentricity, axisRatio, circularity;
    Point2d center;
    RotatedRect rr;
    contourStats(){}
    contourStats(const vector<Point> &contour)
    {
        calculateStats(contour);
    }
    void calculateStats(const vector<Point> &contour)
    {
        Moments m = cv::moments(contour, true);
        area = m.m00;
        center = Point2f(-1, -1);
        if (area > 0){
            center.x = cvRound(m.m10 / m.m00);
            center.y = cvRound(m.m01 / m.m00);
        }
        eccentricity = DBL_MAX;
        if ((m.m20 + m.m02) > 0)
            eccentricity = (pow((m.m20 - m.m02), 2) - 4 * m.m11 * m.m11) / pow((m.m20 + m.m02), 2);
        // axis ratio:circles have ratio=1 lines have ratio->0
        rr = minAreaRect(contour);
        axisMin = min(rr.size.height, rr.size.width);
        axisMax = max(rr.size.height, rr.size.width);
        axisAvg = (axisMin+axisMax) / 2.0;
        axisRatio = axisMax > 0 ? axisMin / axisMax : 0;
        perimeter = arcLength(contour, false);
        circularity = perimeter > 0 ? 4 * CV_PI * area / pow(perimeter, 2) : 0;
    }
    void printStats(const string &title)
    {
        cout << endl << title << endl
            << "\tArea [px^2]: " << area << endl
            << "\tAxis[px]: " << axisMin << "/" << axisMax << endl
            << "\tAxis Ratio: " << axisRatio << endl
            << "\tCircularity: " << circularity << endl
            << "\tAbs(eccentricity): " << abs(eccentricity) << endl;
    }
};

int main(int argc, char** argv)
{
    Mat src, gray, edges, cracks,tmp;
    Mat biscuitMask;
    //src = imread("../img/biscotto_rotated.jpg");
    src = imread("../img/biscotto.jpg");
    cvtColor(src, gray, CV_BGR2GRAY);
    // GET ALL EDGES (CRACKS AND BISCUIT)
    Canny(gray, edges, 100, 200, 3);
    Morph(edges, edges, MORPH_CLOSE, MORPH_ELLIPSE, 5);
    // GET THE BISCUIT ONLY
    GaussianBlur(gray, gray, Size(7, 7), 0);
    threshold(gray, biscuitMask, 0, 255, THRESH_OTSU);
    // VALIDATE THE BISCUIT
    vector<vector<Point> > contours;
    biscuitMask.copyTo(tmp);
    findContours(tmp, contours, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE);
    if ((contours.size() > 1) || (contours[0].size() < 6)) {
        cerr << "ERROR: invalid mask for the biscuit!";
        return 1;
    }
    vector<Point> biscuitContour(contours[0]);
    contourStats theBiscuit(biscuitContour);
    theBiscuit.printStats("BISCUIT STATS:");
    if (theBiscuit.circularity < 0.85) {
        cout << "WARNING: invalid biscuit circularity!";
    }
    drawMarker(src, theBiscuit.center, CL_BLU, MARKER_CROSS, theBiscuit.axisAvg / 8);
    circle(src, theBiscuit.center, theBiscuit.axisAvg / 2, CL_BLU, 2);
    //polylines(src, biscuitContour, true, CL_GREEN); 

    // GET ONLY THE CRACKS INSIDE THE BISCUIT
    Morph(biscuitMask, biscuitMask, MORPH_ERODE,MORPH_ELLIPSE, 5);
    cracks = (edges & biscuitMask);
    imshow("cracks", cracks);

    findContours(cracks, contours, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE);

    double minLenght = theBiscuit.axisAvg / 2;
    double maxLenght = 0;
    int biggest = -1;
    vector<contourStats> crackStats(contours.size());
    for (size_t i = 0; i < contours.size(); i++)
    {
        drawContours(src, contours, i, CL_GREEN, 1); //all contours in GREEN
        crackStats[i].calculateStats(contours[i]);   //analize potential cracks
        if (crackStats[i].axisMax < minLenght)       //ignore short contours
            continue;
        drawContours(src, contours, i, CL_RED, 1);   //longer contours are cracks
        if (crackStats[i].axisMax > maxLenght)       // get longest crack
            biggest = i;
    }
    // DRAW AROUND BIGGER CRACKS
    if (biggest > 0)  {
        cout << "Cracks Found! Biggest size: " << crackStats[biggest].axisMax <<
            "px (" << 100 * crackStats[biggest].axisMax / theBiscuit.axisMax << "%)" 
            << endl;
        RotatedRect rr = crackStats[biggest].rr;
        Point2f rect_points[4];
        rr.points(rect_points);
        //draw enclosing rectangle
        for (int j = 0; j < 4; j++) 
            line(src, rect_points[j], rect_points[(j + 1) % 4], CL_RED, 1, 8);
        //draw major axis
        Point2f pt0 = (rect_points[0] + rect_points[3]) / 2.0;
        Point2f pt1 = (rect_points[1] + rect_points[2]) / 2.0;
        Point2f pt2 = (rect_points[0] + rect_points[1]) / 2.0;
        Point2f pt3 = (rect_points[2] + rect_points[3]) / 2.0;
        double axis1 = norm(pt0 - pt1);
        double axis2 = norm(pt2 - pt3);
        if (axis1>axis2)
            line(src, pt0, pt1, CL_BLACK, 2);
        else
            line(src, pt2, pt3, CL_BLACK, 2);
    }
    imshow("Src", src);
    waitKey(0);
    return 0;
}
void Morph(const cv::Mat &src, cv::Mat &dst, int operation,int kernel_type, int size)
{
    cv::Point anchor = cv::Point(size, size);
    cv::Mat element = getStructuringElement(kernel_type, cv::Size(2 * size + 1, 2 * size + 1), anchor);
    morphologyEx(src, dst, operation, element, anchor);
}