I have an input binary (black and white) image containing two concentric ellipses. The background and the internal ellipse are black while the external ellipse is white.
My goal is to change all pixels of the internal ellipse from black to white.
This is a simple code snippet to generate two concentric ellipses:
import cv2
import numpy as np
# costants
min_height, max_height = 15, 60
min_width, max_width = 15, 60
min_rot, max_rot = 0, 360
center = (max_width*2, max_height*2)
# empty image
image = np.zeros((max_width*4, max_height*4), dtype=np.uint8)
# randomly selecting angle, horizontal and vertical compression
width = int(np.random.uniform(min_width, max_width))
height = int(np.random.uniform(min_height, max_height))
angle = np.random.uniform(min_rot, max_rot)
# draw external ellipse
image = cv2.ellipse(image, center, (width, height), angle, 0, 360, color=255, thickness=-1)
# draw internal ellipse
image = cv2.ellipse(image, center, (width//2, height//2), angle, 0, 360, color=0, thickness=-1)
cv2.imshow('concentric ellipses', image)
cv2.waitKey(0)
My goal is to change all pixels of the internal ellipse from black to white. Like this:
example
A Naive approach could be a loop for on each image but this seems very slow to me:
def fill_image(img):
import numpy as np
num_rows = img.shape[0]
for row in range(num_rows):
# get col index for white pixels (external ellipse)
white_pixels = np.where(img[row, :] == 255)[0]
# assign white pixels
start_px, end_px = min(white_pixels), max(white_pixels)
img[row, start_px:end_px] = 255
return img
Which is the most pythonic and efficient way to do that?
Related
is it possible to get the coordinates of the incomplete circle? i am using opencv and python. so i can find the most of the circles.
But i have no clue how can i detect the incomplete cirlce in the picture.
I am looking for a simple way to solve it.
import sys
import cv2 as cv
import numpy as np
## [load]
default_file = 'captcha2.png'
# Loads an image
src = cv.imread(cv.samples.findFile(default_file), cv.IMREAD_COLOR)
## [convert_to_gray]
# Convert it to gray
gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
## [convert_to_gray]
## [reduce_noise]
# Reduce the noise to avoid false circle detection
gray = cv.medianBlur(gray, 3)
## [reduce_noise]
## [houghcircles]
#rows = gray.shape[0]
circles = cv.HoughCircles(gray, cv.HOUGH_GRADIENT, 1, 5,
param1=1, param2=35,
minRadius=1, maxRadius=30)
## [houghcircles]
## [draw]
if circles is not None:
circles = np.uint16(np.around(circles))
for i in circles[0, :]:
center = (i[0], i[1])
# circle center
cv.circle(src, center, 1, (0, 100, 100), 3)
# circle outline
radius = i[2]
cv.circle(src, center, radius, (255, 0, 255), 3)
## [draw]
## [display]
cv.imshow("detected circles", src)
cv.waitKey(0)
## [display]
Hi - there is an other Picture. I want the x and y cords of the incomplete circle, light blue on the lower left.
Here the original Pic:
You need to remove the colorful background of your image and display only circles.
One approach is:
Get the binary mask of the input image
Apply Hough Circle to detect the circles
Binary mask:
Using the binary mask, we will detect the circles:
Code:
# Load the libraries
import cv2
import numpy as np
# Load the image
img = cv2.imread("r5lcN.png")
# Copy the input image
out = img.copy()
# Convert to the HSV color space
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# Get binary mask
msk = cv2.inRange(hsv, np.array([0, 0, 130]), np.array([179, 255, 255]))
# Detect circles in the image
crc = cv2.HoughCircles(msk, cv2.HOUGH_GRADIENT, 1, 10, param1=50, param2=25, minRadius=0, maxRadius=0)
# Ensure circles were found
if crc is not None:
# Convert the coordinates and radius of the circles to integers
crc = np.round(crc[0, :]).astype("int")
# For each (x, y) coordinates and radius of the circles
for (x, y, r) in crc:
# Draw the circle
cv2.circle(out, (x, y), r, (0, 255, 0), 4)
# Print coordinates
print("x:{}, y:{}".format(x, y))
# Display
cv2.imshow("out", np.hstack([img, out]))
cv2.waitKey(0)
Output:
x:178, y:60
x:128, y:22
x:248, y:20
x:378, y:52
x:280, y:60
x:294, y:46
x:250, y:44
x:150, y:62
Explanation
We have three chance for finding the thresholding:
Simple Threshold result:
Adaptive Threshold
Binary mask
As we can see the third option gave us a suitable result. Of course, you could get the desired result with other options, but it might take a long time for finding the suitable parameters. Then we applied Hough circles, played with parameter values, and got the desired result.
Update
For the second uploaded image, you can detect the semi-circle by reducing the first and second parameters of the Hough circle.
crc = cv2.HoughCircles(msk, cv2.HOUGH_GRADIENT, 1, 10, param1=10, param2=15, minRadius=0, maxRadius=0)
Replacing the above line in the main code will result in:
Console result
x:238, y:38
x:56, y:30
x:44, y:62
x:208, y:26
I have plotted a rectangle using matplotlib and would like to place an image in it as shown in the image below. Does anyone have an idea how I can achieve this in python?
Here is one way using Python/OpenCV/Numpy. Do a perspective warp of the panda image using its 4 corners and the 4 corners of the rectangle. Then make a mask of the excess regions, which are black in the warped image. Finally, blend the warped image and background image using the mask.
Input:
Graph Image:
import numpy as np
import cv2
import math
# read image to be processed
img = cv2.imread("panda.png")
hh, ww = img.shape[:2]
# read background image
bck = cv2.imread("rectangle_graph.png")
hhh, www = bck.shape[:2]
# specify coordinates for corners of img in order TL, TR, BR, BL as x,y pairs
img_pts = np.float32([[0,0], [ww-1,0], [ww-1,hh-1], [0,hh-1]])
# manually pick coordinates of corners of rectangle in background image
bck_pts = np.float32([[221,245], [333,26], [503,111], [390,331]])
# compute perspective matrix
matrix = cv2.getPerspectiveTransform(img_pts,bck_pts)
#print(matrix)
# change black and near-black to graylevel 1 in each channel so that no values
# inside panda image will be black in the subsequent mask
img[np.where((img<=[5,5,5]).all(axis=2))] = [1,1,1]
# do perspective transformation setting area outside input to black
img_warped = cv2.warpPerspective(img, matrix, (www,hhh), cv2.INTER_LINEAR, borderMode=cv2.BORDER_CONSTANT, borderValue=(0,0,0))
# make mask for area outside the warped region
# (black in image stays black and rest becomes white)
mask = cv2.cvtColor(img_warped, cv2.COLOR_BGR2GRAY)
mask = cv2.threshold(mask, 0, 255, cv2.THRESH_BINARY)[1]
mask = cv2.merge([mask,mask,mask])
mask_inv = 255 - mask
# use mask to blend between img_warped and bck
result = ( 255 * (bck * mask_inv + img_warped * mask) ).clip(0, 255).astype(np.uint8)
# save images
cv2.imwrite("panda_warped.png", img_warped)
cv2.imwrite("panda_warped_mask.png", mask)
cv2.imwrite("panda_in_graph.png", result)
# show the result
cv2.imshow("warped", img_warped)
cv2.imshow("mask", mask)
cv2.imshow("result", result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Warped Input:
Mask:
Result:
You can use imshow to place the image at a given position. And add a transform to give the image the same rotation as the rectangle.
To steer away from possible copyright issues, the following code uses an image from wikipedia (author: Fernando Revilla):
import matplotlib.pyplot as plt
from matplotlib import transforms
from matplotlib.patches import Rectangle
file = 'https://upload.wikimedia.org/wikipedia/commons/thumb/8/82/Giant_Panda_Tai_Shan.JPG/1200px-Giant_Panda_Tai_Shan.JPG'
img = plt.imread(file, format='jpg')
fig, ax = plt.subplots()
# suppose a rectangle was drawn onto the plot
x, y = 20, 30
width, height = 12, 9
angle = 70
rect = Rectangle((x, y), width, height, angle=angle, ec='black', fc='none', lw=3)
ax.add_patch(rect)
# draw the image using the rectangles position and rotation
tr = transforms.Affine2D().translate(-x, -y).rotate_deg(angle).translate(x, y)
ax.imshow(img, extent=[x, x + width, y, y + height], transform=tr + ax.transData)
ax.set_aspect('equal') # keep right angles
ax.relim()
ax.autoscale()
plt.show()
I'm trying to extract the detected circles in one image using the circular hough transform. My idea is get every circle or separate each one to then get his color histogram features and after send this features to one classifier as SVM, ANN, KNN etc..
This is my input image:
I'm getting the circles of this way:
import numpy as np
import cv2
import matplotlib.pyplot as plt
cv2.__version__
#read image
file = "lemon.png"
image = cv2.imread(file)
#BGR to RGB
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
#convert to grayscale
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
circles = cv2.HoughCircles(gray,
cv2.HOUGH_GRADIENT,
15,
41,
param1=31,
param2=31,
minRadius=0,
maxRadius=33)
circles = np.uint16(np.around(circles))
for i in circles[0,:]:
# draw the outer circle
cv2.circle(image,(i[0],i[1]),i[2],(0,255,0),2)
# draw the center of the circle
cv2.circle(image,(i[0],i[1]),2,(0,0,255),3)
print("Number of circles: "+ str(len(circles[0,:])))
plt.imshow(image, cmap='gray', vmin=0, vmax=255)
plt.show()
Output:
The next step is try to extract those circles but I don't have idea how to do it.
Well guys I would like to see your suggestions, any I idea I will apreciate it.
Thanks so much.
You can create a binary mask for every circle you detect. Use this mask to extract only the ROIs from the input image. Additionally, you can crop these ROIs and store them in a list to pass them to your classifier.
Here's the code:
import numpy as np
import cv2
# image path
path = "C://opencvImages//"
file = path + "LLfN7.png"
image = cv2.imread(file)
# Convert to grayscale
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
circles = cv2.HoughCircles(gray,
cv2.HOUGH_GRADIENT,
15,
41,
param1=31,
param2=31,
minRadius=0,
maxRadius=33)
# Here are your circles:
circles = np.uint16(np.around(circles))
# Get input size:
dimensions = image.shape
# height, width
height = image.shape[0]
width = image.shape[1]
# Prepare a list to store each ROI:
lemonROIs = []
The idea is that you process one circle at a step. Get the current circle, create a mask, mask the original input, crop the ROI and store it inside the list:
for i in circles[0, :]:
# Prepare a black canvas:
canvas = np.zeros((height, width))
# Draw the outer circle:
color = (255, 255, 255)
thickness = -1
centerX = i[0]
centerY = i[1]
radius = i[2]
cv2.circle(canvas, (centerX, centerY), radius, color, thickness)
# Create a copy of the input and mask input:
imageCopy = image.copy()
imageCopy[canvas == 0] = (0, 0, 0)
# Crop the roi:
x = centerX - radius
y = centerY - radius
h = 2 * radius
w = 2 * radius
croppedImg = imageCopy[y:y + h, x:x + w]
# Store the ROI:
lemonROIs.append(croppedImg)
For each circle you get a cropped ROI:
You can pass that info to your classifier.
I have an image with e.g. Width=999 and Height=767. I know the contour of my ROI and have used rect=cv2.minAreaRect() to get the CenterPosition, Width, Height and Angle of the rotated rectangle around it. Now I need to resize the image to the size of another image with e.g. Width=4096 and Height=2160. So far I am using cv2.resize() to do so.
My problem now is, that of course also the boxPoints of my rectangle take place somewhere else and the data in rect about CenterPosition, Width, Height and Angle of the rotated rectangle around the now resized ROI is not updated and so false. I have tried different workarounds, but didn't find any solution yet.
Here is my code:
import numpy as np
import cv2
#Create black image
img = np.zeros((767, 999, 3), np.uint8)
#Turn ROI to white
cv2.fillConvexPoly(img, np.array(ROI_contour), (255, 255, 255))
#Get Width, Height, and Angle of rectangle around ROI
rect = cv2.minAreaRect(np.array(ROI_contour))
#Draw rotated rectangle in red
box = cv2.boxPoints(rect)
box = np.int0(box)
cv2.drawContours(img,[box], 0, (0,0,255), 1)
#Resize img to new size
img_resized = cv2.resize(img, (4096, 2160), interpolation=cv2.INTER_AREA)
Here is how img e.g. could look like:
img with ROI in white before resizing - CenterPosition, Width, Height and Angle of ROI is known by rect.
How can I get new Width, Height and Angle of the resized ROI?
This is simple unitary method.
In your example, h_old = 767, w_old = 999; h_new = 4096, w_new = 2160.
h_ratio = h_new / h_old = 5.34, w_ratio = w_new / w_old = 2.16
Say the center_position, width and height of the rectangle found in old image is: (old_center_x, old_center_y), old_rect_width and old_rect_height, respectively.
Then the new values would become:
(old_center_x * w_ratio, old_center_y * h_ratio), old_rect_width * w_ratio, old_rect_height * h_ratio, respectively.
Since the aspect ratio of the two images is also not the same,
old_aspect_ratio = 999/767 = 1.30, new_aspect_ratio = 2160 / 4096 = 0.52, you need to multiply this factor with the new dimensions too.
I'm trying to extract the rotated bounding box of contours robustly. I would like to take an image, find the largest contour, get its rotated bounding box, rotate the image to make the bounding box vertical, and crop to size.
For a demonstration, here is an original image linked in the following code. I would like to end up with that shoe rotated to vertical and cropped to size. The following code from this answer seems to work on simple images like opencv lines, etc., but not on photos.
Ends up with this, which is rotated and cropped wrong:
EDIT: After changing the threshold type to cv2.THRESH_BINARY_INV, it now is rotated correctly but cropped wrong:
import cv2
import matplotlib.pyplot as plt
import numpy as np
import urllib.request
plot = lambda x: plt.imshow(x, cmap='gray').figure
url = 'https://i.imgur.com/4E8ILuI.jpg'
img_path = 'shoe.jpg'
urllib.request.urlretrieve(url, img_path)
img = cv2.imread(img_path, 0)
plot(img)
threshold_value, thresholded_img = cv2.threshold(
img, 250, 255, cv2.THRESH_BINARY)
_, contours, _ = cv2.findContours(thresholded_img, 1, 1)
contours.sort(key=cv2.contourArea, reverse=True)
shoe_contour = contours[0][:, 0, :]
min_area_rect = cv2.minAreaRect(shoe_contour)
def crop_minAreaRect(img, rect):
# rotate img
angle = rect[2]
rows, cols = img.shape[0], img.shape[1]
M = cv2.getRotationMatrix2D((cols / 2, rows / 2), angle, 1)
img_rot = cv2.warpAffine(img, M, (cols, rows))
# rotate bounding box
rect0 = (rect[0], rect[1], 0.0)
box = cv2.boxPoints(rect)
pts = np.int0(cv2.transform(np.array([box]), M))[0]
pts[pts < 0] = 0
# crop
img_crop = img_rot[pts[1][1]:pts[0][1],
pts[1][0]:pts[2][0]]
return img_crop
cropped = crop_minAreaRect(thresholded_img, min_area_rect)
plot(cropped)
How can I get the correct cropping?
After some research, this is what I get:
This is how I get it:
pad the original image on each side (500 pixels in my case)
find the four corner points of the shoe (the four points should form a polygon enclosing the shoe, but do not need to be exact rectangle)
employing the code here to crop the shoe:
img = cv2.imread("padded_shoe.jpg")
# four corner points for padded shoe
cnt = np.array([
[[313, 794]],
[[727, 384]],
[[1604, 1022]],
[[1304, 1444]]
])
print("shape of cnt: {}".format(cnt.shape))
rect = cv2.minAreaRect(cnt)
print("rect: {}".format(rect))
box = cv2.boxPoints(rect)
box = np.int0(box)
width = int(rect[1][0])
height = int(rect[1][1])
src_pts = box.astype("float32")
dst_pts = np.array([[0, height-1],
[0, 0],
[width-1, 0],
[width-1, height-1]], dtype="float32")
M = cv2.getPerspectiveTransform(src_pts, dst_pts)
warped = cv2.warpPerspective(img, M, (width, height))
Cheers, hope it helps.