OpenCV - Too many circles detected - python

Aim
Detect moon craters and draw circles on them using OpenCV and Tkinter
(This is for a school science competition - ideally, later craters will be highlighted red and areas between them that are above a certain size will be highlighted)
Problem
Too many circles are detected
The Need
Could someone please suggest what steps may be being missed, as plainly as possible. Or provide comment on actionable steps to take from here.
Additional challenge
This project is being undertaken by my 10yo son. He's working hard on this but I am not able to properly support this - it's outside of my expertise.
(I have asked on this previously on his behalf, but didn't have enough information and it has changed somewhat since. I have had him rewrite this query a number of times so he learns that the better information he provides, the better help he can receive. So please bear in mind, as simply as answers can be given, the better.)
More Detail
Using OpenCV in Tkinter, too many circles are detected. Despite looking through many articles, and tutorials, it has not been solved. I'm wondering if this is missing a step such as thresholding, or something similar. I'll be passing the replies to him, and helping through them if need be, as well.
Thanks in advance!
Images
Original Image
Processed
Notes
The aim: (In gui):
loads an image
finds the circles in the image
What I am doing(In the code)
1.Defines "Open"
2. In "Open" find image/file then load it up
3. Defines "Find Craters"
4. In find craters finds a circle with 'Min radius = 20 Max radius = 200'
5. Turns all the functions in to buttons
What I am trying to do
Blur the Image
Identify circles
Code
import tkinter as tk
from tkinter import filedialog
from PIL import ImageTk, Image
import numpy as np
import cv2
root = tk.Tk()
root.title("AAMLS")
root.geometry("1100x600")
def open():
global my_image
filename = filedialog.askopenfilename(initialdir="images", title="Select A File", filetypes=(("jpg files", "*.jpg"),("all files", "*.*")))
my_label.config(text=filename)
my_image = Image.open(filename)
tkimg = ImageTk.PhotoImage(my_image)
my_image_label.config(image=tkimg)
my_image_label.image = tkimg # save a reference of the image
def find_craters():
global image
global my_image_label
# convert PIL image to OpenCV image
circles_image = np.array(my_image.convert('RGB'))
gray_img = cv2.cvtColor(circles_image, cv2.COLOR_BGR2GRAY)
img = cv2.GaussianBlur(gray_img,(5,5),0)
circles = cv2.HoughCircles(img, cv2.HOUGH_GRADIENT, 1, 20,
param1=20, param2=60, minRadius=20, maxRadius=200)
if circles is not None:
circles = np.uint16(np.around(circles))
for i in circles[0]:
# draw the outer circle
cv2.circle(circles_image, (i[0],i[1]), i[2], (0,255,0), 2)
# draw the center of the circle
cv2.circle(circles_image, (i[0],i[1]), 2, (0,0,255), 3)
#circles_img = cv2.Laplacian(circles_image,cv2.CV_64F)
# convert OpenCV image back to PIL image
image = Image.fromarray(circles_image)
# update shown image
my_image_label.image.paste(image)
btn1 = tk.Button(root, text="Load Terrain", command=open).pack()
btn2 = tk.Button(root, text="Find Craters", command=find_craters).pack()
# for the filename of selected image
my_label = tk.Label(root)
my_label.pack()
# for showing the selected image
my_image_label = tk.Label(root)
my_image_label.pack()
root.mainloop()

To reduce the number of circles, there are essentially these parameters in HoughCircles:
min_dist: increase this value to find circles with a larger distance
maxRadius: reduce this parameter to avoid circles that contain more than one crater
param1: higher values make the edge detector less sensitive; only clear cut edges will be used (set param2 always to 3*param1)
You'll need to play around with these parameters to find a good setting. As a next step, I would do:
circles = cv2.HoughCircles(img, cv2.HOUGH_GRADIENT, 1, 30,
param1=40, param2=120, minRadius=20, maxRadius=80)
If then the circles are too few, relax the conditions to get more circles.

Related

Why is detection rate of Charucos in cv2.aruco.detectMarkers() so poor?

I am in trouble figuring out why cv2.aruco.detectMarkers() has problems in finding more than just a few markers with my calibration board. Playing around with the paramters didn't essentially improve the quality. The dictionary is correct as I tried it with the digital template before printing.
Here is, what I do to detect CHAruco markers from a real image:
import cv2
from cv2 import aruco
#ChAruco board variables
CHARUCOBOARD_ROWCOUNT = 26
CHARUCOBOARD_COLCOUNT = 26
ARUCO_DICT = cv2.aruco.Dictionary_get(aruco.DICT_4X4_1000)
#Create constants to be passed into OpenCV and Aruco methods
CHARUCO_BOARD = aruco.CharucoBoard_create(
squaresX=CHARUCOBOARD_COLCOUNT,
squaresY=CHARUCOBOARD_ROWCOUNT,
squareLength=5, #mm
markerLength=4, #mm
dictionary=ARUCO_DICT)
#load image
img = cv2.imread('imgs\\frame25_crop.png', 1)
test image with CHAruco markers
#initialize detector
parameters = aruco.DetectorParameters_create()
parameters.adaptiveThreshWinSizeMin = 150
parameters.adaptiveThreshWinSizeMax = 186
#Find aruco markers in the query image
corners, ids, _ = aruco.detectMarkers(
image=img,
dictionary=ARUCO_DICT,
parameters=parameters)
#Outline the ChAruco markers found in our image
img = aruco.drawDetectedMarkers(
image=img,
corners=corners)
The result is the following: only 3 are markers are found, which is bad.
resulting image with found markers
Does anyone has an idea how to considerably improve the results of the detector?
Your image is flipped.
Fix it with this line of code:
img = cv2.flip(img, 0)
Without looking at your code I may say that the image quality and perspective you selected is a bit poor. You may try to work with more clear view of your markers. For instance, hang the markers on the wall take one or two step back and try to take photo of it with better light and if not necessary do not add extra rotation, and keep the contrast high :). This will probably give better results.

