Opencv Corner Detection - python

I have an image like this:
When i try to use any of the corner detection algorithms i get corners like this:
however i want to corners of the rectangle.
How can i get rid of those corners that i do not want.
and here is my code
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread("/home/mkmeral/Desktop/opencv/cropped.png")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
corners = cv2.goodFeaturesToTrack(gray,4,0.01,10)
corners = np.int0(corners)
for i in corners:
x,y = i.ravel()
cv2.circle(img,(x,y),3,255,-1)
plt.subplot(121),plt.imshow(img)
plt.title('2'), plt.xticks([]), plt.yticks([])
plt.suptitle("CORNERS")
cv2.imwrite("/home/mkmeral/Desktop/opencv/corners.png", img)
plt.show()
Here is the whole image, i cropped the image to make it smaller.
This is where i need corners to be:

As your image is of rather poor resolution and resemblance to a rectangle, any solution is going to be somewhat in the beholder's eye.
You can do this with OpenCV but I am just explaining the method using ImageMagick at the commandline. So, threshold the image to black and white at 50% and then do a "Blob Analysis" or "Connected Components Analysis".
convert rect.png -threshold 50% \
-define connected-components:verbose=true \
-connected-components 8 result.png
Output
Objects (id: bounding-box centroid area mean-color):
0: 160x120+0+0 78.0,58.7 18551 srgb(0,0,0)
1: 52x50+97+58 121.8,82.6 649 srgb(255,255,255)
So, if we look at the last line we have an object 52x50 pixels with area 649 pixels and colour white - that is your shape - or burning cigarette as I think of it! Let's draw it in:
convert rect.png -stroke red -fill none -draw "rectangle 97,58 148,107" y.png
Now, if it is a rectangle as you say, it will have a length roughly equal to the diagonal of the enclosing box, so
L = sqrt(52*52 + 50*50) = 72
and its area is 649, so its width is around 9 pixels and it starts at +97+58 from the top-left corner. Or its centroid is at 121.8,82.6. So, all that's needed is a little schoolboy geometry to get your corner points.

