Detecting line-line intersections in an image? - python-3.x

I have an image with some intersecting lines where I need to find the point of intersection. I am using cv2.goodFeaturesToTrack to find strong corners, and working on the assumption that intersections are 'strong' corners so they will get detected. But it's not a sure fire way of getting the intersection points of the two lines. Another approach is that I can get the equations of the lines and calculate the line-line intersection... or any other suggestion.
import matplotlib.pyplot as plt
import cv2
import numpy as np
img = cv2.imread('test_lines.png')
new = img.copy()
#invert
imagem = cv2.bitwise_not(img)
gray = cv2.cvtColor(imagem, cv2.COLOR_BGR2GRAY)
corners = cv2.goodFeaturesToTrack(gray, 4, 0.01, 10, blockSize = 5)
corners = np.int0(corners)
for i in corners:
import pdb; pdb.set_trace()
x, y = i.ravel()
cv2.circle(imagem, (x,y),3,255,-1)
plt.imshow(imagem)
cv2.imwrite('hough_img.png',imagem)

How to detect lines in OpenCV?
This answer was helpful in giving me some good results to begin working with. I followed the steps there to get the following result.

Related

problem on filing up the colour between two index values

I have a timeseries data timeseries.txt. First I select a index value (here 50) and put a red line mark on that selected index value. And I want to highlight portion before(idx-20) and after(idx+20) the red line index value on the timeseries.
I wrote this code however i am able to put the red line mark on the timeseries but while using fill_betweenx it doesnot work. I hope experts may help me overcoming this problem.Thanks.
import matplotlib.pyplot as plt
import numpy as np
input_data=np.loadtxt("timeseries.txt")
time=np.arange(len(input_data))
plt.plot(time,input_data)
idx = [50]
mark = [time[i] for i in idx]
plt.plot(idx,[input_data[i] for i in mark], marker="|",color='red',markerfacecolor='none',mew=0.4,ms=30,alpha=2.0)
plt.fill_betweenx(idx-20,idx+20 alpha=0.25,color='lightsteelblue')
plt.show()
If you are looking for just a semi-transparent rectangle, you can use patches.Rectangle to draw one. Refer here. I have updated your code to add a rectangle. See if this meets your requirement. I have used a sine wave as I didn't have your data.
import matplotlib.pyplot as plt
import numpy as np
## Create sine wave
x = np.arange(100)
input_data=np.sin(2*np.pi*3*x/100)
time=np.arange(len(input_data))
plt.plot(time,input_data)
idx = [50]
mark = [time[i] for i in idx]
plt.plot(idx,[input_data[i] for i in mark], marker="|", color='red', markerfacecolor='none', mew=0.4,ms=30,alpha=2.0)
#plt.fill_betweenx(mark,idx-20,0, alpha=0.25,color='lightsteelblue')
# Create a Rectangle patch
import matplotlib.patches as patches
from matplotlib.patches import Rectangle
plt.gca().add_patch(Rectangle((idx[0]-20, -0.15), 40, .3, facecolor = 'lightsteelblue',fill=True,alpha=0.25, lw=0))
plt.show()
EDIT
Please refer to the Rectangle documentation provided earlier in the response. You will need to adjust the start coordinates (x,y) and the height and width to see how big/small you need the Rectangle. For eg: changing the rectangle code like this...
plt.gca().add_patch(Rectangle((idx[0]-10, -0.40), 20, 0.8, facecolor = 'lightsteelblue',fill=True,alpha=0.25, lw=0))
will give you this plot.

Remove Freckles from Simple Binary Image

