Extract contour area from image - python

I got some questions about segmentation of contoured image. For example i got cable image and i can contour this image with threshold and drawcontour function with code in down below. Contoured image, threshold image. My questions are i want to extract this cable and read rgb code. Any advice could be great! Thanks.
gray_image = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
ret, thresh_img = cv2.threshold(gray_image, trs, 255, cv2.THRESH_BINARY)
im2, contours, hierarchy = cv2.findContours(thresh_img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(im2, contours, -1, red, cnt)
cv2.imshow(winName, im2)

You can use cv2.contourArea(contours) more information here and here

You can get the area inside some polygon "contour" using shoelace formula
The Idea is to calculate the are incrementally by summing/subtracting the area between polygon sides and on the axis , after one complete loop though the polygon contour you the result of summation/subtraction will be the area inside the polygon
j = numPoints-1
for (uint_fast8_t i=0; i<numPoints; i++)
{
area = area + (contour[j][0]+contour[i][0]) * (contour[j][1]-contour[i][1]);
area1 = area1 + (contour[j][0]*contour[i][1]) - (contour[j][1]*contour[i][0]); //different form for the formula
j = i; //j is previous vertex to i
}
area= area/2;
area1= area1/2; //sign of area depend on direction of rotation
https://en.wikipedia.org/wiki/Shoelace_formula
https://www.mathopenref.com/coordpolygonarea.html
https://www.mathopenref.com/coordpolygonarea2.html
for python
https://www.101computing.net/the-shoelace-algorithm/

Related

How to locate contours on a ringed shooting target

Firstly, I am new to python and opencv so my understanding is limited, however I am trying to learn more as best I can.
I am currently struggling to locate contours(bullet holes) on a simple ringed target so that I can eventually score each hole. I have managed to solve a similar problem on a different image and I am wondering what I can do to get the same method to work on the new one.
Successful attempt at scoring a target
This is the target that my problem concerns
When I use these HSV Values I am presented with only the Bullet Holes. My limited knowledge tells me that perhaps these HSV values are useful in thresholding(?) but I can not seem to find the execution.
The method used to locate the contours in the example target is shown below:
imgREDUCED = cv2.inRange(image, (60, 60, 60), (150, 150, 150))
kernel = np.ones((10,10),np.uint8)
opening = cv2.morphologyEx(imgREDUCED, cv2.MORPH_OPEN, kernel)
thresh = cv2.threshold(opening, 60, 255, cv2.THRESH_BINARY)[1]
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
for c in cnts:
if cv2.contourArea(c) > 1:
cv2.drawContours(image, [c], -1, (0, 255, 0), 2)
What steps can I take to locate the contours on this new target image?
All help is appreciated and I will try my best to answer any questions
By finding the bullseye and the outer ring we can calculate the score of each hole based on its distance from the center. Here are the steps I took to try and solve this.
First, I converted to HSV and took a look at the different channels:
Hue
Value
From the hue channel we can clearly see the holes in the target so it's a good candidate for thresholding for those. On the value channel we can clearly see the rings and the center so we'll use that channel to detect those.
Hue Mask (0, 30)
Value Mask (0, 155)
We can use findContours to outline the white parts of the mask. From that outline we can get the center of the contour and the area of the contour. Using this on the hue mask we get the center of each hole and using this on the value mask we can get the biggest ring by looking for the contour with the largest area. With the area of the biggest ring, we can estimate the radius via the circle's area formula.
To find the bullseye I thresholded the value mask again, but using the (215, 255) to search for high values. This perfectly captures just the center, but you might not always get that lucky with your pictures. Using the findContours function again I get the center are radius of the bullseye.
Now I can score each of the holes. I get the distance from the hole to the center and figure out where on the scoresheet it should land on.
Marked the outer ring, the center of each hole, the score of each hole, and the bullseye:
Here's the code:
import cv2
import math
import numpy as np
# get center of contour
def centroid(contour):
M = cv2.moments(contour);
cx = int(round(M['m10']/M['m00']));
cy = int(round(M['m01']/M['m00']));
center = (cx, cy);
return center;
# load image
img = cv2.imread("target.png");
img = img[:,:-1,:]; # there's a bit of wall or something on the right
# hsv
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV);
h,s,v = cv2.split(hsv);
# find the outer ring
v_mask = cv2.inRange(v, 0, 155);
# contours
_, contours, _ = cv2.findContours(v_mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE);
# find the biggest contour
biggest_cntr = None;
biggest_area = 0;
marked = img.copy();
for contour in contours:
area = cv2.contourArea(contour);
if area > biggest_area:
biggest_area = area;
biggest_cntr = contour;
cv2.drawContours(marked, [biggest_cntr], -1, (0, 255, 0), 3);
# find biggest radius
big_radius = math.sqrt(biggest_area / math.pi);
# find center
center_v_mask = cv2.inRange(v, 215, 255);
_, contours, _ = cv2.findContours(center_v_mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE);
center = centroid(contours[0]);
# use this to calculate the middle radius
area = cv2.contourArea(contours[0]);
little_radius = math.sqrt(area / math.pi);
# draw center
marked = cv2.circle(marked, center, 2, (155,155,0), -1);
# mask holes
h_mask = cv2.inRange(h, 0, 30);
h_mask = cv2.medianBlur(h_mask, 11);
# draw contour centers
_, contours, _ = cv2.findContours(h_mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE);
holes = [];
for contour in contours:
c = centroid(contour);
holes.append(c);
marked = cv2.circle(marked, c, 2, (0,0,155), -1);
# calculate approximate size of each ring
# (with foreknowledge that there are 9 + bullseye)
remaining_radius = big_radius - little_radius;
slices = remaining_radius / 9;
# calculate scores
scores = [];
for hole in holes:
# get distance from center
dx = hole[0] - center[0];
dy = hole[1] - center[1];
dist = math.sqrt(dx*dx + dy*dy);
# check score
dist -= little_radius;
if dist < 0:
scores.append(10);
else:
scores.append(9 - int(dist / slices));
# draw the scores
font = cv2.FONT_HERSHEY_SIMPLEX ;
for a in range(len(holes)):
tup = (holes[a][0], holes[a][1]);
marked = cv2.putText(marked, str(scores[a]), tup, font, 1, (0,0,155), 2, cv2.LINE_AA);
# show
cv2.imshow("marked", marked);
cv2.waitKey(0);

