6

How can I crop a non rectangular region from image?

Imagine I have four points and I want to crop it, this shape wouldn't be a triangle somehow!

For example I have the following image :

enter image description here

and I want to crop this from image :

enter image description here

How can I do this? regards..

Jeru Luke
  • 20,118
  • 13
  • 80
  • 87
  • How do you want to decide on the region to crop? Do you want to just always blindly make that crop you demonstrated? Or do you want a diamond shape all the time? In general you can use OpenCV's bitwise_and function to mask off part of the image, but then generating the mask is what requires more details. – Tim Sweet Jan 17 '17 at 05:30
  • the region would be none constant I mean it would be in any shape but always it has 4 corners –  Jan 17 '17 at 05:33

2 Answers2

9

The procedure for cropping an arbitrary quadrilateral (or any polygon for that matter) part of an image is summed us as:

  • Generate a "mask". The mask is black where you want to keep the image, and white where you don't want to keep it
  • Compute the "bitwise_and" between your input image and the mask

So, lets assume you have an image. Throughout this I'll use an image size of 30x30 for simplicity, you can change this to suit your use case.

cv::Mat source_image = cv::imread("filename.txt");

And you have four points you want to use as the corners:

cv::Point corners[1][4];
corners[0][0] = Point( 10, 10 );
corners[0][1] = Point( 20, 20 );
corners[0][2] = Point( 30, 10 );
corners[0][3] = Point( 20, 10 );
const Point* corner_list[1] = { corners[0] };

You can use the function cv::fillPoly to draw this shape on a mask:

int num_points = 4;
int num_polygons = 1;
int line_type = 8;
cv::Mat mask(30,30,CV_8UC3, cv::Scalar(0,0,0));
cv::fillPoly( mask, corner_list, &num_points, num_polygons, cv::Scalar( 255, 255, 255 ),  line_type);

Then simply compute the bitwise_and of the image and mask:

cv::Mat result;
cv::bitwise_and(source_image, mask, result);

result now has the cropped image in it. If you want the edges to end up white instead of black you could instead do:

cv::Mat result_white(30,30,CV_8UC3, cv::Scalar(255,255,255));
cv::bitwise_and(source_image, mask, result_white, mask);

In this case we use bitwise_and's mask parameter to only do the bitwise_and inside the mask. See this tutorial for more information and links to all the functions I mentioned.

Tim Sweet
  • 636
  • 5
  • 15
  • I got "no matching function 'fillPoly' error :( please help me: https://stackoverflow.com/q/45441744/3162918 – bendaf Aug 01 '17 at 15:21
  • 1
    Thanks for pointing out that error: I've updated my answer. I needed to pass num_points as a pointer. – Tim Sweet Aug 01 '17 at 16:42
  • and what is your "white" variable in the second bitwise_and case? – bendaf Aug 02 '17 at 07:19
  • 1
    it should be cv::bitwise_and(source_image, mask, result_white, mask); – bendaf Aug 02 '17 at 13:46
  • @bendaf I hadn't originally declared the `white` variable, I updated the answer to declare it (thanks for pointing that out). I don't think your recommended solution works, though: that will make the edges black. The trick here is to AND the image with an already white image just within the mask. – Tim Sweet Aug 02 '17 at 18:14
  • 1
    I did try that out and it worked. I think the content of the first two input matters only in the place of the mask, and the other places will stay white in the result_white image. – bendaf Aug 03 '17 at 06:32
  • 1
    @bendaf ah okay, you're right, thanks for pointing that out. I think when I wrote the example code I had first done it the way I described, then did it your way, but my answer ended up being a hybrid of the two. I have fixed the answer per your recommendation. – Tim Sweet Aug 03 '17 at 15:15
  • `cv::bitwise_and` with `result_white` gives an opencv exception – Iter Ator Mar 29 '18 at 12:21
  • 1
    @IterAtor you should post that as a separate question, including the code you have written. – Tim Sweet Mar 30 '18 at 13:07
1

You may use cv::Mat::copyTo() like this:

cv::Mat img = cv::imread("image.jpeg");
// note mask may be single channel, even if img is multichannel
cv::Mat mask = cv::Mat::zeros(img.rows, img.cols, CV_8UC1);
// fill mask with nonzero values, e.g. as Tim suggests
// cv::fillPoly(...)
cv::Mat result(img.size(), img.type(), cv::Scalar(255, 255, 255));
img.copyTo(result, mask);
ivan_onys
  • 2,282
  • 17
  • 21