I have the following NumPy array of a running man, which you can download here:
https://drive.google.com/file/d/1SfIEqGsBV_vA7iP4UjLdklLJlLdDzozL/view?usp=sharing
To display it, use this code:
import numpy as np
import matplotlib.pyplot as plt
# load data
data = np.load('running_man.npy')
# plot data
plt.imshow(data)
As you can see there is a lot of noise (freckles) in the image. I would like to get rid of it and retrieve a clean image of the runner. Any idea of how to do it?
This is what I have done so far:
from skimage import measure
# Find contours at a constant value of 1
contours = measure.find_contours(data, 1, fully_connected='high')
# Select the largest contiguous contour
contour = sorted(contours, key=lambda x: len(x))[-1]
# Create an empty image to store the masked array
r_mask = np.zeros_like(data, dtype='bool')
# Create a contour image by using the contour coordinates rounded to their nearest integer value
r_mask[np.round(contour[:, 0]).astype('int'), np.round(contour[:, 1]).astype('int')] = 1
# Fill in the hole created by the contour boundary
r_mask = ndimage.binary_fill_holes(r_mask)
# Invert the mask since one wants pixels outside of the region
r_mask = ~r_mask
plt.imshow(r_mask)
... but as you can see the outline is very rough !
What works well is to upload the image to an online jpg to SVG converter -> this makes the lines super smooth. ... but I want to be able to do it in python.
Idea:
I am looking for something that can keep the sharp corners, maybe something that detects the gradient along the edge and only keeps the point where the gradient is above a certain threshold...
For this specific image you can just use numpy:
import numpy as np
import matplotlib.pyplot as plt
data = np.load('running_man.npy')
data[data > 1] = 0
plt.xticks([])
plt.yticks([])
plt.imshow(data)
For a method that preserves the corners better, we can use median filters, but force the preservation of corners.
Masked Image
Mask after filtering
Recolored
import cv2
import numpy as np
# load image
img = cv2.imread("run.png");
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY);
# make mask
_, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU);
# median filter
med = cv2.medianBlur(thresh, 11);
med[thresh == 255] = 255;
# inverse filter
mask = cv2.bitwise_not(med);
med = cv2.medianBlur(mask, 3);
med[mask == 255] = 255;
# recolor
color = np.zeros_like(img);
color[med == 0] = (66, 239, 245);
color[med == 255] = (92, 15, 75);
# show
cv2.imshow("colored", color);
cv2.waitKey(0);

How to connect the points from a contour of a binary image

I have a segmentation result stored in a binary image, from which i want to extract the contours. To do so, I compute the difference between the mask and the eroded mask. Hence, I am able to extract the pixels that are on the boundaries of my segmentation result. Here is a code snippet:
import numpy as np
from skimage.morphology import binary_erosion
from matplotlib import pyplot as plt
# mask is a 2D boolean np.array containing the segmentation result
contour_raw=np.logical_xor(mask,binary_erosion(mask))
contour_y,contour_x=np.where(contour_raw)
fig=plt.figure()
plt.imshow(mask)
plt.plot(contour_x,contour_y,'.r')
I end up with a collection of dots on the contours of the mask:
The troubles starts when I want to connect the dots. Doing a naive plot of the contours results of course in a disappointing results, because contour_x and contour_y are not sorted as I would like:
plt.plot(contour_x,contour_y,'--r')
And here is the result, with a focus on an arbitrary part of the figure to highlight the connection between the dots:
How is it possible to sort the contours coordinates contour_x and contour_y so that they are correctly ordered when I connect the dot? Furthermore, if my mask contains several independent connected component, I would like to obtain as many contours as there are connected components.
Thanks for your help!
Best,
I think combining a clustering and convex hull works in your case. For this example, I am generating three synthetic segments using make_blobs function and demonstrating each with a color:
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_blobs
from sklearn.cluster import DBSCAN
from scipy.spatial import ConvexHull, convex_hull_plot_2d
X, y = make_blobs(n_samples=1000, centers=3, n_features=2, random_state=0, cluster_std=0.3)
plt.scatter(X[:,0], X[:,1], c=y)
Then, since segments are distributed in a two dimensional map, we can run density based clustering method to cluster them, and then by finding a convex hull around each cluster, we can find points surrounding those clusters coming with order:
# Fitting Clustering
c_alg = DBSCAN()
c_alg.fit(X)
labels = c_alg.labels_
for i in range(0, max(labels)+1):
ind = np.where(labels == i)
segment = X[ind, :][0]
hull = ConvexHull(segment)
plt.plot(segment[:, 0], segment[:, 1], 'o')
for simplex in hull.simplices:
plt.plot(segment[simplex, 0], segment[simplex, 1], 'k-')
However in your case Concave Hull should work not Convex Hull. There is a package alphashape in python that claimed to find Concave hulls in two-dimensional maps. More information here. The tricky part is to find the best alpha, but in this example, we can fit concave hulls using:
import alphashape
from descartes import PolygonPatch
fig, ax = plt.subplots()
for i in range(0, max(labels)+1):
ind = np.where(labels == i)
points = X[ind, :][0,:,:]
alpha_shape = alphashape.alphashape(points,5.0)
ax.scatter(*zip(*points))
ax.add_patch(PolygonPatch(alpha_shape, alpha=0.5))
plt.show()

cv2.MORPH_CLOSE joining letters together