I found that by adjusting the arguments passed to cv2.cornerHarris() I could get corners correctly identified.
E.g. given this input image:
We can capture corners with the following (note the arguments passed to cornerHarris():
import cv2
import numpy as np
from matplotlib import pyplot as plt
img_file = 'a.jpg'
img = cv2.imread(img_file, 0)
img = np.float32(img)
'''args:
img - Input image, it should be grayscale and float32 type.
blockSize - It is the size of neighbourhood considered for corner detection
ksize - Aperture parameter of Sobel derivative used.
k - Harris detector free parameter in the equation.
'''
corners = cv2.cornerHarris(img, 4, 3, 0.04)
corners2 = cv2.dilate(corners, None, iterations=3)
img2 = cv2.imread(img_file)
img2[corners2>0.01*corners2.max()] = [255,0,0]
plt.subplot(2, 1, 2)
plt.imshow(img2, cmap = 'gray')
plt.title('Canny Edge Detection')
plt.xticks([])
plt.yticks([])
plt.show()
Output:

Related

How to properly convert image between Cartesian and polar coordinate systems using `cv2.warpPolar`?

How to convert images from Cartesian coordinate system to polar coordinate system and back, using cv2.warpPolar method, without cropping the view therefore losing details?
I observed that for images that are not perfect squares, in the resultant polar system, lines that are parallel to x-axis will become circles instead of ellipses, so a fair chunk of the image will be out of view, therefore information is lost.
I want the circles to become ellipses of the same aspect ratio as the original image so that all of the converted image is squeezed into the view and no information is lost.
For example, this produces a test image:
import numpy as np
import cv2
img = np.zeros(shape=(1080, 1920, 3), dtype=np.uint8)
img[:, :, 0] = np.linspace(0, 255, 1920, dtype=np.uint8)[np.newaxis, :]
img[:, :, 2] = np.linspace(0, 255, 1080, dtype=np.uint8)[:, np.newaxis]
img[0:180, 0:320, 1] = 255
img[900:1080, 0:320, 1] = 255
img[900:1080, 1600:1920, 1] = 255
img[0:180, 1600:1920, 1] = 255
cv2.imshow('test image', img); cv2.waitKey(0)
cv2.imwrite('D:/test_image.jpg', img)
This warps the test image to polar coordinates:
r = (1920*1920+1080*1080)**.5/2
polar = cv2.warpPolar(img, dsize=(1920, 1080), center=(960, 540), maxRadius=r, flags=cv2.WARP_INVERSE_MAP)
cv2.imshow('polar image', polar); cv2.waitKey(0)
cv2.imwrite('D:/polar_test_image.jpg', polar)
And this warps it back to Cartesian:
linear = cv2.warpPolar(polar, dsize=(1920, 1080), center=(960, 540), maxRadius=r, flags=cv2.WARP_POLAR_LINEAR)
cv2.imshow('cartesian image', linear); cv2.waitKey(0)
cv2.imwrite('D:/cartesian_test_image.jpg', linear)
But what I want is this:
The above image is converted using PhotoShop CS6.
And warped back by PhotoShop CS6:
How do I generate the same results as PhotoShop?
I thought I was clear enough but you didn't get it.
I want the warped image to not be a perfect square, it should have exactly the same resolution and aspect ratio as the input image instead.
And there should be no extra black portions. Just like the effect in PhotoShop.
The picture you "want" is easily achieved by rotating the input by 90 degrees. You want the green and cyan squares in the center? Then rotate counterclockwise by 90 degrees, so they're on the left side, before warping. Then they'll be in the center.
You have to make sure a circle of the given radius fits in the dimensions you specify in dsize.
Use dsize=(2*r,2*r) and center accordingly.
Either that or you have to use a different radius value.

How to eliminate the edges surrounding the differences in the SSIM image output of compare_ssim from skimage.measure

I am trying to write a code that calculates the area occupied by the tracks on a microscope image, as this:
As the tracks aren't uniform (I mean, they don't have a unique grey level as they are darker in the edge and lighter in the centre), I can't do this just by comparing their grey level with the grey level of the background because in some parts of the tracks it is the same.
Therefore, what I tried is to compare the image with an image of the background:
I do this in order to extract the differences between these two images (which correspond to the tracks themselves). In order to do this, I have used the compare_ssim function from skimage. The code I've used is the following:
from skimage.measure import compare_ssim
import imutils
import cv2
import numpy as np
# load the two input images
imageA = cv2.imread("./b.jpg")
imageB = cv2.imread("./a.jpg")
# convert the images to grayscale
grayA = cv2.cvtColor(imageA, cv2.COLOR_BGR2GRAY)
grayB = cv2.cvtColor(imageB, cv2.COLOR_BGR2GRAY)
# compute the Structural Similarity Index (SSIM) between the two
# images, ensuring that the difference image is returned
(score, diff) = compare_ssim(grayA, grayB, full=True)
diff = (diff * 255).astype("uint8")
# show the diff image
cv2.imshow("Diff", diff)
The diff image I obtain is the following:
This is quite good, because now the grey level in the tracks is well-differentiated from the grey level of the background, and I can calculate the area occupied by the tracks (there are some points in the track that are lighter, but they are few and it's okay for my purpose).
Nevertheless, the problem is that in the diff image not only the tracks appear in black, but also a thick border surrounding the tracks:
This edge makes my area estimation incorrect.
So I would like to know how I can eliminate that edge, or at least make it thinner.
If this is not possible, it would be really helpful if you can show me another Python function that can achieve my purpose of calculating the area occupied by the tracks.
There is always a better way to do the same thing, but I used here a simple approach, which you can improve or tune later according to your needs:
Algorithm:
Firstly, proper thresholding will keep only the edges
Secondly, Morphology (dilation, or erosion depending on your thresholding approach) will thin your Edges.
At the end, to get rid of everything, except your tracks, you can use Flood_Fill algorithm, then count the white pixels to get your area(in pixels).
Results:
In the Final result:
The count of the white of pixels is: 52219
Code:
#========================
# Import Libraries
#========================
import numpy as np
import matplotlib.pyplot as plt
import cv2
from skimage.morphology import flood_fill
#========================
# Read images
#========================
img = cv2.imread('1.png',0)
bck = cv2.imread('2.png',0)
#========================
# Gaussian Blur
#========================
gauss = cv2.GaussianBlur(img, (5,5), 1)
bck_gauss = cv2.GaussianBlur(bck, (5,5), 1)
#========================
# Thresholding
#========================
_,thresh1 = cv2.threshold(gauss,127,255,cv2.THRESH_BINARY)
_,bck_th = cv2.threshold(bck_gauss,127,255,cv2.THRESH_BINARY_INV)
# Get rid of black borders
thresh1 = thresh1 + bck_th
#========================
# Morphology
#========================
kernel = np.zeros((7,7),np.uint8)
kernel[2,:] = 1
kernel[:,2] = 1
dilation = cv2.dilate(thresh1,kernel,iterations = 1)
#========================
# Flood Fill
#========================
res = flood_fill(dilation,(1,1), 0)
print("The count of the white of pixels is: ", int(np.sum(res)/np.max(res)))
#========================
# Visualize Results
#========================
plt.figure(num='Blobs')
plt.subplot(221)
plt.imshow(img, cmap='gray')
plt.title('Original')
plt.axis('off')
plt.subplot(222)
plt.imshow(thresh1, cmap='gray')
plt.title('Thresholded')
plt.axis('off')
plt.subplot(223)
plt.imshow(dilation, cmap='gray')
plt.title('Morphology')
plt.axis('off')
plt.subplot(224)
plt.imshow(res, cmap='gray')
plt.title('Final Result')
plt.axis('off')
plt.show()

How to plot centroids on image after kmeans clustering?

I have a color image and wanted to do k-means clustering on it using OpenCV.
This is the image on which I wanted to do k-means clustering.
This is my code:
import numpy as np
import cv2
import matplotlib.pyplot as plt
image1 = cv2.imread("./triangle.jpg", 0)
Z1 = image1.reshape((-1))
Z1 = np.float32(Z1)
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)
K1 = 2
ret, mask, center =cv2.kmeans(Z1,K1,None,criteria,10,cv2.KMEANS_RANDOM_CENTERS)
center = np.uint8(center)
print(center)
res_image1 = center[mask.flatten()]
clustered_image1 = res_image1.reshape((image1.shape))
for c in center:
plt.hlines(c, xmin=0, xmax=max(clustered_image1.shape[0], clustered_image1.shape[1]), lw=1.)
plt.imshow(clustered_image1)
plt.show()
This is what I get from the center variable.
[[112]
[255]]
This is the output image
My problem is that I'm unable to understand the output. I have two lists in the center variable because I wanted two classes. But why do they have only one value?
Shouldn't it be something like this (which makes sense because centroids should be points):
[[x1, y1]
[x2, y2]]
instead of this:
[[x]
[y]]
and if I read the image as a color image like this:
image1 = cv2.imread("./triangle.jpg")
Z1 = image1.reshape((-1, 3))
I get this output:
[[255 255 255]
[ 89 173 1]]
Color image output
Can someone explain to me how I can get 2d points instead of lines? Also, how do I interpret the output I got from the center variable when using the color image?
Please let me know if I'm unclear anywhere. Thanks!!
K-Means-clustering finds clusters of similar values. Your input is an array of color values, hence you find the colors that describe the 2 clusters. [255 255 255] is the white color, [ 89 173 1] is the green color. Similar for [112] and [255] in the grayscale version. What you're doing is color quantization
They are correctly the centroids, but their dimension is color, not location. Therefor you cannot plot it anywhere. Well you can, but I looks like this:
See how the 'color location' determines to which class each pixel belongs?
This is not something you can locate in your image. What you can do is find the pixels that belong to the different clusters, and use the locations of the found pixels to determine their centroid or 'average' position.
To get the 'average' position of each color, you have to separate out the pixel coordinates according to the class/color to which they belong. In the code below I used np.where( img <= 240) where 240 is the threshold. I used 240 out of ease, but you could use K-Means to determine where the threshold should be. (inRange() might be useful at some point)) If you sum the coordinates and divide that by the number of pixels found, you'll have what I think you are looking for:
Result:
Code:
import cv2
# load image as grayscale
img = cv2.imread('D21VU.jpg',0)
# get the positions of all pixels that are not full white (= triangle)
triangle_px = np.where( img <= 240)
# dividing the sum of the values by the number of pixels
# to get the average location
ty = int(sum(triangle_px[0])/len(triangle_px[0]))
tx = int(sum(triangle_px[1])/len(triangle_px[1]))
# print location and draw filled black circle
print("Triangle ({},{})".format(tx,ty))
cv2.circle(img, (tx,ty), 10,(0), -1)
# the same process, but now with only white pixels
white_px = np.where( img > 240)
wy = int(sum(white_px[0])/len(white_px[0]))
wx = int(sum(white_px[1])/len(white_px[1]))
# print location and draw white filled circle
print("White: ({},{})".format(wx,wy))
cv2.circle(img, (wx,wy), 10,(255), -1)
# display result
cv2.imshow('Result',img)
cv2.waitKey(0)
cv2.destroyAllWindows()
Here is an Imagemagick solution, since I am not proficient with OpenCV.
Basically, I convert your actual image (from your link in the comments) to binary, then use image moments to extract the centroid and other statistics.
I suspect you can do something similar in OpenCV, Skimage, or Python Wand, which is based upon Imagemagick. (See for example:
https://docs.opencv.org/3.4/d3/dc0/group__imgproc__shape.html#ga556a180f43cab22649c23ada36a8a139
https://scikit-image.org/docs/dev/api/skimage.measure.html#skimage.measure.moments_coords_central
https://en.wikipedia.org/wiki/Image_moment)
Input:
Your image does not have just two colors. Perhaps this image did not have kmeans clustering applied with 2 colors only. So I will do that with an Imagemagick script that I have built.
kmeans -n 2 -m 5 img.png img2.png
final colors:
count,hexcolor
99234,#65345DFF
36926,#27AD0EFF
Then I convert the two colors to black and white by simply thresholding and stretching the dynamic range to full black and white.
convert img2.png -threshold 50% -auto-level img3.png
Then I get all the image moment statistics for the white pixels, which includes the x,y centroid in pixels relative to the top left corner of the image. It also includes the equivalent ellipse major and minor axes, angle of major axis, eccentricity of the ellipse, and equivalent brightness of the ellipse, plus the 8 Hu image moments.
identify -verbose -moments img3.png
Channel moments:
Gray:
--> Centroid: 208.523,196.302 <--
Ellipse Semi-Major/Minor axis: 170.99,164.34
Ellipse angle: 140.853
Ellipse eccentricity: 0.197209
Ellipse intensity: 106.661 (0.41828)
I1: 0.00149333 (0.380798)
I2: 3.50537e-09 (0.000227937)
I3: 2.10942e-10 (0.00349771)
I4: 7.75424e-13 (1.28576e-05)
I5: 9.78445e-24 (2.69016e-09)
I6: -4.20164e-17 (-1.77656e-07)
I7: 1.61745e-24 (4.44704e-10)
I8: 9.25127e-18 (3.91167e-08)

How to mark the center and calculate the diameter of an image using opencv - python?

I am working on the recognition of the center and the image rendering. I'm using cv2.findContours to delimit the edges of the image of interest. And using cv.minEnclosingCircle (cnt) to circumnavigate my region of interest. The code below I can identify the center of each ROI, but I am not able to mark in the output of the image the circle corresponding to the image that I want to calculate and also I want to mark with a pqno point the exact location where the algorithm identified the center.
import cv2
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.offsetbox import AnchoredText
thresh = cv2.imread('IMD044.png',0)
_, contours,hierarchy = cv2.findContours(thresh,2,1)
print (len(contours))
cnt = contours
for i in range (len(cnt)):
(x,y),radius = cv2.minEnclosingCircle(cnt[i])
center = (int(x),int(y))
radius = int(radius)
cv2.circle(thresh,center,radius,(0,255,0),2)
print ('Circle: ' + str(i) + ' - Center: ' + str(center) + ' - Radius: ' + str(radius))
plt.text(x-15, y+10, '+', fontsize=25, color = 'red')
plt.text(10, -10, 'Centro: '+str(center), fontsize=11, color = 'red')
plt.text(340, -10, 'Diametro: '+str((radius*2)/100)+'mm', fontsize=11, color = 'red')
plt.Circle(x, y, color='red', fill=False)
plt.imshow(thresh, cmap='gray')
plt.show()
I used the Opencv documentation to demarcate the contours and get the regions, but the green circle mark does not appear.
Exit:
Exit expected:
updating the question I was able to add the information, it's only necessary to add the circle and check if the diameter is correct.
You are using a single channel image, but trying to show 3-channel color. Add the following:
...
thresh = cv2.imread('IMD016.png',0)
_, contours,hierarchy = cv2.findContours(thresh,2,1)
thresh = cv2.cvtColor(thresh, cv2.COLOR_GRAY2RGB)
print (len(contours))
...
Also, be extra careful when mixing the OpenCV and PyPlot drawing routines. OpenCV uses BGR by default, while PyPlot uses RGB. Also, cv2 drawing routines don't add extra channels, while the plt does. Just need to keep that in mind

opencv findContours miss some area.[ not get all correct bounding boxes ]

I am new to opencv, start to learn it by extract char from simple captcha.
After some effort, I got findContours and some method to clean the image, sometimes worked, but not more often.
For example:
I have a original image(already scale to a large size):
convert to grayscale and use cv2.threshold clean:
use cv2.findContours to get bounding boxes:
W only cover a half, and not get b.
My code:
from StringIO import StringIO
import string
from PIL import Image
import requests
import cv2
import numpy as np
import matplotlib.pyplot as plt
def get_ysdm_captcha():
url = 'http://www.ysdm.net/common/CleintCaptcha'
r = requests.get(url)
img = Image.open(StringIO(r.content))
return img
def scale_image(img, ratio):
return img.resize((int(img.width*ratio), int(img.height*ratio)))
def draw_rect(im):
im = np.array(im)
if len(im.shape) == 3 and im.shape[2] == 3:
imgray = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
else:
imgray = im
#plt.imshow(Image.fromarray(imgray), 'gray')
pilimg = Image.fromarray(imgray)
ret,thresh = cv2.threshold(imgray,127,255,0)
threimg = Image.fromarray(thresh)
plt.figure(figsize=(4,3))
plt.imshow(threimg, 'gray')
plt.xticks([]), plt.yticks([])
contours, hierarchy = cv2.findContours(np.array(thresh),cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
areas = []
for c in contours:
rect = cv2.boundingRect(c)
area = cv2.contourArea(c)
areas.append(area)
x,y,w,h = rect
if area > 2000 or area < 200 : continue
cv2.rectangle(thresh,(x,y),(x+w,y+h),(0,255,0),1)
plt.figure(figsize=(1,1))
plt.imshow(threimg.crop((x,y,x+w,y+h)), 'gray')
plt.xticks([]), plt.yticks([])
plt.figure(figsize=(10,10))
plt.figure()
plt.imshow(Image.fromarray(thresh), 'gray')
plt.xticks([]), plt.yticks([])
image = get_ysdm_captcha()
im = scale_image(image, 3)
im = np.array(im)
imgray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
imgray = cv2.GaussianBlur(imgray,(5,5),0)
# im = cv2.medianBlur(imgray,9)
# im = cv2.bilateralFilter(imgray,9,75,75)
draw_rect(imgray)
I tried my best to write above code.
The solutions I imagine is:
find was there any way to tell cv2.findContours I need 4 bounding boxes in some size
tried some different parameter (I tried all from http://docs.opencv.org/2.4/modules/imgproc/doc/structural_analysis_and_shape_descriptors.html?highlight=findcontours#findcontours , but still not work)
Now I'm stuck , have no idea how to improve cv2.findContours...
You can use morphological operations to modify your image and fill the gaps, for example erode and dilate
See here:
http://docs.opencv.org/2.4/doc/tutorials/imgproc/erosion_dilatation/erosion_dilatation.html
Original:
Dilated:
By the way: I would implement a HSV separation step in the original image, removing all the 'white/grey/black' content (low saturation). This will reduce the number of specks. Do this before converting to grayscale.
Here is the result filtering on: saturation > 90
Final result: (Added a blur step before)
Also, if there always a gradient, you could detect this and filter out even more colors. But that is a bit much if you just started image processing ;)
findCountours works properly as it finds all connected component of your image. Your area condition is what is probably avoiding you to get a bounding box around letter b, for instance. Of course, if you put a bounding box around each connected component you will not end up with a bounding box around each character because you have many holes in your letters.
If you want to segment the letters, I would first try to play with opening operations (because your letters are black on a white background, it would be closing if it was the opposite) in order to fill the holes that you have in your letters. Then I would project vertically the pixels and analyze the shape that you get. If you find the valley points in this projected shape you will get the vertical limits between characters. You can do the same horizontally to get the upper and bottom limits of your chars. This approach will only work if the text is horizontal. If it is not, you should find the main axis angle of your string and you could rotate the image accordingly. To find the main axis angle you could fit an ellipse to your text and find its main axis angle or you could keep rotating your image by a certain angle until your horixontal projection is maximum.

Categories

Resources