How to get the location of all text present in an image using OpenCV?

I have this image that contains text (numbers and alphabets) in it. I want to get the location of all the text and numbers present in this image. Also I want to extract all the text as well.
How do I get the coordinates as well as the all the text (numbers and alphabets) in my image? For eg 10B, 44, 16, 38, 22B etc
Here's a potential approach using morphological operations to filter out non-text contours. The idea is:
Obtain binary image. Load image, grayscale, then
Otsu's threshold
Remove horizontal and vertical lines. Create horizontal and vertical kernels using cv2.getStructuringElement() then remove lines with cv2.drawContours()
Remove diagonal lines, circle objects, and curved contours. Filter using contour area cv2.contourArea()
and contour approximation cv2.approxPolyDP()
to isolate non-text contours
Extract text ROIs and OCR. Find contours and filter for ROIs then OCR using
Pytesseract.
Removed horizontal lines highlighted in green
Removed vertical lines
Removed assorted non-text contours (diagonal lines, circular objects, and curves)
Detected text regions
import cv2
import numpy as np
import pytesseract
pytesseract.pytesseract.tesseract_cmd = r"C:\Program Files\Tesseract-OCR\tesseract.exe"
# Load image, grayscale, Otsu's threshold
image = cv2.imread('1.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
clean = thresh.copy()
# Remove horizontal lines
horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (15,1))
detect_horizontal = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, horizontal_kernel, iterations=2)
cnts = cv2.findContours(detect_horizontal, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
cv2.drawContours(clean, [c], -1, 0, 3)
# Remove vertical lines
vertical_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1,30))
detect_vertical = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, vertical_kernel, iterations=2)
cnts = cv2.findContours(detect_vertical, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
cv2.drawContours(clean, [c], -1, 0, 3)
cnts = cv2.findContours(clean, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
# Remove diagonal lines
area = cv2.contourArea(c)
if area < 100:
cv2.drawContours(clean, [c], -1, 0, 3)
# Remove circle objects
elif area > 1000:
cv2.drawContours(clean, [c], -1, 0, -1)
# Remove curve stuff
peri = cv2.arcLength(c, True)
approx = cv2.approxPolyDP(c, 0.02 * peri, True)
x,y,w,h = cv2.boundingRect(c)
if len(approx) == 4:
cv2.rectangle(clean, (x, y), (x + w, y + h), 0, -1)
open_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (2,2))
opening = cv2.morphologyEx(clean, cv2.MORPH_OPEN, open_kernel, iterations=2)
close_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3,2))
close = cv2.morphologyEx(opening, cv2.MORPH_CLOSE, close_kernel, iterations=4)
cnts = cv2.findContours(close, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
x,y,w,h = cv2.boundingRect(c)
area = cv2.contourArea(c)
if area > 500:
ROI = image[y:y+h, x:x+w]
ROI = cv2.GaussianBlur(ROI, (3,3), 0)
data = pytesseract.image_to_string(ROI, lang='eng',config='--psm 6')
if data.isalnum():
cv2.rectangle(image, (x, y), (x + w, y + h), (36,255,12), 2)
print(data)
cv2.imwrite('image.png', image)
cv2.imwrite('clean.png', clean)
cv2.imwrite('close.png', close)
cv2.imwrite('opening.png', opening)
cv2.waitKey()
Alright, here's another possible solution. I know you work with Python - I work with C++. I'll give you some ideas and hopefully, if you desire so, you will be able to implement this answer.
The main idea is to not use pre-processing at all (at least not at the initial stage) and instead focus on each target character, get some properties, and filter every blob according to these properties.
I'm trying to not use pre-processing because: 1) Filters and morphological stages could degrade the quality of the blobs and 2) your target blobs appear to exhibit some characteristics that we could exploit, mainly: aspect ratio and area.
Check it out, the numbers and letters all appear to be taller than wider… furthermore, they appear to vary within a certain area value. For example, you want to discard objects "too wide" or "too big".
The idea is that I'll filter everything that does not fall within pre-calculated values. I examined the characters (numbers and letters) and came with minimum, maximum area values and a minimum aspect ratio (here, the ratio between height and width).
Let's work on the algorithm. Start by reading the image and resizing it to half the dimensions. Your image is way too big. Convert to grayscale and get a binary image via otsu, here's in pseudo-code:
//Read input:
inputImage = imread( "diagram.png" );
//Resize Image;
resizeScale = 0.5;
inputResized = imresize( inputImage, resizeScale );
//Convert to grayscale;
inputGray = rgb2gray( inputResized );
//Get binary image via otsu:
binaryImage = imbinarize( inputGray, "Otsu" );
Cool. We will work with this image. You need to examine every white blob, and apply a "properties filter". I’m using connected components with stats to loop trough each blob and get its area and aspect ratio, in C++ this is done as follows:
//Prepare the output matrices:
cv::Mat outputLabels, stats, centroids;
int connectivity = 8;
//Run the binary image through connected components:
int numberofComponents = cv::connectedComponentsWithStats( binaryImage, outputLabels, stats, centroids, connectivity );
//Prepare a vector of colors – color the filtered blobs in black
std::vector<cv::Vec3b> colors(numberofComponents+1);
colors[0] = cv::Vec3b( 0, 0, 0 ); // Element 0 is the background, which remains black.
//loop through the detected blobs:
for( int i = 1; i <= numberofComponents; i++ ) {
//get area:
auto blobArea = stats.at<int>(i, cv::CC_STAT_AREA);
//get height, width and compute aspect ratio:
auto blobWidth = stats.at<int>(i, cv::CC_STAT_WIDTH);
auto blobHeight = stats.at<int>(i, cv::CC_STAT_HEIGHT);
float blobAspectRatio = (float)blobHeight/(float)blobWidth;
//Filter your blobs…
};
Now, we will apply the properties filter. This is just a comparison with the pre-calculated thresholds. I used the following values:
Minimum Area: 40 Maximum Area:400
MinimumAspectRatio: 1
Inside your for loop, compare the current blob properties with these values. If the tests are positive, you "paint" the blob black. Continuing inside the for loop:
//Filter your blobs…
//Test the current properties against the thresholds:
bool areaTest = (blobArea > maxArea)||(blobArea < minArea);
bool aspectRatioTest = !(blobAspectRatio > minAspectRatio); //notice we are looking for TALL elements!
//Paint the blob black:
if( areaTest || aspectRatioTest ){
//filtered blobs are colored in black:
colors[i] = cv::Vec3b( 0, 0, 0 );
}else{
//unfiltered blobs are colored in white:
colors[i] = cv::Vec3b( 255, 255, 255 );
}
After the loop, construct the filtered image:
cv::Mat filteredMat = cv::Mat::zeros( binaryImage.size(), CV_8UC3 );
for( int y = 0; y < filteredMat.rows; y++ ){
for( int x = 0; x < filteredMat.cols; x++ )
{
int label = outputLabels.at<int>(y, x);
filteredMat.at<cv::Vec3b>(y, x) = colors[label];
}
}
And… that's pretty much it. You filtered all the elements that are not similar to what you are looking for. Running the algorithm you get this result:
I've additionally found the Bounding Boxes of the blobs to better visualize the results:
As you see, some elements are miss-detected. You can refine the "properties filter" to better identify the characters you are looking for. A deeper solution, involving a little bit of machine learning, requires the construction of an "ideal feature vector", extracting features from the blobs, and comparing both vectors via a similarity measure. You can also apply some post-processing to improve the results...
One method is to use sliding window (It is expensive).
Determine the size of the characters in the image (all characters are of same size as seen in the image) and set the size of the window. Try tesseract for the detection (The input image requires pre processing). If a window detects characters consecutively, then store the coordinates of the window. Merge the coordinates and get the region on the characters.

How to find area of a curve in python where coordinates are not known?

So I am currently working on a project where I need to find the area in terms of cm of a particular curve.The problem is the curve has more than one colors each representing a different values
Something Like This
Can someone help me do it? There are more than one such curves in the image. How to simultaneously calculate all of them in Python.
You can use the following code to print area in terms of pixels. To get the area in cm^2, you need to know the relationship between pixels and actual length.
The following code prints the area of largest blob in the image.
To get the area of all the blobs in the image, just replace [c] with contours
import cv2
import numpy as np
img = cv2.imread("image.png", 0)
blank = np.zeros_like(img)
ret, thresh = cv2.threshold(img, 0 ,255, cv2.THRESH_BINARY)
im2, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
if( len(contours) != 0 ):
c = max(contours, key = cv2.contourArea)
cv2.drawContours(blank, [c], -1, 255, -1)
print cv2.countNonZero(blank)
cv2.imshow("img", blank)
cv2.waitKey(0)
cv2.destroyAllWindows()
Edit:
import cv2
import numpy as np
img = cv2.imread("images.png", 0)
blank = np.zeros_like(img)
ret, thresh = cv2.threshold(img, 0 ,255, cv2.THRESH_BINARY)
im2, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
for i in range(len(contours)):
cv2.drawContours(blank, contours[i], -1, 255, -1)
print "area of contour " + str(i)+" = " + str(cv2.contourArea(contours[i]))
cv2.imshow("img", blank)
cv2.waitKey(0)
cv2.destroyAllWindows()
1, separate out the different colored blobs. They look like they are generated by some other mapping software so presumably the colors are fixed and known. Make a new image for each color
2, For an image that contains only blobs of a fixed color and a black background you can make contours of the outlines (see findContours). Opencv will give you a separate contour for each blob.
3, Calculate the area of each contour -there is an opencv function to do this.

Opencv divide contacted circles into single

I have an image to process.I need detect all the circles in the image.Here is it.
And here is my code.
import cv2
import cv2.cv as cv
img = cv2.imread(imgpath)
cv2.imshow("imgorg",img)
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
cv2.imshow("gray",gray)
ret,thresh = cv2.threshold(gray, 199, 255, cv.CV_THRESH_BINARY_INV)
cv2.imshow("thresh",thresh)
cv2.waitKey(0)
cv2.destrotAllWindows()
Then,I got a image like this.
And I tried to use erode and dilate to divided them into single.But it doesnt work.My question is how to divide these contacted circles into single,so i can detect them.
According to #Micka's idea,I tried to process the image in following way,and here is my code.
import cv2
import cv2.cv as cv
import numpy as np
def findcircles(img,contours):
minArea = 300;
minCircleRatio = 0.5;
for contour in contours:
area = cv2.contourArea(contour)
if area < minArea:
continue
(x,y),radius = cv2.minEnclosingCircle(contour)
center = (int(x),int(y))
radius = int(radius)
circleArea = radius*radius*cv.CV_PI;
if area/circleArea < minCircleRatio:
continue;
cv2.circle(img, center, radius, (0, 255, 0), 2)
cv2.imshow("imggg",img)
img = cv2.imread("a.png")
cv2.imshow("org",img)
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret,threshold = cv2.threshold(gray, 199, 255,cv. CV_THRESH_BINARY_INV)
cv2.imshow("threshold",threshold)
blur = cv2.medianBlur(gray,5)
cv2.imshow("blur",blur)
laplacian=cv2.Laplacian(blur,-1,ksize = 5,delta = -50)
cv2.imshow("laplacian",laplacian)
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(7,7))
dilation = cv2.dilate(laplacian,kernel,iterations = 1)
cv2.imshow("dilation", dilation)
result= cv2.subtract(threshold,dilation)
cv2.imshow("result",result)
contours, hierarchy = cv2.findContours(result,cv2.RETR_LIST,cv2.CHAIN_APPROX_NONE)
findcircles(gray,contours)
But I dont get the same effect as #Micka's.I dont know which step is wrong.
Adapting the idea of #jochen I came to this:
extract the full circle mask as you've done (I called it fullForeground )
from your colored image, compute grayscale, blur (median blur size 7) it and and extract edges, for example with cv::Laplacian
This laplacian thresholded > 50 gives:
cv::Laplacian(blurred, lap, 0, 5); // no delta
lapMask = lap > 50; // thresholding to values > 50
This one dilated once gives:
cv::dilate(lapMask, dilatedThresholdedLaplacian, cv::Mat()); // dilate the edge mask once
Now subtraction fullForeground - dilatedThresholdedLaplacian (same as and_not operator for this type of masks) gives:
from this you can compute contours. For each contour you can compute the area and compare it to the area of an enclosing circle, giving this code and result:
std::vector<std::vector<cv::Point> > contours;
cv::findContours(separated.clone(), contours, CV_RETR_LIST, CV_CHAIN_APPROX_NONE);
double minArea = 500;
double minCircleRatio = 0.5;
for(unsigned int i=0; i<contours.size(); ++i)
{
double cArea = cv::contourArea(contours[i]);
if(cArea < minArea) continue;
//filteredContours.push_back(contours[i]);
//cv::drawContours(input, contours, i, cv::Scalar(0,255,0), 1);
cv::Point2f center;
float radius;
cv::minEnclosingCircle(contours[i], center, radius);
double circleArea = radius*radius*CV_PI;
if(cArea/circleArea < minCircleRatio) continue;
cv::circle(input, center, radius, cv::Scalar(0,0,255),2);
}
here is another image showing the coverage:
hope this helps
I think the first mistake ist the value of thesh.
In your example the command cv2.threshold converts all white areas to black and everything else to white. I would suggest using a smaller value for thesh so that all black pixel get converted to white and all white or "colored" pixels (inside the circles) get converted to black or vise versa. The value of thesh should be a little bigger than the brightest of the black pixels.
See opencv docu for threshold for more information.
Afterwards I would let opencv find all contours in the thresholded image and filter them for "valid" circles, e.g. by size and shape.
If that is not sufficiant you could segment the inner circle from the rest of the image: First compute threasholdImageA with all white areas colored black. Then compute threasholdImageB with all the black areas being black. Afterwards combine both, threasholdImageA and threasholdImageB, (e.g. with numpy.logical_and) to have a binary image with only the inner circle being white and the rest black. Of course the values for the threshold have to be chosen wisely to get the specific result.
That way also circles where the inner part directly touches the background will be segmented.