I'm using Opencv to do some morphological operations on an image:
but it joins some of the letters together creating problems when I detect it's contours. For example:
Is there some tweaking I can do wih my code to fix this or will I have to do this a different way?(but it has to be a closing algorithm or function because it is pretty helpful in preprocessing).
My code I am using is as below:
kernel = np.ones((5,5),np.uint8)
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)
closing = cv2.morphologyEx(opening, cv2.MORPH_CLOSE, kernel)
Here is workable solution:
import numpy as np
import cv2
from matplotlib import pyplot as plt
I = cv2.imread('/home/smile/Downloads/words.jpg',cv2.IMREAD_GRAYSCALE)
_,It = cv2.threshold(I,0.,255,cv2.THRESH_OTSU)
It = cv2.bitwise_not(It)
_,labels = cv2.connectedComponents(I)
result = np.zeros((I.shape[0],I.shape[1],3),np.uint8)
for i in range(labels.min(),labels.max()+1):
mask = cv2.compare(labels,i,cv2.CMP_EQ)
_,ctrs,_ = cv2.findContours(mask,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE)
result = cv2.drawContours(result,ctrs,-1,(0xFF,0,0))
plt.figure()
plt.imshow(result)
plt.axis('off')
During the two first steps the image is binarize and reverse in order to make the letters appear has white over black.
_,It = cv2.threshold(I,0.,255,cv2.THRESH_OTSU)
It = cv2.bitwise_not(It)
Then during the next step each letter become a labelized region.
_,labels = cv2.connectedComponents(I)
The final step consist for each label value to find the area in the image that correspond to it, process the external contour of that area and "draw" it in the output image.
result = np.zeros((I.shape[0],I.shape[1],3),np.uint8)
for i in range(labels.min(),labels.max()+1):
mask = cv2.compare(labels,i,cv2.CMP_EQ)
_,ctrs,_ = cv2.findContours(mask,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE)
result = cv2.drawContours(result,ctrs,-1,(0xFF,0,0)
Hope it helps.

Issue with drawparallels argument in Basemap

This seems like it should be an easy fix but I can't get it to work. I would like 40°N to display in the attached plot, but setting the labels argument in drawparallels to [1,0,1,1] isn't doing the trick. That should plot the parallels lables where they intersect the left, top and bottom of the plot according to the documentation. I would also like for 0° to once again show up in the bottom right corner. Any idea of how I can fix those 2 issues?
from netCDF4 import Dataset as NetCDFFile
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.basemap import Basemap
from mpl_toolkits.basemap import addcyclic
nc = NetCDFFile('C:/myfile.nc')
lat = nc.variables['lat'][:]
lon = nc.variables['lon'][:]
time = nc.variables['time'][:]
olr = nc.variables['olr'][:]
olr,lon = addcyclic(olr,lon)
map = Basemap(llcrnrlon=0.,llcrnrlat=-40.,urcrnrlon=360.,urcrnrlat=40.,resolution='l')
lons,lats = np.meshgrid(lon,lat)
x,y = map(lons,lats)
levels = np.arange(-19.5,20.0,0.5)
levels = levels[levels!=0]
ticks = np.arange(-20.0,20.0,4.0)
cs = map.contourf(x,y,olr[0],levels, cmap='bwr')
cbar = plt.colorbar(cs, orientation='horizontal', cmap='bwr', spacing='proportional', ticks=ticks)
cbar.set_label('Outgoing Longwave Radiation Anomalies $\mathregular{(W/m^2)}$')
map.drawcoastlines()
map.drawparallels(np.arange(-40,40,20),labels=[1,0,1,1], linewidth=0.5, fontsize=7)
map.drawmeridians(np.arange(0,360,40),labels=[1,1,0,1], linewidth=0.5, fontsize=7)
The first part of the question is easy. In order for the label to show up, you have to actually draw the parallel, but np.arange(-40,40,20) does not include 40. So, if you change that statement to np.arange(-40,41,20) your 40N label will show up.
The second part should in principle be solvable in the same way, but Basemap apparently uses the modulo of the longitudes to compute the position of the labels, so just using np.arange(0,361,40) when drawing the meridians will result in two 0 labels on top of each other. However, we can capture the labels that drawmeridians generates and manually change the position of the second 0 label. The labels are stored in a dictionary, so they are easy to deal with. To compute the x position of the last label, I compute the difference in x-position between the first and the second label, multiply that with the amount of meridians to be drawn (360/40) and add the x-position of the first label.
Here the complete example:
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.basemap import Basemap
map = Basemap(llcrnrlon=0.,llcrnrlat=-40.,urcrnrlon=360.,urcrnrlat=40.,resolution='l')
map.drawcoastlines()
yticks = map.drawparallels(
np.arange(-40,41,20),labels=[1,0,1,1], linewidth=0.5, fontsize=7
)
xticks = map.drawmeridians(
np.arange(0,361,40),labels=[1,1,0,1], linewidth=0.5, fontsize=7
)
first_pos = xticks[0][1][0].get_position()
second_pos = xticks[40][1][0].get_position()
last_x = first_pos[0]+(second_pos[0]-first_pos[0])*360/40
xticks[360][1][0].set_position((last_x,first_pos[1]))
plt.show()
Here the resulting plot:
Hope this helps.

Resources