Crop image to bounding box in Tensorflow Object Detection API - tensorflow

How can I crop an image to the bounding box in Tensorflow? I am using the Python API.
From the documentation,
tf.image.crop_to_bounding_box(image, offset_height, offset_width, target_height, target_width)
Crops an image to a specified bounding box.
This op cuts a rectangular part out of image. The top-left corner of the returned image is at offset_height, offset_width in image, and its lower-right corner is at offset_height + target_height, offset_width + target_width.
I can get the coordinates of a bounding box in normalized coordinates as,
ymin = boxes[0,i,0]
xmin = boxes[0,i,1]
ymax = boxes[0,i,2]
xmax = boxes[0,i,3]
and convert these to absolute coordinates,
(xminn, xmaxx, yminn, ymaxx) = (xmin * im_width, xmax * im_width, ymin * im_height, ymax * im_height)
However I cant figure out how to use these coordinates in the crop_to_bounding_box function.

Since we consider x as horizontal and y as vertical, following would crop the image with specified box.
cropped_image = tf.image.crop_to_bounding_box(image, yminn, xminn,
ymaxx - yminn, xmaxx - xminn)

Below is working code from cropping and saving bounding box in in tensorflow
for idx in range(len(bboxes)):
if bscores[idx] >= Threshold:
#Region of Interest
y_min = int(bboxes[idx][0] * im_height)
x_min = int(bboxes[idx][1] * im_width)
y_max = int(bboxes[idx][2] * im_height)
x_max = int(bboxes[idx][3] * im_width)
class_label = category_index[int(bclasses[idx])]['name']
class_labels.append(class_label)
bbox.append([x_min, y_min, x_max, y_max, class_label, float(bscores[idx])])
#Crop Image - Working Code
cropped_image = tf.image.crop_to_bounding_box(image, y_min, x_min, y_max - y_min, x_max - x_min).numpy().astype(np.int32)
# encode_jpeg encodes a tensor of type uint8 to string
output_image = tf.image.encode_jpeg(cropped_image)
# decode_jpeg decodes the string tensor to a tensor of type uint8
#output_image = tf.image.decode_jpeg(output_image)
score = bscores[idx] * 100
file_name = tf.constant(OUTPUT_PATH+image_name[:-4]+'_'+str(idx)+'_'+class_label+'_'+str(round(score))+'%'+'_'+os.path.splitext(image_name)[1])
writefile = tf.io.write_file(file_name, output_image)

Related

translate bounding box anotation from [xmin, ymin, width, height] to YOLOv7

I'm struggling with translating coordinates from [xmin, ymin, width, height] to YOLOv7 representation, could someone help me, please?
For example my image is width = 9477px, hight = 23354px,
the annotation for [xmin, ymin, width, height] is :
[2009 21947 207 251]
I would like to know how I translate it to YoloV7 coordinates.
got an answer to it:
def convert_bbox_coco2yolo(img_width, img_height, bbox):
"""
Convert bounding box from COCO format to YOLO format
Parameters
----------
img_width : int
width of image
img_height : int
height of image
bbox : list[int]
bounding box annotation in COCO format:
[top left x position, top left y position, width, height]
Returns
-------
list[float]
bounding box annotation in YOLO format:
[x_center_rel, y_center_rel, width_rel, height_rel]
"""
# YOLO bounding box format: [x_center, y_center, width, height]
# (float values relative to width and height of image)
x_tl, y_tl, w, h = bbox
dw = 1.0 / img_width
dh = 1.0 / img_height
x_center = x_tl + w / 2.0
y_center = y_tl + h / 2.0
x = x_center * dw
y = y_center * dh
w = w * dw
h = h * dh
return [x, y, w, h]

Problems when drawing bounding boxes manually

I am trying to draw bounding boxes manually using openCV. The bounding boxes coordinates is gathered using tensorflow object detection API.
When I get the coordinates, they are normalized, so I convert them and then add them to the image like so:
boxes = np.squeeze(detections['detection_boxes'])
scores = np.squeeze(detections['detection_scores'])
min_score_thresh = 0.7
bboxes = boxes[scores > min_score_thresh]
im_width, im_height,_ = image.shape
for box in bboxes:
ymin, xmin, ymax, xmax = box
ymin, xmin, ymax, xmax = int(xmin * im_width), int(xmax * im_width), int(ymin * im_height), int(ymax * im_height)
cv2.rectangle(image_np_with_detections, (xmin,ymin),(xmax,ymax),(0,0,255),5)
cv2.putText(image_np_with_detections,"TEST",(int(xmin),int(ymin)-5),cv2.FONT_HERSHEY_COMPLEX_SMALL,1,(0,0,255), 1)
Then I compare this rectangle with the ones that Tensorflow API generates like so:
viz_utils.visualize_boxes_and_labels_on_image_array(
image_np_with_detections,
detections['detection_boxes'],
detections['detection_classes'] + label_id_offset,
detections['detection_scores'],
category_index,
use_normalized_coordinates=True,
max_boxes_to_draw=5,
min_score_thresh=.7,
agnostic_mode=False)
#Show the image
cv2.imshow('object detection', image_np_with_detections)
But as the image below shows the bounding box that is drawn manualy is off. What could be the cause to this?
Thanks for any help!
EDIT:
After swapping im_width,im_height to im_height,im_width I now get:
The logic error lies on this line:
im_width, im_height, _ = image.shape
As opencv image arrays are structured in this manner: (height, width, channels), you would need to swap the positions you put the im_width and im_height in:
im_height, im_width, _ = image.shape
Also, this line:
ymin, xmin, ymax, xmax = int(xmin * im_width), int(xmax * im_width), int(ymin * im_height), int(ymax * im_height)
as you can see, should be:
ymin, xmin, ymax, xmax = int(ymin * im_height), int(xmin * im_width), int(ymax * im_height), int(xmax * im_width)

