I have a program which iterates over a set of images with black background and has 7 objects of different colours.
I need to iterate over each pixel in order to find the middle x and y points by using the lower and upper boundaries of each coordinate then use these values to compute some distances.
The images are 640x480 in size and it takes about 2 seconds for each image which seems quite a lot. I have attached the code of how I am iterating through the pixels below.
def isBlack(r, g, b): return r == 0 and g == 0 and b == 0
def isRed(r, g, b): return r > 0 and g == 0 and b == 0
def isYellow(r, g, b): return r > 0 and g > 0 and b == 0 and r == g
def isOrange(r, g, b): return r > 0 and g > 0 and b == 0 and r != g
def isBlue(r, g, b): return r == 0 and g == 0 and b > 0
def isCyan(r, g, b): return r == 0 and g > 0 and b > 0
def isGreen(r, g, b): return r == 0 and g > 0 and b == 0
def isWhite(r, g, b): return r == g == b and r != 0
def getAbsoluteValues (im, side, frame):
sizes = im.shape
ny, nx, nc = sizes
array_of_maxes_x = np.empty((14,))
array_of_maxes_x[::2] = nx + 1
array_of_maxes_x[1::2] = -1
array_of_maxes_x = array_of_maxes_x.reshape(7, 2)
array_of_maxes_y = array_of_maxes_x.copy()
for idx_y, y in enumerate(im):
for idx_x, x in enumerate(y):
b, g, r = x
if isBlack(r, g, b):
continue
elif isRed(r, g, b):
array_of_maxes_x[0] = compareLoAndHi(idx_x, array_of_maxes_x[0])
array_of_maxes_y[0] = compareLoAndHi(idx_y, array_of_maxes_y[0])
elif isYellow(r, g, b):
array_of_maxes_x[1] = compareLoAndHi(idx_x, array_of_maxes_x[1])
array_of_maxes_y[1] = compareLoAndHi(idx_y, array_of_maxes_y[1])
elif isOrange(r, g, b):
array_of_maxes_x[2] = compareLoAndHi(idx_x, array_of_maxes_x[2])
array_of_maxes_y[2] = compareLoAndHi(idx_y, array_of_maxes_y[2])
elif isBlue(r, g, b):
array_of_maxes_x[3] = compareLoAndHi(idx_x, array_of_maxes_x[3])
array_of_maxes_y[3] = compareLoAndHi(idx_y, array_of_maxes_y[3])
elif isCyan(r, g, b):
array_of_maxes_x[4] = compareLoAndHi(idx_x, array_of_maxes_x[4])
array_of_maxes_y[4] = compareLoAndHi(idx_y, array_of_maxes_y[4])
elif isGreen(r, g, b):
array_of_maxes_x[5] = compareLoAndHi(idx_x, array_of_maxes_x[5])
array_of_maxes_y[5] = compareLoAndHi(idx_y, array_of_maxes_y[5])
elif isWhite(r, g, b) and not isStray(im, idx_x, idx_y):
array_of_maxes_x[6] = compareLoAndHi(idx_x, array_of_maxes_x[6])
array_of_maxes_y[6] = compareLoAndHi(idx_y, array_of_maxes_y[6])
def compareLoAndHi(coord, current):
if coord < current[0]: current[0] = coord
if coord > current[1]: current[1] = coord
return current
def isStray(im, x, y):
values = np.array([[x, y - 1],[x, y + 1],[x - 1, y],[x + 1, y]])
for i in range(0, 4):
b, g ,r = im[values[i][1], values[i][0]]
if(not isBlack(r, g, b) and not isWhite(r, g, b)):
return True
return False
I am not sure how to make this faster I've been looking at matrix routines and everything but I can't find an answer that fits my problem.
An example image is below.
You can check the colour of only detected contour.
import numpy as np
import cv2
image = cv2.imread('image.png')
cv2.imshow("image", image)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
_, threshold = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY)
cv2.imshow('threshold', threshold)
contours, hierarchy = cv2.findContours(threshold, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
blank_image = np.zeros_like(image)
for cnt in contours:
M = cv2.moments(cnt)
cX = int(M["m10"] / M["m00"])
cY = int(M["m01"] / M["m00"])
colour = (int(image[cY, cX, 0]), int(image[cY, cX, 1]), int(image[cY, cX, 2]))
print(f'point: ({cX},{cY}), color (BGR): {colour}')
cv2.circle(blank_image, (cX, cY), 2, colour, 2)
cv2.imshow('contour_image', blank_image)
cv2.waitKey(0)
cv2.destroyAllWindows()
Output:
point: (464,219), color (BGR): (0, 156, 213)
point: (368,220), color (BGR): (0, 215, 0)
point: (388,197), color (BGR): (217, 217, 217)
point: (384,176), color (BGR): (211, 0, 0)
point: (338,176), color (BGR): (111, 238, 238)
point: (333,171), color (BGR): (215, 215, 0)
point: (366,143), color (BGR): (2, 2, 216)
Also, you can iterate only non zero points
import numpy as np
import cv2
image = cv2.imread('image.png')
cv2.imshow("image", image)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
X_Cordinates,Y_Cordinates = np.nonzero(gray)
print("X_Cordinates : \n", X_Cordinates)
print("\nY_Cordinates : \n", Y_Cordinates)
Output:
X_Cordinates :
[105 105 106 106 106 106 107 107 107 107 108 108 108 108 109 109 109 109
110 110 126 126 126 127 127 127 127 128 128 128 128 128 129 129 129 129
130 130 130 130 130 130 130 131 131 131 131 131 131 131 131 132 132 132
132 132 132 132 132 133 133 133 133 133 133 133 133 134 134 134 134 134
146 147 147 147 147 147 148 148 148 148 148 149 149 149 149 149 163 163
163 163 163 163 163 163 164 164 164 164 164 164 164 164 164 165 165 165
165 165 165 165 165 165 165 166 166 166 166 166 166 166 166 166 167 167
167 167 167 167 167 167]
Y_Cordinates :
[274 275 273 274 275 276 273 274 275 276 273 274 275 276 273 274 275 276
274 275 249 250 251 249 250 251 252 248 249 250 251 252 249 250 251 252
249 250 251 253 254 288 289 252 253 254 255 287 288 289 290 252 253 254
255 287 288 289 290 252 253 254 255 287 288 289 290 252 253 254 288 289
291 289 290 291 292 293 289 290 291 292 293 289 290 291 292 293 275 276
277 278 347 348 349 350 275 276 277 278 346 347 348 349 350 274 275 276
277 278 346 347 348 349 350 275 276 277 278 346 347 348 349 350 275 276
277 278 347 348 349 350]
try to use smaller resolution for the same images like a 320x240 and see the time needed for the the iteration i think this will cut the time to the half.
Related
I have a df containing x, y coordinates that looks like this:
x y
280 230
230 247
219 255
209 270
203 290
199 313
198 336
204 365
208 372
220 392
253 429
281 448
... ...
The function that I created must take in 4 x coordinates and 4 corresponding y coordinates at a time, however I don't know how to iterate through a df and pass 4 rows of data at a time. Here is an example of my function call at the moment:
my_function(ax, ay, bx, by, cx, cy, dx, dy)
The function call doesn't have to be this way, I just need to be able to access all 4 x and y coordinates.
How would I go about passing the data into my function?
Edit: Just to clarify, the rows need to be passed in on a rolling basis, so I would like to call my_function(x1, y1, x2, y2, x3, y3, x4, y4) and then my_function(x2, y2, x3, y3, x4, y4, x5, y5) and so on...
You can set a custom grouper using np.arange(len(df))//4 to make groups of 4.
to get the function output back in the dataframe:
df.groupby(np.arange(len(df))//4).apply(lambda g: g.values.flatten())
output:
0 [280, 230, 230, 247, 219, 255, 209, 270]
1 [203, 290, 199, 313, 198, 336, 204, 365]
2 [208, 372, 220, 392, 253, 429, 281, 448]
dtype: object
to apply a function per group and get the result outside of the dataframe:
for name, g in df.groupby(np.arange(len(df))//4):
print(f'group: {name}')
print(g.values.flatten()) # replace print here with your function
output:
group: 0
[280 230 230 247 219 255 209 270]
group: 1
[203 290 199 313 198 336 204 365]
group: 2
[208 372 220 392 253 429 281 448]
Rolling over multiple columns in pandas is quite tricky, because it only supports single column aggregations. But you can roll over one column and slice the same rows out of multiple columns of you dataframe. Also rolling apply must return one single number. So, a rolling solution could be
def my_function(ax, ay, bx, by, cx, cy, dx, dy):
return ax + ay + bx + by + cx + cy + dx + dy
df['result'] = df.rolling(4, min_periods=4).x.apply(
lambda x: my_function(*df.loc[x.index, ['x','y']].to_numpy().ravel()))
df
Output
x y result
0 280 230 NaN
1 230 247 NaN
2 219 255 NaN
3 209 270 1940.0
4 203 290 1923.0
5 199 313 1958.0
6 198 336 2018.0
7 204 365 2108.0
8 208 372 2195.0
9 220 392 2295.0
10 253 429 2443.0
11 281 448 2603.0
Assuming you want to pass consecutive slices of rows to your function you can group every 4 rows and flatten the dataframe to get the shape you need.
def my_function(ax, ay, bx, by, cx, cy, dx, dy):
return f'ax={ax}, ay={ay}, bx={bx}, by={by}, cx={cx}, cy={cy}, dx={dx}, dy={dy}'
df.groupby(df.index//4).apply(lambda x: my_function(*x.to_numpy().ravel()))
Output
0 ax=280, ay=230, bx=230, by=247, cx=219, cy=255...
1 ax=203, ay=290, bx=199, by=313, cx=198, cy=336...
2 ax=208, ay=372, bx=220, by=392, cx=253, cy=429...
dtype: object
Dataframe used in this example
import pandas as pd
import io
t = '''
x y
280 230
230 247
219 255
209 270
203 290
199 313
198 336
204 365
208 372
220 392
253 429
281 448'''
df = pd.read_csv(io.StringIO(t), sep='\s+')
df
Output
x y
0 280 230
1 230 247
2 219 255
3 209 270
4 203 290
5 199 313
6 198 336
7 204 365
8 208 372
9 220 392
10 253 429
11 281 448
i have txt file with x,y,z coordinates as follows:
x y z another value
129.000000 -51.000000 3.192000 166 166 166
133.000000 -21.000000 6.982500 171 169 170
134.000000 -51.000000 8.379000 172 170 171
135.000000 -45.000000 8.379000 167 165 166
136.000000 -81.000000 8.578500 160 158 159
137.000000 -51.000000 9.376500 159 157 158
138.000000 -51.000000 9.576000 169 168 167
how to read the value of z when x=20,y=33?
I tried using data = numpy.genfromtxt(yourFileName) but it not worked for me
import pandas as pd
from io import StringIO
x = '''x y z v1 v2 v3
129.000000 -51.000000 3.192000 166 166 166
133.000000 -21.000000 6.982500 171 169 170
134.000000 -51.000000 8.379000 172 170 171
135.000000 -45.000000 8.379000 167 165 166
136.000000 -81.000000 8.578500 160 158 159
137.000000 -51.000000 9.376500 159 157 158
138.000000 -51.000000 9.576000 169 168 167'''
out = StringIO(x )
df = pd.read_csv( out , delimiter="\s+" )
print (df.query( "x==138 and y==-51" ).z.values )
Just tried using blobs to detect my image, using the example here:
https://www.learnopencv.com/blob-detection-using-opencv-python-c/,
but it just does not detect anything.
https://imgur.com/a/YE1YZpV
I tried using the original Image, grey image, and thresholding it to only black and white, but none of them detect any blobs, and the keypoints always remain 0.
import numpy as np
import cv2
im_width = 320
im_height = 240
img = cv2.imread("D:\\20190822\\racket.bmp")
GreyImage=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret,thresh=cv2.threshold(GreyImage,50,255,cv2.THRESH_BINARY)
detector = cv2.SimpleBlobDetector_create()
keypoints = detector.detect(thresh)
blobs = cv2.drawKeypoints(thresh, keypoints, np.array([]), (0,0,255), cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
print(len(keypoints))
cv2.imshow("Keypoints", blobs)
cv2.waitKey(0)
cv2.destroyAllWindows()
I am not an expert on this, but it appears from the documentation that the default is to find circular blobs. You do not have circles except for some small dots. So you have to relax all the arguments to catch every shape. See
https://docs.opencv.org/3.4/d0/d7a/classcv_1_1SimpleBlobDetector.html
https://docs.opencv.org/3.4/d2/d29/classcv_1_1KeyPoint.html#a308006c9f963547a8cff61548ddd2ef2
https://craftofcoding.wordpress.com/tag/cv2/
So try this:
Input:
import numpy as np
import cv2
import math
img = cv2.imread("racket.png")
GreyImage=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret,thresh=cv2.threshold(GreyImage,50,255,cv2.THRESH_BINARY)
# erode to one large blob
#thresh = cv2.erode(thresh, None, iterations=4)
cv2.imshow("Threshold", thresh)
cv2.waitKey(0)
cv2.destroyAllWindows()
# Set up the SimpleBlobdetector with default parameters.
params = cv2.SimpleBlobDetector_Params()
# Change thresholds
params.minThreshold = 0
params.maxThreshold = 256
# Filter by Area.
params.filterByArea = True
params.minArea = 0
params.maxArea = 100000000000
# Filter by Color (black)
params.filterByColor = True
params.blobColor = 0
# Filter by Circularity
params.filterByCircularity = True
params.minCircularity = 0
params.maxCircularity = 100000000
# Filter by Convexity
params.filterByConvexity = True
params.minConvexity = 0
params.maxConvexity = 100000000
# Filter by InertiaRatio
params.filterByInertia = True
params.minInertiaRatio = 0
params.maxInertiaRatio = 100000000
# Distance Between Blobs
params.minDistBetweenBlobs = 0
# Do detecting
detector = cv2.SimpleBlobDetector_create(params)
# Get keypoints
keypoints = detector.detect(thresh)
print(len(keypoints))
print('')
# Get keypoint locations and radius
for keypoint in keypoints:
x = int(keypoint.pt[0])
y = int(keypoint.pt[1])
s = keypoint.size
r = int(math.floor(s/2))
print (x,y,r)
#cv2.circle(img, (x, y), r, (0, 0, 255), 2)
# Draw blobs
blobs = cv2.drawKeypoints(thresh, keypoints, np.array([]), (0,0,255), cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
cv2.imshow("Keypoints", blobs)
cv2.waitKey(0)
cv2.destroyAllWindows()
# Save result
cv2.imwrite("racket_blobs.jpg", blobs)
42
136 226 35
138 225 1
136 225 1
134 225 1
140 223 1
122 223 1
137 222 1
144 221 1
114 221 1
83 232 9
144 219 1
150 217 1
114 215 1
164 209 1
158 209 1
163 206 1
118 203 1
128 195 1
175 194 1
134 189 1
185 184 1
154 175 1
197 174 1
159 174 1
157 172 1
196 171 1
162 171 1
200 169 1
198 167 1
204 165 1
200 165 1
200 163 1
211 162 1
179 160 1
208 159 1
210 157 1
204 157 1
135 227 1
203 156 1
214 155 1
204 155 1
200 155 1
If you want to see the shapes, then you might be better off using contours rather than blobs.
I have a 32*32 matrix and I want to break it into 4 8x8 matrixes.
Here's how I try to make a smaller matrix for top-left part of the big one (pix is a 32x32 matrix).
A = [[0]*mat_size]*mat_size
for i in range(mat_ size):
for j in range(mat_size):
A[i][j] = pix[i, j]
So, pix has the following values for top-left part:
198 197 194 194 197 192 189 196
199 199 198 198 199 195 195 145
200 200 201 200 200 204 131 18
201 201 199 201 203 192 57 56
201 200 198 200 207 171 41 141
200 200 198 199 208 160 38 146
198 198 198 198 206 157 39 129
198 197 197 199 209 157 38 77
But when I print(A) after the loop, all the rows of A equal to the last row of pix. So it's 8 rows of 198 197 197 199 209 157 38 77 I know I can use A = pix[:8, :8], but I prefer to use loop for some purpose. I wonder why that loop solution doesn't gives me correct result.
A = np.zeros((4, 4, 8, 8))
for i in range(4):
for j in range(4):
A[i, j] = pix[i*8:(i+1)*8, j*8:(j+1)*8]
If I understand your question correctly, this solution should work. What it's doing is iterating through the pix matrix, and selecting a 8*8 matrix each time. Is this what you need?
Consider using numpy in order to avoid multiple references pointing to the same list (the last list in the matrix):
mat_size = 8
A = np.empty((mat_size,mat_size))
pix = np.array(pix)
for i in range(mat_size):
for j in range(mat_size):
A[i][j] = pix[i][j]
Is there a way to save a custom maplotlib colourmap (matplotlib.cm) as a file (e.g Color Palette Table file (.cpt), like used in MATLAB) to be shared and then use later in other programs? (e.g. Panopoly, MATLAB...)
Example
Below a new LinearSegmentedColormap is made by modifying an existing colormap (by truncation, as shown in another question linked here).
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
# Get an existing colorbar
cb = 'CMRmap'
cmap = plt.get_cmap( cb )
# Variables to modify (truncate) the colormap with
minval = 0.15
maxval = 0.95
npoints = 100
# Now modify (truncate) the colorbar
cmap = matplotlib.colors.LinearSegmentedColormap.from_list(
'trunc({n},{a:.2f},{b:.2f})'.format(n=cmap.name, a=minval,
b=maxval), cmap(np.linspace(minval, maxval, npoints)))
# Now the data can be extracted as a dictionary
cdict = cmap._segmentdata
# e.g. variables ('blue', 'alpha', 'green', 'red')
print( cdict.keys() )
# Now, is it possible to save to this as a .cpt?
More detail
I am aware of ways of loading external colormaps in matplotlib (e.g. shown here and here).
From NASA GISS's Panoply documentation:
Color Palette Table (CPT) indicates a color palette format used by the
Generic Mapping Tools program. The format defines a number of solid
color and/or gradient bands between the colorbar extrema rather than a
finite number of distinct colors.
The following is a function that takes a colormap, some limits (vmin and vmax) and the number of colors as input and creates a cpt file from it.
import matplotlib.pyplot as plt
import numpy as np
def export_cmap_to_cpt(cmap, vmin=0,vmax=1, N=255, filename="test.cpt",**kwargs):
# create string for upper, lower colors
b = np.array(kwargs.get("B", cmap(0.)))
f = np.array(kwargs.get("F", cmap(1.)))
na = np.array(kwargs.get("N", (0,0,0))).astype(float)
ext = (np.c_[b[:3],f[:3],na[:3]].T*255).astype(int)
extstr = "B {:3d} {:3d} {:3d}\nF {:3d} {:3d} {:3d}\nN {:3d} {:3d} {:3d}"
ex = extstr.format(*list(ext.flatten()))
#create colormap
cols = (cmap(np.linspace(0.,1.,N))[:,:3]*255).astype(int)
vals = np.linspace(vmin,vmax,N)
arr = np.c_[vals[:-1],cols[:-1],vals[1:],cols[1:]]
# save to file
fmt = "%e %3d %3d %3d %e %3d %3d %3d"
np.savetxt(filename, arr, fmt=fmt,
header="# COLOR_MODEL = RGB",
footer = ex, comments="")
# test case: create cpt file from RdYlBu colormap
cmap = plt.get_cmap("RdYlBu",255)
# you may create your colormap differently, as in the question
export_cmap_to_cpt(cmap, vmin=0,vmax=1,N=20)
The resulting file looks like
# COLOR_MODEL = RGB
0.000000e+00 165 0 38 5.263158e-02 190 24 38
5.263158e-02 190 24 38 1.052632e-01 215 49 39
1.052632e-01 215 49 39 1.578947e-01 231 83 55
1.578947e-01 231 83 55 2.105263e-01 244 114 69
2.105263e-01 244 114 69 2.631579e-01 249 150 86
2.631579e-01 249 150 86 3.157895e-01 253 181 104
3.157895e-01 253 181 104 3.684211e-01 253 207 128
3.684211e-01 253 207 128 4.210526e-01 254 230 153
4.210526e-01 254 230 153 4.736842e-01 254 246 178
4.736842e-01 254 246 178 5.263158e-01 246 251 206
5.263158e-01 246 251 206 5.789474e-01 230 245 235
5.789474e-01 230 245 235 6.315789e-01 206 234 242
6.315789e-01 206 234 242 6.842105e-01 178 220 235
6.842105e-01 178 220 235 7.368421e-01 151 201 224
7.368421e-01 151 201 224 7.894737e-01 120 176 211
7.894737e-01 120 176 211 8.421053e-01 96 149 196
8.421053e-01 96 149 196 8.947368e-01 70 118 180
8.947368e-01 70 118 180 9.473684e-01 59 86 164
9.473684e-01 59 86 164 1.000000e+00 49 54 149
B 165 0 38
F 49 54 149
N 0 0 0
and would be in the required format.