Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cropping panorama image in OpenCV

I'm trying to find a simple algorithm to crop (remove the black areas) of a panorama image created with the openCV Stitcher module.

My idea is to calculate the most inner black points in the image which will define the cropping area, as shown in the next image:

enter image description here

Expected cropped result:

enter image description here

I've tried the next two approaches, but they don't crop the image as expected:

First Approach:

void testCropA(cv::Mat& image)
{
    cv::Mat gray;
    cvtColor(image, gray, CV_BGR2GRAY);

    Size size = gray.size();
    int type = gray.type();
    int left = 0, top = 0, right = size.width, bottom = size.height;

    cv::Mat row_zeros = Mat::zeros(1, right, type);
    cv::Mat col_zeros = Mat::zeros(bottom, 1, type);

    while (countNonZero(gray.row(top) != row_zeros) == 0) { top++; }

    while (countNonZero(gray.col(left) != col_zeros) == 0) { left++; }

    while (countNonZero(gray.row(bottom-1) != row_zeros) == 0) { bottom--; }

    while (countNonZero(gray.col(right-1) != col_zeros) == 0) { right--;  }

    cv::Rect cropRect(left, top, right - left, bottom - top);
    image = image(cropRect);
}

Second Approach:

void testCropB(cv::Mat& image)
{
    cv::Mat gray;
    cvtColor(image, gray, CV_BGR2GRAY);

    int minCol = gray.cols;
    int minRow = gray.rows;
    int maxCol = 0;
    int maxRow = 0;

    for (int i = 0; i < gray.rows - 3; i++)
    {
        for (int j = 0; j < gray.cols; j++)
        {
            if (gray.at<char>(i, j) != 0)
            {
                if (i < minRow) {minRow = i;}
                if (j < minCol) {minCol = j;}
                if (i > maxRow) {maxRow = i;}
                if (j > maxCol) {maxCol = j;}
            }
        }
    }

    cv::Rect cropRect = Rect(minCol, minRow, maxCol - minCol, maxRow - minRow);
    image = image(cropRect);
}
like image 418
PerracoLabs Avatar asked May 24 '14 17:05

PerracoLabs


People also ask

How do I crop an image in OpenCV Python?

There is no specific function for cropping using OpenCV, NumPy array slicing is what does the job. Every image that is read in, gets stored in a 2D array (for each color channel). Simply specify the height and width (in pixels) of the area to be cropped. And it's done!

How do you crop an image in Python?

crop() method is used to crop a rectangular portion of any image. Parameters: box – a 4-tuple defining the left, upper, right, and lower pixel coordinate. Return type: Image (Returns a rectangular region as (left, upper, right, lower)-tuple).


1 Answers

This is my current solution. Hope it helps to others:

bool checkInteriorExterior(const cv::Mat &mask, const cv::Rect &croppingMask,
                                 int &top, int &bottom, int &left, int &right)
{
    // Return true if the rectangle is fine as it is
    bool result = true;

    cv::Mat sub = mask(croppingMask);
    int x = 0;
    int y = 0;

    // Count how many exterior pixels are, and choose that side for
    // reduction where mose exterior pixels occurred (that's the heuristic)

    int top_row = 0;
    int bottom_row = 0;
    int left_column = 0;
    int right_column = 0;

    for (y = 0, x = 0; x < sub.cols; ++x)
    {
        // If there is an exterior part in the interior we have
        // to move the top side of the rect a bit to the bottom
        if (sub.at<char>(y, x) == 0)
        {
            result = false;
            ++top_row;
        }
    }

    for (y = (sub.rows - 1), x = 0; x < sub.cols; ++x)
    {
        // If there is an exterior part in the interior we have
        // to move the bottom side of the rect a bit to the top
        if (sub.at<char>(y, x) == 0)
        {
            result = false;
            ++bottom_row;
        }
    }

    for (y = 0, x = 0; y < sub.rows; ++y)
    {
        // If there is an exterior part in the interior
        if (sub.at<char>(y, x) == 0)
        {
            result = false;
            ++left_column;
        }
    }

    for (x = (sub.cols - 1), y = 0; y < sub.rows; ++y)
    {
        // If there is an exterior part in the interior
        if (sub.at<char>(y, x) == 0)
        {
            result = false;
            ++right_column;
        }
    }

    // The idea is to set `top = 1` if it's better to reduce
    // the rect at the top than anywhere else.
    if (top_row > bottom_row)
    {
        if (top_row > left_column)
        {
            if (top_row > right_column)
            {
                top = 1;
            }
        }
    }
    else if (bottom_row > left_column)
    {
        if (bottom_row > right_column)
        {
            bottom = 1;
        }
    }

    if (left_column >= right_column)
    {
        if (left_column >= bottom_row)
        {
            if (left_column >= top_row)
            {
                left = 1;
            }
        }
    }
    else if (right_column >= top_row)
    {
        if (right_column >= bottom_row)
        {
            right = 1;
        }
    }

    return result;
}