How can I get the width and height of a text that was Image.Draw-drawn on a picture?

enter image description hereThere are already a few questions, that sound quite similar to this one on stackoverflow, but their focuses are a little bit different from what I want to know.
I pasted a text on an image using ImageDraw from the Python Image Library. My question now is: Is there a way to find out the width and height of the text as a whole? For myself imagine the text to have kind of a rectangular frame. I want to get the measurements of this text-field. Can my text1 be treated like an image and can I get its size by typing in something like "width, height = text1.size"?
I have already tried to get the width, by using a ImageDraw.getwidth command, as proposed in other questions, but those solutions sadly didn't work for me.
What I want to achieve in particular is to be able to calculate, where the next text-field can be placed without overlaying the current text.
Thanks in advance for your support!
In the attached picture you can see the two ImageDraw-drawed texts, named "text" and "xyz". I want xyz to fit behind "test", even if "test" would include more letters. Therefor I need to know the width of "test".
You can just write the same text with the same font of the same size in white on a separate canvas (black background) and then get the trimmed "bounding box" like this which is the size:
#!/usr/bin/env python3
from PIL import Image, ImageFont, ImageDraw
# Create a blank canvas
canvas = Image.new('RGB', (100,100))
# Get a drawing context
fontsize=28
draw = ImageDraw.Draw(canvas)
monospace = ImageFont.truetype("/Library/Fonts/Andale Mono.ttf",fontsize)
text = "Hello"
white = (255,255,255)
draw.text((10, 10), text, font=monospace, fill=white)
# Find bounding box
bbox = canvas.getbbox()
# Debug
print(bbox)
Output
(12, 17, 93, 36)
That means the text is (93-12) = 81 pixels wide and (36-17) = 19 pixels tall.
using opencv or matplotlib to read your image
img = cv2.cvtColor(cv2.imread('/home/ksooklall/q0MXd.jpg'), cv2.COLOR_BGR2RGB)
find the image location
text1 = img[950:, 50:250]
print(text1.shape)
(50, 200, 3)

How to deal with bright reflections in paper sheets detection in Python