How to create a scalebar using cartopy and matplotlib?

in respect to the previous examples in stackoverflow, I searched for other alternatives in order to create a scalebar.
In my research, I verified that the Basemap class from mpl_toolkits.basemap see here. It has the "drawmapscale" method. This method has the option barstyle = 'fancy' for a more interesting scalebar drawing.
Therefore, I attempted to convert the "drawmapscale" from the Basemap into a cartopy version.
Nevertheless, the results were not positive, and I got error messages from the figure. I believe that the error is in the Transform of the data.
Here is the script:
import numpy as np
import matplotlib.pyplot as plt
import pyproj
import cartopy.crs as ccrs
from matplotlib import is_interactive
from cartopy.crs import (WGS84_SEMIMAJOR_AXIS, WGS84_SEMIMINOR_AXIS)
from pyproj import Transformer
class Scalebar():
def __init__(self,
ax,
suppress_ticks=True,
geographical_crs = 4326,
planar_crs = 5880,
fix_aspect=True,
anchor='C',
celestial=False,
round=False,
noticks=False,
metric_ccrs=ccrs.TransverseMercator()):
self.ax = ax
self.fix_aspect = fix_aspect
# setting metric ccrs for reference in the plotting
self.metric_ccrs = metric_ccrs
self.anchor = anchor
# geographic or celestial coords?
self.celestial = celestial
# map projection.
self.projection = ax.projection
self.geographical_crs = geographical_crs
self.planar_crs = planar_crs
self._initialized_axes = set()
self.round = round
# map boundary not yet drawn.
self._mapboundarydrawn = False
self.rmajor = np.float(ax.projection.globe.semimajor_axis or WGS84_SEMIMAJOR_AXIS)
self.rminor = np.float(ax.projection.globe.semiminor_axis or WGS84_SEMIMINOR_AXIS)
# set instance variables defining map region.
self.xmin = self.projection.boundary.bounds[0]
self.xmax = self.projection.boundary.bounds[2]
self.ymin = self.projection.boundary.bounds[1]
self.ymax = self.projection.boundary.bounds[3]
self._width = self.xmax - self.xmin
self._height = self.ymax - self.ymin
self.noticks = noticks
def __call__(self,x,y,
inverse=False
):
"""
Calling the class instance with the arguments lon, lat will
convert lon/lat (in degrees) to x/y map projection
coordinates (in meters).
If optional keyword ``inverse`` is True (default is False),
the inverse transformation from x/y to lon/lat is performed.
Input arguments:
lon, lat can be either scalar floats, sequences, or numpy arrays.
"""
if not inverse:
transformer = Transformer.from_crs("epsg:{0}".format(self.geographical_crs),
"epsg:{0}".format(self.planar_crs))
else:
transformer = Transformer.from_crs("epsg:{0}".format(self.planar_crs),
"epsg:{0}".format(self.geographical_crs))
return transformer.transform(x, y)
def drawmapscale(self,
lon,
lat,
length,
lon0=None,
lat0=None,
barstyle='simple',\
units='km',
fontsize=9,
yoffset=None,
labelstyle='simple',\
fontcolor='k',
fillcolor1='w',
fillcolor2='k',\
format='%d',
zorder=None,
linecolor=None,
linewidth=None):
"""
Draw a map scale at ``lon,lat`` of length ``length``
representing distance in the map
projection coordinates at ``lon0,lat0``.
.. tabularcolumns:: |l|L|
============== ====================================================
Keywords Description
============== ====================================================
units the units of the length argument (Default km).
barstyle ``simple`` or ``fancy`` (roughly corresponding
to the styles provided by Generic Mapping Tools).
Default ``simple``.
fontsize for map scale annotations, default 9.
fontcolor for map scale annotations, default black.
labelstyle ``simple`` (default) or ``fancy``. For
``fancy`` the map scale factor (ratio betwee
the actual distance and map projection distance
at lon0,lat0) and the value of lon0,lat0 are also
displayed on the top of the scale bar. For
``simple``, just the units are display on top
and the distance below the scale bar.
If equal to False, plot an empty label.
format a string formatter to format numeric values
yoffset yoffset controls how tall the scale bar is,
and how far the annotations are offset from the
scale bar. Default is 0.02 times the height of
the map (0.02*(self.ymax-self.ymin)).
fillcolor1(2) colors of the alternating filled regions
(default white and black). Only relevant for
'fancy' barstyle.
zorder sets the zorder for the map scale.
linecolor sets the color of the scale, by default, fontcolor
is used
linewidth linewidth for scale and ticks
============== ====================================================
Extra keyword ``ax`` can be used to override the default axis instance.
"""
# get current axes instance (if none specified).
ax = self.ax
# convert length to meters
lenlab = length
if units == 'km':
length = length*1000
elif units == 'mi':
length = length*1609.344
elif units == 'nmi':
length = length*1852
elif units == 'ft':
length = length*0.3048
elif units != 'm':
msg = "units must be 'm' (meters), 'km' (kilometers), "\
"'mi' (miles), 'nmi' (nautical miles), or 'ft' (feet)"
raise KeyError(msg)
# Setting the center coordinates of the axes:
xmin, xmax, ymin, ymax = self.ax.get_extent()
if lon0 == None:
lon0 = np.mean([xmin, xmax])
if lat0 == None:
lat0 = np.mean([ymin, ymax])
# reference point and center of scale.
x0,y0 = self(lon0,lat0)
print('\n\n Central coords prior to transform')
print('lon0,lat0: ', [lon0,lat0])
print('\n\n central coordinates after transform')
print('x0,y0: ', [x0,y0])
xc,yc = self(lon,lat)
print('\n\n positional coordinates prior to transform')
print('lon, lat: ', [lon,lat])
print('\n\n central coordinates after transform')
print('xc,yc: ', [xc,yc])
print('-'*20, '\n')
# make sure lon_0 between -180 and 180
lon_0 = ((lon0+360) % 360) - 360
if lat0>0:
if lon>0:
lonlatstr = u'%g\N{DEGREE SIGN}N, %g\N{DEGREE SIGN}E' % (lat0,lon_0)
elif lon<0:
lonlatstr = u'%g\N{DEGREE SIGN}N, %g\N{DEGREE SIGN}W' % (lat0,lon_0)
else:
lonlatstr = u'%g\N{DEGREE SIGN}, %g\N{DEGREE SIGN}W' % (lat0,lon_0)
else:
if lon>0:
lonlatstr = u'%g\N{DEGREE SIGN}S, %g\N{DEGREE SIGN}E' % (lat0,lon_0)
elif lon<0:
lonlatstr = u'%g\N{DEGREE SIGN}S, %g\N{DEGREE SIGN}W' % (lat0,lon_0)
else:
lonlatstr = u'%g\N{DEGREE SIGN}S, %g\N{DEGREE SIGN}' % (lat0,lon_0)
# left edge of scale
lon1,lat1 = self(x0-length/2,y0, inverse=True)
x1,y1 = self(lon1,lat1)
# right edge of scale
lon4,lat4 = self(x0+length/2,y0, inverse=True)
x4,y4 = self(lon4,lat4)
x1 = x1-x0+xc
y1 = y1-y0+yc
print('\n\n positional coordinates prior to transform')
print('lon1,lat1: ', [lon1,lat1])
print('\n\n positional coordinates prior to transform')
print('x1, y1: ', [x1,y1])
print()
print('\n\n central coordinates after transform')
print('lon4,lat4: ', [lon4,lat4])
print('-'*20, '\n')
print('\n\n central coordinates after transform')
print('x4,y4: ', [x4,y4])
print('-'*20, '\n')
x4 = x4-x0+xc
y4 = y4-y0+yc
if x1 > 1.e20 or x4 > 1.e20 or y1 > 1.e20 or y4 > 1.e20:
raise ValueError("scale bar positioned outside projection limb")
# scale factor for true distance
gc = pyproj.Geod(a=self.rmajor,b=self.rminor)
az12,az21,dist = gc.inv(lon1,lat1,lon4,lat4)
scalefact = dist/length
# label to put on top of scale bar.
if labelstyle=='simple':
labelstr = units
elif labelstyle == 'fancy':
labelstr = units+" (scale factor %4.2f at %s)"%(scalefact,lonlatstr)
elif labelstyle == False:
labelstr = ''
else:
raise KeyError("labelstyle must be 'simple' or 'fancy'")
# default y offset is 2 percent of map height.
if yoffset is None:
yoffset = 0.02*(self.ymax-self.ymin)
rets = [] # will hold all plot objects generated.
# set linecolor
if linecolor is None:
linecolor = fontcolor
# 'fancy' style
if barstyle == 'fancy':
#we need 5 sets of x coordinates (in map units)
#quarter scale
lon2,lat2 = self(x0-length/4,y0,inverse=True)
x2,y2 = self(lon2,lat2)
x2 = x2-x0+xc; y2 = y2-y0+yc
#three quarter scale
lon3,lat3 = self(x0+length/4,y0,inverse=True)
x3,y3 = self(lon3,lat3)
x3 = x3-x0+xc; y3 = y3-y0+yc
#plot top line
ytop = yc+yoffset/2
ybottom = yc-yoffset/2
ytick = ybottom - yoffset/2
ytext = ytick - yoffset/2
lontext , lattext = self(lon0,ytext, inverse=True)
#lon_top, lat_top = self(lon4,ytop,inverse=True)
#lon_top, lat_bottom = self(lon4,ybottom,inverse=True)
transform = self.metric_ccrs # this crs projection is meant to be for metric data
rets.append(self.plot([x1,x4],
[ytop,ytop],
transform=transform,
color=linecolor,
linewidth=linewidth)[0])
#plot bottom line
rets.append(self.plot([x1,x4],
[ybottom,ybottom],
transform=transform,
color=linecolor,
linewidth=linewidth)[0])
#plot left edge
rets.append(self.plot([x1,x1],
[ybottom,ytop],
transform=transform,
color=linecolor,
linewidth=linewidth)[0])
#plot right edge
rets.append(self.plot([x4,x4],
[ybottom,ytop],
transform=transform,
color=linecolor,
linewidth=linewidth)[0])
#make a filled black box from left edge to 1/4 way across
rets.append(ax.fill([x1,x2,x2,x1,x1],
[ytop,ytop,ybottom,ybottom,ytop],
transform=transform,
ec=fontcolor,
fc=fillcolor1)[0])
#make a filled white box from 1/4 way across to 1/2 way across
rets.append(ax.fill([x2,x0,x0,x2,x2],
[ytop,ytop,ybottom,ybottom,ytop],
transform=transform,
ec=fontcolor,
fc=fillcolor2)[0])
#make a filled white box from 1/2 way across to 3/4 way across
rets.append(ax.fill([x0,x3,x3,x0,x0],
[ytop,ytop,ybottom,ybottom,ytop],
transform=transform,
ec=fontcolor,
fc=fillcolor1)[0])
#make a filled white box from 3/4 way across to end
rets.append(ax.fill([x3,x4,x4,x3,x3],
[ytop,ytop,ybottom,ybottom,ytop],
transform=transform,
ec=fontcolor,
fc=fillcolor2)[0])
#plot 3 tick marks at left edge, center, and right edge
rets.append(self.plot([x1,x1],
[ytick,ybottom],
color=linecolor,
transform=transform,
linewidth=linewidth)[0])
rets.append(self.plot([x0,x0],
[ytick,ybottom],
transform=transform,
color=linecolor,
linewidth=linewidth)[0])
rets.append(self.plot([x4,x4],
[ytick,ybottom],
transform=transform,
color=linecolor,
linewidth=linewidth)[0])
#label 3 tick marks
rets.append(ax.text(x1,lattext,format % (0),\
horizontalalignment='center',\
verticalalignment='top',\
fontsize=fontsize,color=fontcolor))
rets.append(ax.text(x0,lattext,format % (0.5*lenlab),\
horizontalalignment='center',\
verticalalignment='top',\
fontsize=fontsize,color=fontcolor))
rets.append(ax.text(x4,lattext,format % (lenlab),\
horizontalalignment='center',\
verticalalignment='top',\
fontsize=fontsize,color=fontcolor))
#put units, scale factor on top
rets.append(ax.text(x0,ytop+yoffset/2,labelstr,\
horizontalalignment='center',\
verticalalignment='bottom',\
fontsize=fontsize,color=fontcolor))
# 'simple' style
elif barstyle == 'simple':
rets.append(self.plot([x1,x4],[yc,yc],color=linecolor, linewidth=linewidth)[0])
rets.append(self.plot([x1,x1],[yc-yoffset,yc+yoffset],color=linecolor, linewidth=linewidth)[0])
rets.append(self.plot([x4,x4],[yc-yoffset,yc+yoffset],color=linecolor, linewidth=linewidth)[0])
rets.append(ax.text(xc,yc-yoffset,format % lenlab,\
verticalalignment='top',horizontalalignment='center',\
fontsize=fontsize,color=fontcolor))
#put units, scale factor on top
rets.append(ax.text(xc,yc+yoffset,labelstr,\
horizontalalignment='center',\
verticalalignment='bottom',\
fontsize=fontsize,color=fontcolor))
else:
raise KeyError("barstyle must be 'simple' or 'fancy'")
if zorder is not None:
for ret in rets:
try:
ret.set_zorder(zorder)
except:
pass
return rets
def plot(self, *args, **kwargs):
"""
Draw lines and/or markers on the map
(see matplotlib.pyplot.plot documentation).
If ``latlon`` keyword is set to True, x,y are intrepreted as
longitude and latitude in degrees. Data and longitudes are
automatically shifted to match map projection region for cylindrical
and pseudocylindrical projections, and x,y are transformed to map
projection coordinates. If ``latlon`` is False (default), x and y
are assumed to be map projection coordinates.
Extra keyword ``ax`` can be used to override the default axis instance.
Other \**kwargs passed on to matplotlib.pyplot.plot.
"""
ax = self.ax
self._save_use_hold(ax, kwargs)
try:
ret = ax.plot(*args,
**kwargs)
finally:
self._restore_hold(ax)
# set axes limits to fit map region.
self.set_axes_limits(ax=ax)
# clip to map limbs
ret,c = self._cliplimb(ax,ret)
return ret
def _save_use_hold(self, ax, kwargs):
h = kwargs.pop('hold', None)
if hasattr(ax, '_hold'):
self._tmp_hold = ax._hold
if h is not None:
ax._hold = h
def _restore_hold(self, ax):
if hasattr(ax, '_hold'):
ax._hold = self._tmp_hold
def set_axes_limits(self,ax=None):
"""
Final step in Basemap method wrappers of Axes plotting methods:
Set axis limits, fix aspect ratio for map domain using current
or specified axes instance. This is done only once per axes
instance.
In interactive mode, this method always calls draw_if_interactive
before returning.
"""
# get current axes instance (if none specified).
ax = ax or self._check_ax()
# If we have already set the axes limits, and if the user
# has not defeated this by turning autoscaling back on,
# then all we need to do is plot if interactive.
if (hash(ax) in self._initialized_axes
and not ax.get_autoscalex_on()
and not ax.get_autoscaley_on()):
if is_interactive():
import matplotlib.pyplot as plt
plt.draw_if_interactive()
return
self._initialized_axes.add(hash(ax))
# Take control of axis scaling:
ax.set_autoscale_on(False)
# update data limits for map domain.
corners = ((self.xmin, self.ymin), (self.xmax, self.ymax))
ax.update_datalim(corners)
ax.set_xlim((self.xmin, self.xmax))
ax.set_ylim((self.ymin, self.ymax))
# if map boundary not yet drawn for elliptical maps, draw it with default values.
# make sure aspect ratio of map preserved.
# plot is re-centered in bounding rectangle.
# (anchor instance var determines where plot is placed)
if self.fix_aspect:
ax.set_aspect('equal',anchor=self.anchor)
else:
ax.set_aspect('auto',anchor=self.anchor)
# make sure axis ticks are turned off.
if self.noticks:
ax.set_xticks([])
ax.set_yticks([])
# force draw if in interactive mode.
if is_interactive():
import matplotlib.pyplot as plt
plt.draw_if_interactive()
def _cliplimb(self,ax,coll):
if not self._mapboundarydrawn:
return coll, None
c = self._mapboundarydrawn
if c not in ax.patches:
p = ax.add_patch(c)
#p.set_clip_on(False)
try:
coll.set_clip_path(c)
except:
for item in coll:
item.set_clip_path(c)
return coll,c
# now the test
from cartopy.mpl.gridliner import LONGITUDE_FORMATTER, LATITUDE_FORMATTER
import geopandas as gpd
def get_standard_gdf():
""" basic function for getting some geographical data in geopandas GeoDataFrame python's instance:
An example data can be downloaded from Brazilian IBGE:
ref: ftp://geoftp.ibge.gov.br/organizacao_do_territorio/malhas_territoriais/malhas_municipais/municipio_2017/Brasil/BR/br_municipios.zip
"""
gdf_path = r'C:\my_file_path\Shapefile.shp'
return gpd.read_file(gdf_path)
def format_ax(ax, projection, xlim, ylim):
ax.set_xlim(xlim)
ax.set_ylim(ylim)
ax.set_global()
ax.coastlines()
def main():
fig = plt.figure(figsize=(8, 10))
# Label axes of a Plate Carree projection with a central longitude of 180:
#for enum, proj in enumerate(['Mercator, PlateCarree']):
gdf = get_standard_gdf()
xmin, ymin, xmax, ymax = gdf.total_bounds
xlim = [xmin, xmax]
ylim = [ymin, ymax]
lon_c = np.mean(xlim)
lat_c = np.mean(ylim)
projection = ccrs.PlateCarree(central_longitude=0)
ax1 = fig.add_subplot(3, 1, 1,
projection=projection,
xlim=[xmin, xmax],
ylim=[ymin, ymax])
gdf.plot(ax=ax1, transform=projection)
format_ax(ax1, projection, xlim, ylim)
Grider = ax1.gridlines(draw_labels=True)
Grider.xformatter = LONGITUDE_FORMATTER
Grider.yformatter = LATITUDE_FORMATTER
Grider.xlabels_top = False
Grider.ylabels_right = False
# Label axes of a Mercator projection without degree symbols in the labels
# and formatting labels to include 1 decimal place:
ax2 = fig.add_subplot(3, 1, 2,
projection=ccrs.Mercator(),
xlim=[xmin, xmax],
ylim=[ymin, ymax])
gdf.plot(ax=ax2, transform=projection)
format_ax(ax2, projection, xlim, ylim)
Grider = ax2.gridlines(draw_labels=True)
Grider.xformatter = LONGITUDE_FORMATTER
Grider.yformatter = LATITUDE_FORMATTER
Grider.xlabels_top = False
Grider.ylabels_right = False
ax3 = fig.add_subplot(3, 1, 3,
projection=ccrs.Robinson(central_longitude=lon_c,
#central_latitude=lat_c
),
xlim=[xmin, xmax],
ylim=[ymin, ymax])
gdf.plot(ax=ax3, transform=projection)
format_ax(ax3, projection, xlim, ylim)
ax3.set_xticks([-180, -120, -60, 0, 60, 120, 180])
ax3.set_yticks([-78.5, -60, -25.5, 25.5, 60, 80])
ax3.xaxis.set_major_formatter(LONGITUDE_FORMATTER)
ax3.yaxis.set_major_formatter(LATITUDE_FORMATTER)
plt.draw()
return fig, fig.get_axes()
if __name__ == '__main__':
length = 1000
fig, axes = main()
gdf = get_standard_gdf()
xmin, ymin, xmax, ymax = gdf.total_bounds
xoff = 0.3 * (xmax - xmin)
yoff = 0.2 * (ymax - ymin)
for ax in axes:
if hasattr(ax, 'projection'):
x0, x1, y0, y1 = np.ravel(ax.get_extent())
Scaler = Scalebar(ax=ax,
metric_ccrs=ccrs.Geodetic())
Scaler.drawmapscale(lon = xmin+xoff,
lat = ymin + yoff,
length=length,
units = 'km',
barstyle='fancy',
yoffset=0.2 * (ymax - ymin)
)
fig.suptitle('Using Cartopy')
fig.show()
When the above code is run, the scalebar is misplaced in the geoaxes. The scalebar xticks are misplaced, and its yaxis height proportion is also wrong.
Here is an example: the geopandas is plotted in blue. Note that the scalebar is only visible in the second and third geoaxes.
I found a solution for the current problem.
For sake of brevety, the code is presented in here.
Feel free to check it out. The algorithm still requires some adjustment in order to support other cartopy projections.
Meanwhile, it can be applied to PlateCarree projection.