bool compareX(cv::Point a, cv::Point b)
{
    return a.x < b.x;
}

bool compareY(cv::Point a, cv::Point b)
{
    return a.y < b.y;
}

void crop(cv::Mat &source)
{
    cv::Mat gray;
    source.convertTo(source, CV_8U);
    cvtColor(source, gray, cv::COLOR_RGB2GRAY);

    // Extract all the black background (and some interior parts maybe)

    cv::Mat mask = gray > 0;

    // now extract the outer contour
    std::vector<std::vector<cv::Point> > contours;
    std::vector<cv::Vec4i> hierarchy;

    cv::findContours(mask, contours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE, cv::Point(0, 0));
    cv::Mat contourImage = cv::Mat::zeros(source.size(), CV_8UC3);;

    // Find contour with max elements

    int maxSize = 0;
    int id = 0;

    for (int i = 0; i < contours.size(); ++i)
    {
        if (contours.at((unsigned long)i).size() > maxSize)
        {
            maxSize = (int)contours.at((unsigned long)i).size();
            id = i;
        }
    }

    // Draw filled contour to obtain a mask with interior parts

    cv::Mat contourMask = cv::Mat::zeros(source.size(), CV_8UC1);
    drawContours(contourMask, contours, id, cv::Scalar(255), -1, 8, hierarchy, 0, cv::Point());

    // Sort contour in x/y directions to easily find min/max and next

    std::vector<cv::Point> cSortedX = contours.at((unsigned long)id);
    std::sort(cSortedX.begin(), cSortedX.end(), compareX);
    std::vector<cv::Point> cSortedY = contours.at((unsigned long)id);
    std::sort(cSortedY.begin(), cSortedY.end(), compareY);

    int minXId = 0;
    int maxXId = (int)(cSortedX.size() - 1);
    int minYId = 0;
    int maxYId = (int)(cSortedY.size() - 1);

    cv::Rect croppingMask;

    while ((minXId < maxXId) && (minYId < maxYId))
    {
        cv::Point min(cSortedX[minXId].x, cSortedY[minYId].y);
        cv::Point max(cSortedX[maxXId].x, cSortedY[maxYId].y);
        croppingMask = cv::Rect(min.x, min.y, max.x - min.x, max.y - min.y);

        // Out-codes: if one of them is set, the rectangle size has to be reduced at that border

        int ocTop = 0;
        int ocBottom = 0;
        int ocLeft = 0;
        int ocRight = 0;

        bool finished = checkInteriorExterior(contourMask, croppingMask, ocTop, ocBottom, ocLeft, ocRight);

        if (finished == true)
        {
            break;
        }

        // Reduce rectangle at border if necessary

        if (ocLeft)
        { ++minXId; }
        if (ocRight)
        { --maxXId; }
        if (ocTop)
        { ++minYId; }
        if (ocBottom)
        { --maxYId; }
    }

    // Crop image with created mask

    source = source(croppingMask);
}
like image 178
PerracoLabs Avatar answered Nov 15 '22 09:11

PerracoLabs