I am trying to solve the issue of creating paths around logos with OpenCV.
I have attached two images, tekst.png and tekst2.png. I have also attached an image comparison.png that shows the wanted result (created manually) and the result I currently am getting with my program.
If anyone has any tips for me, I'd appreciate it a lot!
Short description of wanted solution:
Returns one outer contour that is as close as possible to the logo.
I can use the contour mentioned in the last sentence to scale it up to make padding in between the logo and the contour.
Some kind of algorithm to smooth out the finished contour
The code I currently have:
def current_milli_time():
return round(time.time() * 1000)
def time_calculation_start():
timing.append(current_milli_time())
def time_calculation_end(string):
timing.append(current_milli_time())
print(str(string) + ": ", timing[1] - timing[0], "ms")
timing.clear()
def render_png(filename):
print(filename)
time_calculation_start()
original_image = cv2.imread(str(filename), cv2.IMREAD_UNCHANGED)
copy = original_image.copy() # Saved for imagecreation
time_calculation_end("Setup")
time_calculation_start()
if(original_image.shape[2] == 4):
b,g,r,mask = cv2.split(original_image)
time_calculation_end("Mask")
# Reduce outer turdss
time_calculation_start()
kernel = np.ones((3,3), np.uint8)
dilation = cv2.dilate(mask,kernel,iterations = 2)
dilation = cv2.erode(dilation,kernel,iterations = 1)
time_calculation_end("Dialtion")
time_calculation_start()
gaublur = cv2.GaussianBlur(dilation,(16,16),0)
time_calculation_end("Gaussian blur")
#Find contours
time_calculation_start()
contours, hierarchy = cv2.findContours(gaublur, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
time_calculation_end("Find contours")
print("\tContour layers: ", len(contours))
# Draw contours
time_calculation_start()
cv2.drawContours(copy, contours, -1, (0, 255, 0, 255),1)
time_calculation_end("Draw contours")
print("\n")
cv2.imwrite(str(render_path) + str(filename), copy)
Here is one way to do that in Python/OpenCV. Note that I reduced the size of your input.
Read the input
Extract the BGR channels
Extract the alpha channel
Get the largest contour from the alpha channel to remove small regions
Reduce the number of vertices to make it smoother
Draw a white filled contour on black background
Dilate the contour image
Make an edge image and thicken it
Make a white background image
Invert the dilated contour and blur it for the shadow
Overlay the blurred dilated area on the background
Overlay the dilated white region
Overlay the bgr image
Overlay the edge
Save the result
Input:
import cv2
import numpy as np
# read image
img = cv2.imread('hjemsokt_small.png', cv2.IMREAD_UNCHANGED)
# extract bgr image
bgr = img[:,:,0:3]
# extract alpha channel
alpha = img[:,:,3]
# get largest contours
contours = cv2.findContours(alpha, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
big_contour = max(contours, key=cv2.contourArea)
# smooth contour
peri = cv2.arcLength(big_contour, True)
big_contour = cv2.approxPolyDP(big_contour, 0.001 * peri, True)
# draw white filled contour on black background
contour_img = np.zeros_like(alpha)
cv2.drawContours(contour_img, [big_contour], 0, 255, -1)
# apply dilate to connect the white areas in the alpha channel
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (40,40))
dilate = cv2.morphologyEx(contour_img, cv2.MORPH_DILATE, kernel)
# make edge outline
edge = cv2.Canny(dilate, 0, 200)
# thicken edge
edge = cv2.GaussianBlur(edge, (0,0), sigmaX=0.3, sigmaY=0.3)
# make background
result = np.full_like(bgr, (255,255,255))
# invert dilated image and blur
dilate_inv = 255 - dilate
dilate_inv = cv2.GaussianBlur(dilate_inv, (0,0), sigmaX=21, sigmaY=21)
dilate_inv = cv2.merge([dilate_inv,dilate_inv,dilate_inv])
# overlay blurred dilated area on background
result[dilate_inv>0] = dilate_inv[dilate_inv>0]
# overlay dilated white region
result[dilate==255] = (255,255,255)
# overlay bgr image
result[contour_img==255] = bgr[contour_img==255]
# overlay edge
result[edge!=0] = (96,96,96)
# save resulting images
cv2.imwrite('hjemsokt_small_alpha.jpg',alpha)
cv2.imwrite('hjemsokt_small_contour.jpg',contour_img)
cv2.imwrite('hjemsokt_small_alpha_dilated.jpg',dilate)
cv2.imwrite('hjemsokt_small_alpha_dilated_inv.jpg',dilate_inv)
cv2.imwrite('hjemsokt_small_alpha_dilated_edge.jpg',edge)
cv2.imwrite('hjemsokt_small_result.jpg',result)
# show thresh and result
cv2.imshow("bgr", bgr)
cv2.imshow("alpha", alpha)
cv2.imshow("contour_img", contour_img)
cv2.imshow("dilate", dilate)
cv2.imshow("dilate_inv", dilate_inv)
cv2.imshow("edge", edge)
cv2.imshow("result", result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Alpha channel:
Contour Image:
Smoothed Dilated contour image:
Inverted contour blurred:
Edge image:
Result:
Related
I've got a shape detection with OpenCV in Python going on; bolts and nuts. I take a picture, make it binary, and detect edges. Now the white area is always grainy because of dust and grime. My detection uses the largest areas as parts, which works great. But how can I delete the thousands of objects caused by dust?
In short, I want to reduce the array of shapes to only the biggest ones for further processing.
Here is one way to do that as per my comment above using Python/OpenCV.
From your binary image get the contours. Then select the largest contour. Then draw a white filled contour on a black background image the same size as your input as a mask. Then use numpy to blacken everything in your image that is black in your mask.
Input:
import cv2
import numpy as np
# load image
img = cv2.imread("coke_bottle2.png")
hh, ww = img.shape[:2]
# convert to gray
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
# threshold using inRange
thresh = cv2.threshold(gray, 50, 255, cv2.THRESH_BINARY)[1]
# apply morphology closing to fill black holes and smooth outline
# could use opening to remove white spots, but we will use contours
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (25,25))
thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
# get the largest contour
contours = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
big_contour = max(contours, key=cv2.contourArea)
# draw largest contour as white filled on black background as mask
mask = np.zeros((hh,ww), dtype=np.uint8)
cv2.drawContours(mask, [big_contour], 0, 255, -1)
# use mask to black all but largest contour
result = img.copy()
result[mask==0] = (0,0,0)
# write result to disk
cv2.imwrite("coke_bottle2_threshold.png", thresh)
cv2.imwrite("coke_bottle2_mask.png", mask)
cv2.imwrite("coke_bottle2_background_removed.jpg", result)
# display it
cv2.imshow("thresh", thresh)
cv2.imshow("mask", mask)
cv2.imshow("result", result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Threshold Image (contains small extraneous white regions):
Mask Image (only the largest filled contour):
Result:
This might be a bit too "general" question, but how do I perform GRAYSCALE image segmentation and keep the largest contour? I am trying to remove background noise (i.e. labels) from breast mammograms, but I am not successful. Here is the original image:
First, I applied AGCWD algorithm (based on paper "Efficient Contrast Enhancement Using Adaptive Gamma Correction With Weighting Distribution") in order to get better contrast of the image pixels, like so:
Afterwards, I tried executing following steps:
Image segmentation using OpenCV's KMeans clustering algorithm:
enhanced_image_cpy = enhanced_image.copy()
reshaped_image = np.float32(enhanced_image_cpy.reshape(-1, 1))
number_of_clusters = 10
stop_criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 100, 0.1)
ret, labels, clusters = cv2.kmeans(reshaped_image, number_of_clusters, None, stop_criteria, 10, cv2.KMEANS_RANDOM_CENTERS)
clusters = np.uint8(clusters)
Canny Edge Detection:
removed_cluster = 1
canny_image = np.copy(enhanced_image_cpy).reshape((-1, 1))
canny_image[labels.flatten() == removed_cluster] = [0]
canny_image = cv2.Canny(canny_image,100,200).reshape(enhanced_image_cpy.shape)
show_images([canny_image])
Find and Draw Contours:
initial_contours_image = np.copy(canny_image)
initial_contours_image_bgr = cv2.cvtColor(initial_contours_image, cv2.COLOR_GRAY2BGR)
_, thresh = cv2.threshold(initial_contours_image, 50, 255, 0)
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(initial_contours_image_bgr, contours, -1, (255,0,0), cv2.CHAIN_APPROX_SIMPLE)
show_images([initial_contours_image_bgr])
Here is how image looks after I draw 44004 contours:
I am not sure how can I get one BIG contour, instead of 44004 small ones. Any ideas how to fix my approach, or possibly any ideas on using alternative approach to get rid of label in top right corner.
Thanks in advance!
Here is one way to do that in Python OpenCV
Read the image
Threshold and invert so the borders are black
Remove the borders of the image as follows (so as to make it easier to get the relevant contours later):
Count the number of non-zero pixels in each column and find the first and last column that have counts greater than 0
Count the number of non-zero pixels in each row and find the first and last row that have counts greater than 0
Crop the image to remove the borders
Crop thresh1 and invert to make thresh2
Get the external contours from thresh2
Find the largest contour and draw as white filled on a black background as a mask
Make all pixels in the cropped image black where the mask is black
Save the results -
Input:
import cv2
import numpy as np
# read image
img = cv2.imread('xray3.png')
# convert to gray
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# threshold and invert
thresh1 = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY)[1]
thresh1 = 255 - thresh1
# remove borders
# count number of white pixels in columns as new 1D array
count_cols = np.count_nonzero(thresh1, axis=0)
# get first and last x coordinate where black
first_x = np.where(count_cols>0)[0][0]
last_x = np.where(count_cols>0)[0][-1]
print(first_x,last_x)
# count number of white pixels in rows as new 1D array
count_rows = np.count_nonzero(thresh1, axis=1)
# get first and last y coordinate where black
first_y = np.where(count_rows>0)[0][0]
last_y = np.where(count_rows>0)[0][-1]
print(first_y,last_y)
# crop image
crop = img[first_y:last_y+1, first_x:last_x+1]
# crop thresh1 and invert
thresh2 = thresh1[first_y:last_y+1, first_x:last_x+1]
thresh2 = 255 - thresh2
# get external contours and keep largest one
contours = cv2.findContours(thresh2, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
big_contour = max(contours, key=cv2.contourArea)
# make mask from contour
mask = np.zeros_like(thresh2 , dtype=np.uint8)
cv2.drawContours(mask, [big_contour], 0, 255, -1)
# make crop black everywhere except where largest contour is white in mask
result = crop.copy()
result[mask==0] = (0,0,0)
# write result to disk
cv2.imwrite("xray3_thresh1.jpg", thresh1)
cv2.imwrite("xray3_crop.jpg", crop)
cv2.imwrite("xray3_thresh2.jpg", thresh2)
cv2.imwrite("xray3_mask.jpg", mask)
cv2.imwrite("xray3_result.png", result)
# display it
cv2.imshow("thresh1", thresh1)
cv2.imshow("crop", crop)
cv2.imshow("thresh2", thresh2)
cv2.imshow("mask", mask)
cv2.imshow("result", result)
cv2.waitKey(0)
Threshold 1 image:
Cropped image:
Threshold 2 image:
Mask image:
Result:
I want to detect text on x-ray images. The goal is to extract the oriented bounding boxes as a matrix where each row is a detected bounding box and each row contains the coordinates of all four edges i.e. [x1, x2, y1, y2]. I'm using python 3 and OpenCV 4.2.0.
Here is a sample image:
The string "test word", "a" and "b" should be detected.
I followed this OpenCV tutorial about creating rotated boxes for contours and this stackoverflow answer about detecting a text area in an image.
The resulting boundary boxes should look something like this:
I was able to detect the text, but the result included a lot of boxes without text.
Here is what I tried so far:
img = cv2.imread(file_name)
## Open the image, convert it into grayscale and blur it to get rid of the noise.
img2gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
ret, mask = cv2.threshold(img2gray, 180, 255, cv2.THRESH_BINARY)
image_final = cv2.bitwise_and(img2gray, img2gray, mask=mask)
ret, new_img = cv2.threshold(image_final, 180, 255, cv2.THRESH_BINARY) # for black text , cv.THRESH_BINARY_INV
kernel = cv2.getStructuringElement(cv2.MORPH_CROSS, (3, 3))
dilated = cv2.dilate(new_img, kernel, iterations=6)
canny_output = cv2.Canny(dilated, 100, 100 * 2)
cv2.imshow('Canny', canny_output)
## Finds contours and saves them to the vectors contour and hierarchy.
contours, hierarchy = cv2.findContours(canny_output, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# Find the rotated rectangles and ellipses for each contour
minRect = [None] * len(contours)
for i, c in enumerate(contours):
minRect[i] = cv2.minAreaRect(c)
# Draw contours + rotated rects + ellipses
drawing = np.zeros((canny_output.shape[0], canny_output.shape[1], 3), dtype=np.uint8)
for i, c in enumerate(contours):
color = (255, 0, 255)
# contour
cv2.drawContours(drawing, contours, i, color)
# rotated rectangle
box = cv2.boxPoints(minRect[i])
box = np.intp(box) # np.intp: Integer used for indexing (same as C ssize_t; normally either int32 or int64)
cv2.drawContours(img, [box], 0, color)
cv2.imshow('Result', img)
cv2.waitKey()
Do I need to run the results through OCR to make sure whether it is text or not? What other approaches should I try?
PS: I'm quite new to computer vision and not familiar with most concepts yet.
Here's a simple approach:
Obtain binary image. Load image, create blank mask, convert to grayscale, Gaussian blur, then Otsu's threshold
Merge text into a single contour. Since we want to extract the text as one piece, we perform morphological operations to connect individual text contours into a single contour.
Extract text. We find contours then filter using contour area with cv2.contourArea and aspect ratio using cv2.arcLength + cv2.approxPolyDP. If a contour passes the filter, we find the rotated bounding box and draw this onto our mask.
Isolate text. We perform an cv2.bitwise_and operation to extract the text.
Here's a visualization of the process. Using this screenshotted input image (since your provided input image was connected as one image):
Input image -> Binary image
Morph close -> Detected text
Isolated text
Results with the other image
Input image -> Binary image + morph close
Detected text -> Isolated text
Code
import cv2
import numpy as np
# Load image, create mask, grayscale, Gaussian blur, Otsu's threshold
image = cv2.imread('1.png')
original = image.copy()
blank = np.zeros(image.shape[:2], dtype=np.uint8)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (5,5), 0)
thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
# Merge text into a single contour
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))
close = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel, iterations=3)
# Find contours
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:
# Filter using contour area and aspect ratio
x,y,w,h = cv2.boundingRect(c)
area = cv2.contourArea(c)
ar = w / float(h)
if (ar > 1.4 and ar < 4) or ar < .85 and area > 10 and area < 500:
# Find rotated bounding box
rect = cv2.minAreaRect(c)
box = cv2.boxPoints(rect)
box = np.int0(box)
cv2.drawContours(image,[box],0,(36,255,12),2)
cv2.drawContours(blank,[box],0,(255,255,255),-1)
# Bitwise operations to isolate text
extract = cv2.bitwise_and(thresh, blank)
extract = cv2.bitwise_and(original, original, mask=extract)
cv2.imshow('thresh', thresh)
cv2.imshow('image', image)
cv2.imshow('close', close)
cv2.imshow('extract', extract)
cv2.waitKey()
I removed the text using the following comand (after the code of above):
gray2 = cv2.cvtColor(extract, cv2.COLOR_BGR2GRAY)
blur2 = cv2.GaussianBlur(gray2, (5,5), 0)
thresh2 = cv2.threshold(blur2, 0, 255, cv2.THRESH_BINARY)[1]
test = cv2.inpaint(original, thresh2, 7, cv2.INPAINT_TELEA)
I have an image which looks something like this:
here is the image
I need to make borders around the points such that they are divided into clusters. For example, the center of the image is one region. One other region can be the top of the image. How can I achieve this, with python preferably?
Here is one way to do that in Python/OpenCV.
- Read the input as unchanged, since it has transparency
- Separate the base image and the alpha channel
- Mask the base image with the alpha channel so as to make the white outer region with the text into all black
- Convert that image into grayscale and then into black/white
- Apply morphology close to connect all the dots in the regions
- Find all contours larger than some minimum area
- Draw the contours on the base image
- Save the results
Input:
import cv2
import numpy as np
# read image with transparency
image = cv2.imread("dots.png", cv2.IMREAD_UNCHANGED)
# separate base image and alpha channel and make background under transparency into black to remove white border and text
base = image[:,:,0:3]
alpha = image[:,:,3]
alpha = cv2.merge([alpha,alpha,alpha])
img = cv2.bitwise_and(base, alpha)
# convert img to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#do threshold on gray image
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY)[1]
# apply morphology close
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (15, 15))
close = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
# Get contours
cnts = cv2.findContours(close, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
result = base.copy()
for c in cnts:
area = cv2.contourArea(c)
if area > 100:
cv2.drawContours(result, [c], -1, (0, 255, 0), 1)
# display it
cv2.imshow("BASE", base)
cv2.imshow("BLACKENED", img)
cv2.imshow("CLOSED", close)
cv2.imshow("RESULT", result)
cv2.waitKey(0)
# write results to disk
cv2.imwrite("dots_blackened.png", img)
cv2.imwrite("dots_closed.png", close)
cv2.imwrite("dots_clusters.png", result)
Base Image with transparency blackened:
Morphology Close Image:
Contours on base image:
please help me, I need to insert an image on the substrate.
substrate:
It png, and in the area that is blank with cities, you must insert the image from edge to edge of the frame.
The problem is that I can't find an example of how to insert an image to the known coordinate points of the corners of a given substrate.
Pls help))
My test image
import cv2
import numpy as np
from skimage import io
frame = cv2.cvtColor(io.imread('as.png'), cv2.COLOR_RGB2BGR)
image = cv2.cvtColor(io.imread("Vw5Rc.jpg"), cv2.COLOR_RGB2BGR)
mask = 255 * np.uint8(np.all(frame == [0, 0, 0], axis=2))
contours, _ = cv2.findContours(mask, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
cnt = min(contours, key=cv2.contourArea)
(x, y, w, h) = cv2.boundingRect(cnt)
# Copy appropriately resized image to frame
frame[y:y+h, x:x+w] = cv2.resize(image, (w, h))
cv2.imwrite('frame.png', frame)
I'm trying to find the area where to insert the image by color, the red color of the area I can find, and if there is no color?
The static frame has a constant size.
Here is one way to do it in Python/OpenCV, if I understand what you want.
Read the substrate and trees images
Extract the alpha channel from the substrate
Extract the substrate image without the alpha channel
Use the alpha channel to color the base substrate image white where the alpha channel is black to correct a flaw in the base image
Threshold the alpha channel and invert it
Use morphology to remove the grid lines so that there is only one "outer" contour.
Extract the contour and its bounding box
Resize the trees image to the size of the bounding box.
Use numpy indexing and slicing to multiply the region of the substrate with the resized trees image.
Save the results.
Optionally, display the various images.
Substrate Image:
Trees Image:
import cv2
import numpy as np
# load substrate with alpha channel
substrate = cv2.imread("substrate.png", cv2.IMREAD_UNCHANGED)
hh, ww, cc = substrate.shape
# load colored image
trees = cv2.imread("trees.jpg")
# make img white where alpha is black to merge the alpha channel with the image
alpha = substrate[:,:,3]
img = substrate[:,:,0-2]
img[alpha==0] = 255
img = cv2.merge((img,img,img))
# threshold the img
ret, thresh = cv2.threshold(alpha,0,255,0)
# invert thresh
thresh = 255 - thresh
# make grid lines white in thresh so will get only one contour
kernel = np.ones((9,9), np.uint8)
thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
# find one outer contour
cntrs = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cntrs = cntrs[0] if len(cntrs) == 2 else cntrs[1]
# get bounding box of contour of white rectangle in thresh
for c in cntrs:
x,y,w,h = cv2.boundingRect(c)
#cv2.rectangle(img, (x,y), (x+w,y+h),(0, 0, 255), 2)
# resize trees
trees = cv2.resize(trees,(w,h),0,0)
# generate result
result = img.copy()
result[y:y+h, x:x+w] = img[y:y+h, x:x+w]/255 * trees
# write result to disk
cv2.imwrite("substrate_over_trees.jpg", result)
cv2.imshow("ALPHA", alpha)
cv2.imshow("IMG", img)
cv2.imshow("THRESH", thresh)
cv2.imshow("TREES", trees)
cv2.imshow("RESULT", result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Result:
Note that there is distortion of the trees image, because its aspect ratio does not match the region of the substrate image corresponding to the contour bounding box. This can be changed to maintain the aspect ratio, but then the image will need to be padded to white or some other color to fill the remaining area of the bounding box.