How can I draw a rectangle from the points I want, using ROI?

Hello I am beginner in OpenCv.
I have a maze image. I wrote maze solver code. I need to get the photo like the picture for this code to work.
I want to choose the contours of the white area using ROI but I could not
When I try the ROI method I get a smooth rectangle with a black area selected.
https://i.stack.imgur.com/Ty5BX.png -----> this is my code result
https://i.stack.imgur.com/S7zuJ.png --------> I want to this result
import cv2
import numpy as np
#import image
image = cv2.imread('rt4.png')
#grayscaleqq
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
#cv2.imshow('gray', gray)
#qcv2.waitKey(0)
#binary
#ret,thresh = cv2.threshold(gray,127,255,cv2.THRESH_BINARY_INV)
threshold = 150
thresh = cv2.threshold(gray, threshold, 255, cv2.THRESH_BINARY)[1]
cv2.namedWindow('second', cv2.WINDOW_NORMAL)
cv2.imshow('second', thresh)
cv2.waitKey(0)
cv2.destroyAllWindows()
#dilation
kernel = np.ones((1,1), np.uint8)
img_dilation = cv2.dilate(thresh, kernel, iterations=1)
cv2.namedWindow('dilated', cv2.WINDOW_NORMAL)
cv2.imshow('dilated', img_dilation)
cv2.waitKey(0)
cv2.destroyAllWindows()
#find contours
im2,ctrs, hier = cv2.findContours(img_dilation.copy(),
cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
#sort contours
sorted_ctrs = sorted(ctrs, key=lambda ctr: cv2.boundingRect(ctr)
[0])
list = []
for i, ctr in enumerate(sorted_ctrs):
# Get bounding box
x, y, w, h = cv2.boundingRect(ctr)
# Getting ROI
roi = image[y:y+h, x:x+w]
a = w-x
b = h-y
list.append((a,b,x,y,w,h))
# show ROI
#cv2.imshow('segment no:'+str(i),roi)
cv2.rectangle(image,(x,y),( x + w, y + h ),(0,255,0),2)
#cv2.waitKey(0)
if w > 15 and h > 15:
cv2.imwrite('home/Desktop/output/{}.png'.format(i), roi)
cv2.namedWindow('marked areas', cv2.WINDOW_NORMAL)
cv2.imshow('marked areas',image)
cv2.waitKey(0)
cv2.destroyAllWindows()
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
gray = np.float32(gray)
dst = cv2.cornerHarris(gray,2,3,0.04)
#result is dilated for marking the corners, not important
dst = cv2.dilate(dst,None)
image[dst>0.01*dst.max()]=[0,0,255]
cv2.imshow('dst',image)
if cv2.waitKey(0) & 0xff == 27:
cv2.destroyAllWindows()
list.sort()
print(list[len(list)-1])
I misunderstood your question earlier. So, I'm rewriting.
As #Silencer has already stated, you could use the drawContours method. You can do it as follows:
import cv2
import numpy as np
#import image
im = cv2.imread('Maze2.png')
gaus = cv2.GaussianBlur(im, (5, 5), 1)
# mask1 = cv2.dilate(gaus, np.ones((15, 15), np.uint8, 3))
mask2 = cv2.erode(gaus, np.ones((5, 5), np.uint8, 1))
imgray = cv2.cvtColor(mask2, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(imgray, 127, 255, 0)
im2, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
maxArea1=0
maxI1=0
for i in range(len(contours)):
area = cv2.contourArea(contours[i])
epsilon = 0.01 * cv2.arcLength(contours[i], True)
approx = cv2.approxPolyDP(contours[i], epsilon, True)
if area > maxArea1 :
maxArea1 = area
print(maxArea1)
print(maxI1)
cv2.drawContours(im, contours, maxI1, (0,255,255), 3)
cv2.imshow("yay",im)
cv2.imshow("gray",imgray)
cv2.waitKey(0)
cv2.destroyAllWindows()
I used it on the following image:
And I got the right answer. You can add additional filters, or you could decrease the area using an ROI, to decrese the discrepancy, but it wasn't required
Hope it helps!
A simple solution to just draw a slanted rectangle would be to use cv2.polylines. Based on your result, I'm assuming you have the coordinates of the vertices of the area already, lets call them [x1,y1], [x2,y2], [x3,y3], [x4,y4]. The polylines function draws a line from vertex to vertex to create a closed polygon.
import cv2
import numpy as np
#List coordinates of vertices as an array
pts = np.array([[x1,y1],[x2,y2],[x3,y3],[x4,y4]], np.int32)
pts = pts.reshape((-1,1,2))
#Draw lines from vertex to vertex
cv2.polylines(image, [pts], True, (255,0,0))

Annotating ranges of data in matplotlib

How can I annotate a range of my data? E.g., say the data from x = 5 to x = 10 is larger than some cut-off, how could I indicate that on the graph. If I was annotating by hand, I would just draw a large bracket above the range and write my annotation above the bracket.
The closest I've seen is using arrowstyle='<->' and connectionstyle='bar', to make two arrows pointing to the edges of your data with a line connecting their tails. But that doesn't quite do the right thing; the text that you enter for the annotation will end up under one of the arrows, rather than above the bar.
Here is my attempt, along with it's results:
annotate(' ', xy=(1,.5), xycoords='data',
xytext=(190, .5), textcoords='data',
arrowprops=dict(arrowstyle="<->",
connectionstyle="bar",
ec="k",
shrinkA=5, shrinkB=5,
)
)
Another problem with my attempted solution is that the squared shape of the annotating bracket does not really make it clear that I am highlighting a range (unlike, e.g., a curly brace). But I suppose that's just being nitpicky at this point.
As mentioned in this answer, you can construct curly brackets with sigmoidal functions. Below is a function that adds curly brackets just above the x-axis. The curly brackets it produces should look the same regardless of the axes limits, as long as the figure width and height don't vary.
import numpy as np
import matplotlib.pyplot as plt
def draw_brace(ax, xspan, text):
"""Draws an annotated brace on the axes."""
xmin, xmax = xspan
xspan = xmax - xmin
ax_xmin, ax_xmax = ax.get_xlim()
xax_span = ax_xmax - ax_xmin
ymin, ymax = ax.get_ylim()
yspan = ymax - ymin
resolution = int(xspan/xax_span*100)*2+1 # guaranteed uneven
beta = 300./xax_span # the higher this is, the smaller the radius
x = np.linspace(xmin, xmax, resolution)
x_half = x[:resolution//2+1]
y_half_brace = (1/(1.+np.exp(-beta*(x_half-x_half[0])))
+ 1/(1.+np.exp(-beta*(x_half-x_half[-1]))))
y = np.concatenate((y_half_brace, y_half_brace[-2::-1]))
y = ymin + (.05*y - .01)*yspan # adjust vertical position
ax.autoscale(False)
ax.plot(x, y, color='black', lw=1)
ax.text((xmax+xmin)/2., ymin+.07*yspan, text, ha='center', va='bottom')
ax = plt.gca()
ax.plot(range(10))
draw_brace(ax, (0, 8), 'large brace')
draw_brace(ax, (8, 9), 'small brace')
Output:
I modified Joooeey's answer to allow to change the vertical position of braces:
def draw_brace(ax, xspan, yy, text):
"""Draws an annotated brace on the axes."""
xmin, xmax = xspan
xspan = xmax - xmin
ax_xmin, ax_xmax = ax.get_xlim()
xax_span = ax_xmax - ax_xmin
ymin, ymax = ax.get_ylim()
yspan = ymax - ymin
resolution = int(xspan/xax_span*100)*2+1 # guaranteed uneven
beta = 300./xax_span # the higher this is, the smaller the radius
x = np.linspace(xmin, xmax, resolution)
x_half = x[:int(resolution/2)+1]
y_half_brace = (1/(1.+np.exp(-beta*(x_half-x_half[0])))
+ 1/(1.+np.exp(-beta*(x_half-x_half[-1]))))
y = np.concatenate((y_half_brace, y_half_brace[-2::-1]))
y = yy + (.05*y - .01)*yspan # adjust vertical position
ax.autoscale(False)
ax.plot(x, y, color='black', lw=1)
ax.text((xmax+xmin)/2., yy+.07*yspan, text, ha='center', va='bottom')
ax = plt.gca()
ax.plot(range(10))
draw_brace(ax, (0, 8), -0.5, 'large brace')
draw_brace(ax, (8, 9), 3, 'small brace')
Output:
Also note that in Joooeey's answer, line
x_half = x[:resolution/2+1]
should be
x_half = x[:int(resolution/2)+1]
Otherwise, the number that the script tries to use as index here is a float.
Finally, note that right now the brace will not show up if you move it out of bounds. You need to add parameter clip_on=False, like this:
ax.plot(x, y, color='black', lw=1, clip_on=False)
You can just wrap it all up in a function:
def add_range_annotation(ax, start, end, txt_str, y_height=.5, txt_kwargs=None, arrow_kwargs=None):
"""
Adds horizontal arrow annotation with text in the middle
Parameters
----------
ax : matplotlib.Axes
The axes to draw to
start : float
start of line
end : float
end of line
txt_str : string
The text to add
y_height : float
The height of the line
txt_kwargs : dict or None
Extra kwargs to pass to the text
arrow_kwargs : dict or None
Extra kwargs to pass to the annotate
Returns
-------
tuple
(annotation, text)
"""
if txt_kwargs is None:
txt_kwargs = {}
if arrow_kwargs is None:
# default to your arrowprops
arrow_kwargs = {'arrowprops':dict(arrowstyle="<->",
connectionstyle="bar",
ec="k",
shrinkA=5, shrinkB=5,
)}
trans = ax.get_xaxis_transform()
ann = ax.annotate('', xy=(start, y_height),
xytext=(end, y_height),
transform=trans,
**arrow_kwargs)
txt = ax.text((start + end) / 2,
y_height + .05,
txt_str,
**txt_kwargs)
if plt.isinteractive():
plt.draw()
return ann, txt
Alternately,
start, end = .6, .8
ax.axvspan(start, end, alpha=.2, color='r')
trans = ax.get_xaxis_transform()
ax.text((start + end) / 2, .5, 'test', transform=trans)
Here is a minor modification to guzey and jooeey's answer to plot the flower braces outside the axes.
def draw_brace(ax, xspan, yy, text):
"""Draws an annotated brace outside the axes."""
xmin, xmax = xspan
xspan = xmax - xmin
ax_xmin, ax_xmax = ax.get_xlim()
xax_span = ax_xmax - ax_xmin
ymin, ymax = ax.get_ylim()
yspan = ymax - ymin
resolution = int(xspan/xax_span*100)*2+1 # guaranteed uneven
beta = 300./xax_span # the higher this is, the smaller the radius
x = np.linspace(xmin, xmax, resolution)
x_half = x[:int(resolution/2)+1]
y_half_brace = (1/(1.+np.exp(-beta*(x_half-x_half[0])))
+ 1/(1.+np.exp(-beta*(x_half-x_half[-1]))))
y = np.concatenate((y_half_brace, y_half_brace[-2::-1]))
y = yy + (.05*y - .01)*yspan # adjust vertical position
ax.autoscale(False)
ax.plot(x, -y, color='black', lw=1, clip_on=False)
ax.text((xmax+xmin)/2., -yy-.17*yspan, text, ha='center', va='bottom')
# Sample code
fmax = 1
fstart = -100
fend = 0
frise = 50
ffall = 20
def S(x):
if x<=0:
return 0
elif x>=1:
return 1
else:
return 1/(1+np.exp((1/(x-1))+(1/x)))
x = np.linspace(700,1000,500)
lam = [fmax*(S((i-880)/60)-S(((i-1000)/25)+1)) for i in x]
fig = plt.figure(1)
ax = fig.add_subplot(111)
plt.plot(x,lam)
plt.xlim([850,1000])
ax.set_aspect(50,adjustable='box')
plt.ylabel('$\lambda$')
plt.xlabel('$x$')
ax.xaxis.set_label_coords(0.5, -0.35)
draw_brace(ax, (900,950),0.2, 'rise')
draw_brace(ax, (980,1000),0.2, 'fall')
plt.text(822,0.95,'$(\lambda_{\mathrm{max}})$')
Sample output
a minor modification of the draw_brace of #Joooeey and #guezy to have also the brace upside down
+argument upsidedown
def draw_brace(ax, xspan, yy, text, upsidedown=False):
"""Draws an annotated brace on the axes."""
# shamelessly copied from https://stackoverflow.com/questions/18386210/annotating-ranges-of-data-in-matplotlib
xmin, xmax = xspan
xspan = xmax - xmin
ax_xmin, ax_xmax = ax.get_xlim()
xax_span = ax_xmax - ax_xmin
ymin, ymax = ax.get_ylim()
yspan = ymax - ymin
resolution = int(xspan/xax_span*100)*2+1 # guaranteed uneven
beta = 300./xax_span # the higher this is, the smaller the radius
x = np.linspace(xmin, xmax, resolution)
x_half = x[:int(resolution/2)+1]
y_half_brace = (1/(1.+np.exp(-beta*(x_half-x_half[0])))
+ 1/(1.+np.exp(-beta*(x_half-x_half[-1]))))
if upsidedown:
y = np.concatenate((y_half_brace[-2::-1], y_half_brace))
else:
y = np.concatenate((y_half_brace, y_half_brace[-2::-1]))
y = yy + (.05*y - .01)*yspan # adjust vertical position
ax.autoscale(False)
line = ax.plot(x, y, color='black', lw=1)
if upsidedown:
text = ax.text((xmax+xmin)/2., yy+-.07*yspan, text, ha='center', va='bottom',fontsize=7)
else:
text = ax.text((xmax+xmin)/2., yy+.07*yspan, text, ha='center', va='bottom',fontsize=7)
return line, text
I updated the previous answers to have some of the features I wanted, like an option for a vertical brace, that I wanted to place in multi-plot figures. One still has to futz with the beta_scale parameter sometimes depending on the scale of the data that one is applying this to.
def rotate_point(x, y, angle_rad):
cos,sin = np.cos(angle_rad),np.sin(angle_rad)
return cos*x-sin*y,sin*x+cos*y
def draw_brace(ax, span, position, text, text_pos, brace_scale=1.0, beta_scale=300., rotate=False, rotate_text=False):
'''
all positions and sizes are in axes units
span: size of the curl
position: placement of the tip of the curl
text: label to place somewhere
text_pos: position for the label
beta_scale: scaling for the curl, higher makes a smaller radius
rotate: true rotates to place the curl vertically
rotate_text: true rotates the text vertically
'''
# get the total width to help scale the figure
ax_xmin, ax_xmax = ax.get_xlim()
xax_span = ax_xmax - ax_xmin
resolution = int(span/xax_span*100)*2+1 # guaranteed uneven
beta = beta_scale/xax_span # the higher this is, the smaller the radius
# center the shape at (0, 0)
x = np.linspace(-span/2., span/2., resolution)
# calculate the shape
x_half = x[:int(resolution/2)+1]
y_half_brace = (1/(1.+np.exp(-beta*(x_half-x_half[0])))
+ 1/(1.+np.exp(-beta*(x_half-x_half[-1]))))
y = np.concatenate((y_half_brace, y_half_brace[-2::-1]))
# put the tip of the curl at (0, 0)
max_y = np.max(y)
min_y = np.min(y)
y /= (max_y-min_y)
y *= brace_scale
y -= max_y
# rotate the trace before shifting
if rotate:
x,y = rotate_point(x, y, np.pi/2)
# shift to the user's spot
x += position[0]
y += position[1]
ax.autoscale(False)
ax.plot(x, y, color='black', lw=1, clip_on=False)
# put the text
ax.text(text_pos[0], text_pos[1], text, ha='center', va='bottom', rotation=90 if rotate_text else 0)