I'm making a document scanner for a college project, my code work quite well for any of the uniform lighted images. However I came across issues detecting images with even a little amount of light reflections (or too much light) on the background surface.
I first tried different simple codes I found online, then using different morphological operation, with the result that now my code is a little messy and inaccurate.
Here's the code:
def scanner(img):
clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8,8))
image = cv2.imread(img)
original = image.copy()
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
contrast = clahe.apply(gray)
blurred = cv2.medianBlur(contrast, 21)
canny = cv2.Canny(blurred, 0, 70)
dialated = cv2.dilate(canny, cv2.getStructuringElement(cv2.MORPH_RECT,(5,5)), iterations = 3)
closing = cv2.morphologyEx(dialated, cv2.MORPH_CLOSE, np.ones((5,5),np.uint8),iterations = 10)
contimage, contours, hierarchy = cv2.findContours(closing, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
contours = sorted(contours, key = cv2.contourArea, reverse = True)[:5]
target = None
for c in contours:
p = cv2.arcLength(c, True)
approx = cv2.approxPolyDP(c, 0.09 * p, True)
if len(approx) == 4:
target = approx
cv2.drawContours(image, [target], -1, (0, 255, 0), 2)
break
plt.figure(figsize = (20,20))
plt.imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
plt.title("final")
plt.show()
Here's an example of the code working:
input1, output1, input2, output2
And not working:
input1, output1, input2, output2
This image shows a successful segmentation:
This image shows a failed segmentation:
It looks like the main issue here is that in the failing images, there is not a sufficient contrast between the paper and the table it's placed on. the ones that work it's basically white paper on dark background, so it's relatively easy to segment out the paper, however when you place the paper on a light colored surface, there isn't sufficient contrast between the paper and the background to tell what is what. Unfortunately in image processing, there is only so much you can do when your input image is bad so there is no easy automated fix for this but I can think of a few workarounds, but they're all going to require extra work.
One would be instead of having your program automatically detect where the paper is, just have a static box that the user has to place the document inside of and simply capture the contents that way. Probably the most simple to implement however it seems like you WANT to detect it automatically so this probably isn't what you're looking for.
Two would be to have some intermediate step allowing the user to select a specific threshold value to apply to the image. Basically you would take the picture, then have the user set a threshold value such that the paper ends up being white, and the background is dark, then you could use that as a template to create the boundary of the paper which you can then segment from the original image. This is probably the most work but closest to what you're looking for.
Three would be similar to number one but instead of having a set area you place the documents within, you could take the picture then have the user manually select where the corners are and segment it that way, more work than #1, less work than #2, but probably still not what you're looking for.
Finally you could just leave it as is and use it knowing that you need a sufficiently dark background for it to work correctly. There are probably some other work arounds but with a lot of image processing stuff you can be very constrained by the quality of your image and there isn't always a software solution for exactly what you're looking to do.

Overwriting a border on an image in Python PIL

I have a gallery application where the users upload photos and my code gives it a border, writes some of the photo attributes on the border and stores it.
image2 = Image.open('media/' + str(image.file))
width, height = image2.size;
image2 = ImageOps.expand(image2, border=(int(width/25),int(height/20),int(width/25),int(height/10)), fill='rgb(0,0,0)')
(Note that here my bottom border is longer than the top because I am writing attributes on the bottom border.)
Now I'm building an edit feature for the uploaded images where the user can change the attributes of the uploaded images. But the attributes that are already written on the border have to be overwritten.
So here, my approach is to put a black patch on the bottom border and re-write the new attributes without changes the top and side borders and without changing the aspect ratio. All of this has to be done using PIL.
Question is how do I put a black box on the bottom border?
I tried ImageOps.fit() as mentioned here https://pillow.readthedocs.io/en/3.3.x/reference/ImageOps.html#PIL.ImageOps.fit, but the aspect ratio doesn't seem to be right and I want to overwrite on the black border a black box and not crop the photo.
To me it seems like the easiest solution is just quickly draw the black pixels in the area that you want using a couple loops and Image.putpixel
from PIL import Image
img = Image.open('red.png')
for x in range(img.width):
for y in range(img.height - 40, img.height):
img.putpixel((x, y), (0, 0, 0))
img.save('red2.png')
The simplest way in my opinion is to create a new black image and paste onto your existing image -
from PIL import Image
im = Image.open('test.png')
blackBox = Image.new(im.mode, (im.width, 50), '#000')
im.paste(blackBox, (0, im.height - blackBox.height))
Alternatively, you could use ImageDraw - http://pillow.readthedocs.io/en/5.2.x/reference/ImageDraw.html - which you could use to draw rectangles and other shapes.
from PIL import Image, ImageDraw
im = Image.open('test.png')
d = ImageDraw.Draw(im)
d.rectangle((0, im.height - 50, im.width, im.height), fill='#000')

Trying to improve my road segmentation program in OpenCV

I am trying to make a program that is capable of identifying a road in a scene and proceeded to using morphological filtering and the watershed algorithm. However the program produces either mediocre or bad results. It seems to do okay (not good enough through) if the road takes up most of the scene. However in other pictures, it turns out that the sky gets segmented instead (watershed with the clouds).
I tried to see if I can preform more image processing to improve the results, but this is the best I have so far and don't know how to move forward to improve my program.
How can I improve my program?
Code:
import numpy as np
import cv2
from matplotlib import pyplot as plt
import imutils
def invert_img(img):
img = (255-img)
return img
#img = cv2.imread('images/coins_clustered.jpg')
img = cv2.imread('images/road_4.jpg')
img = imutils.resize(img, height = 300)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
thresh = invert_img(thresh)
# noise removal
kernel = np.ones((3,3), np.uint8)
opening = cv2.morphologyEx(thresh,cv2.MORPH_OPEN,kernel, iterations = 4)
# sure background area
sure_bg = cv2.dilate(opening,kernel,iterations=3)
#sure_bg = cv2.morphologyEx(sure_bg, cv2.MORPH_TOPHAT, kernel)
# Finding sure foreground area
dist_transform = cv2.distanceTransform(opening, cv2.DIST_L2, 5)
ret, sure_fg = cv2.threshold(dist_transform,0.7*dist_transform.max(),255,0)
# Finding unknown region
sure_fg = np.uint8(sure_fg)
unknown = cv2.subtract(sure_bg,sure_fg)
# Marker labelling
ret, markers = cv2.connectedComponents(sure_fg)
# Add one to all labels so that sure background is not 0, but 1
markers = markers+1
# Now, mark the region of unknown with zero
markers[unknown==255] = 0
'''
imgray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
imgray = cv2.GaussianBlur(imgray, (5, 5), 0)
img = cv2.Canny(imgray,200,500)
'''
markers = cv2.watershed(img,markers)
img[markers == -1] = [255,0,0]
cv2.imshow('background',sure_bg)
cv2.imshow('foreground',sure_fg)
cv2.imshow('threshold',thresh)
cv2.imshow('result',img)
cv2.waitKey(0)
For start, segmentation problems are hard. The more general you want the solution to be, the more hard it gets. Road segemntation is a well-known problem, and i'm sure you can find many papers which tackle this issue from various directions.
Something that helps me get ideas for computer vision problems is trying to think what makes it so easy for me to detect it and so hard for computer.
For example, let's look on the road on your images. What makes it unique from the background?
Distinct gray color.
Always have 2 shoulders lines in white color
Always on the bottom section of the image
Always have a seperation line in the middle (yellow/white)
Pretty smooth
Wider on the bottom and vanishing into horizon.
Now, after we have found some unique features, we need to find ways to quantify them, so it will be obvious to the algorithm as it is obvious to us.
Work on the RGB (or even better - HSV) image, don't convert it to gray on the beginning and lose all the color data. Look for gray area!
Again, let's find white regions (inside gray ones). You can try do edge detection in the specific orientation of the shoulders line. You are looking for line that takes about half of the height of the image. etc...
Lets delete the upper half of the image. It is hardly that you ever have there a road, and you will get rid from a lot of noise in your algorithm.
see 2...
Lets check the local standard deviation, or some other smoothness feature.
If we found some shape, lets check if it fits what we expect.
I know these are just ideas and I don't claim they are easy to implement, but if you want to improve your algorithm you must give it more "knowledge", just as you have.
Exploit some domain knowledge; in other words, make some simplifying assumptions. Even basic things like "the camera's not upside down" and "the pavement has a uniform hue" will improve the common case.
If you can treat crossroads as a special case, then finding the edges of the roadway may be a simpler and more useful task than finding the roadway itself.

Categories