How to extract white region in an image

I have a sample image like this
I'm looking for a way to black out the noise from the image such that I end up with an image that just has black text on white background so that I may send it to tesseract.
I've tried morphing with
kernel = np.ones((4,4),np.uint8)
opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)
cv2.imshow("opening", opening)
but it doesn't seem to work.
I've also tried to find contours
img = cv2.cvtColor(rotated, cv2.COLOR_BGR2GRAY)
(cnts, _) = cv2.findContours(img, cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
cnts = sorted(cnts, key = cv2.contourArea, reverse = True)[:1]
for c in cnts:
x,y,w,h = cv2.boundingRect(c)
roi=rotated[y:y+h,x:x+w].copy()
cv2.imwrite("roi.png", roi)
With the above code, I get the following contours:
which leads to this image when cropped:
which is still not good enough. I want black text on white background, so that I can send it to tesseract OCR and have good success rate.
Is there anything else I can try?
Update
Here is an additional similar image. This one is a bit easier because it has a smooth rectangle in it
The following works for your given example, although it might need tweaking for a wider range of images.
import numpy as np
import cv2
image_src = cv2.imread("input.png")
gray = cv2.cvtColor(image_src, cv2.COLOR_BGR2GRAY)
ret, gray = cv2.threshold(gray, 250,255,0)
image, contours, hierarchy = cv2.findContours(gray, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
largest_area = sorted(contours, key=cv2.contourArea)[-1]
mask = np.zeros(image_src.shape, np.uint8)
cv2.drawContours(mask, [largest_area], 0, (255,255,255,255), -1)
dst = cv2.bitwise_and(image_src, mask)
mask = 255 - mask
roi = cv2.add(dst, mask)
roi_gray = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY)
ret, gray = cv2.threshold(roi_gray, 250,255,0)
image, contours, hierarchy = cv2.findContours(gray, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
max_x = 0
max_y = 0
min_x = image_src.shape[1]
min_y = image_src.shape[0]
for c in contours:
if 150 < cv2.contourArea(c) < 100000:
x, y, w, h = cv2.boundingRect(c)
min_x = min(x, min_x)
min_y = min(y, min_y)
max_x = max(x+w, max_x)
max_y = max(y+h, max_y)
roi = roi[min_y:max_y, min_x:max_x]
cv2.imwrite("roi.png", roi)
Giving you the following type of output images:
And...
The code works by first locating the largest contour area. From this a mask is created which is used to first select only the area inside, i.e. the text. The inverse of the mask is then added to the image to convert the area outside the mask to white.
Lastly contours are found again for this new image. Any contour areas outside a suitable size range are discarded (this is used to ignore any small noise areas), and a bounding rect is found for each. With each of these rectangles, an outer bounding rect is calculated for all of the remaining contours, and a crop is made using these values to give the final image.
Update - To get the remainder of the image, i.e. with the above area removed, the following could be used:
image_src = cv2.imread("input.png")
gray = cv2.cvtColor(image_src, cv2.COLOR_BGR2GRAY)
ret, gray = cv2.threshold(gray, 10, 255,0)
image, contours, hierarchy = cv2.findContours(gray, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
largest_area = sorted(contours, key=cv2.contourArea)[-1]
mask = np.zeros(image_src.shape, np.uint8)
cv2.drawContours(mask, [largest_area], 0, (255,255,255,255), -1)
image_remainder = cv2.bitwise_and(image_src, 255 - mask)
cv2.imwrite("remainder.png", image_remainder)
I get this:
Result
Source Code:
if __name__ == '__main__':
SrcImg = cv2.imread('./Yahi9.png', cv2.CV_LOAD_IMAGE_GRAYSCALE)
_, BinImg = cv2.threshold(SrcImg, 80, 255, cv2.THRESH_OTSU)
Contours, Hierarchy = cv2.findContours(image=copy.deepcopy(SrcImg),
mode=cv2.cv.CV_RETR_EXTERNAL,
method=cv2.cv.CV_CHAIN_APPROX_NONE)
MaxContour, _ = getMaxContour(Contours)
Canvas = np.ones(SrcImg.shape, np.uint8)
cv2.drawContours(image=Canvas, contours=[MaxContour], contourIdx=0, color=(255), thickness=-1)
mask = (Canvas != 255)
RoiImg = copy.deepcopy(BinImg)
RoiImg[mask] = 255
RoiImg = cv2.morphologyEx(src=RoiImg, op=cv2.MORPH_CLOSE, kernel=np.ones((3,3)), iterations=4)
cv2.imshow('RoiImg', RoiImg)
cv2.waitKey(0)
Function:
def getMaxContour(contours):
MaxArea = 0
Location = 0
for idx in range(0, len(contours)):
Area = cv2.contourArea(contours[idx])
if Area > MaxArea:
MaxArea = Area
Location = idx
MaxContour = np.array(contours[Location])
return MaxContour, MaxArea
Ehh, it's python code.
It only works when the white region is the max contour.
Basic idea of this answer is to use border around text.
1) Erode horizontally with a very large kernel, say size of 100 px or 8 times size of single expected character, something like that. It should be done row-wise. The extreme ordinate will give y-location of boundaries around text.
2) Process vertically same way to get x-location of boundaries around text. Then use these locations to crop out image you want.
-- One benefit of this method is you will get every sentence/word segmented separately which, I presume, is good for an OCR.
Happy Coding :)
Edited in by Mark Setchell
Here is a demo of 1)
Here is a demo of 2)

Categories