第三方包安裝
pip install pyclipper
1. 輪廓點等距離外擴
def equidistant_zoom_contour(contour, margin):
"""
等距離縮放多邊形輪廓點
:param contour: 一個圖形的輪廓格式[[[x1, x2]],...],shape是(-1, 1, 2)
:param margin: 輪廓外擴的像素距離,margin正數是外擴,負數是縮小
:return: 外擴后的輪廓點
"""
pco = pyclipper.PyclipperOffset()
##### 參數限制,默認成2這里設置大一些,主要是用於多邊形的尖角是否用圓角代替
pco.MiterLimit = 10
contour = contour[:, 0, :]
pco.AddPath(contour, pyclipper.JT_MITER, pyclipper.ET_CLOSEDPOLYGON)
solution = pco.Execute(margin)
solution = np.array(solution).reshape(-1, 1, 2).astype(int)
return solution
調試用例
import pyclipper
import math
from shapely.geometry import LineString, Polygon, MultiLineString, Point, MultiPoint
poly = np.array([[[200, 200]], [[200, 300]], [[400, 350]], [[350, 200]], [[300, 200]], [[200, 100]]])
contour1 = equidistant_zoom_contour(poly, 20)
img = np.zeros((500, 500, 3))
cv2.polylines(img, [poly], True, (0, 0, 255), 3)
cv2.polylines(img, [contour1], True, (0, 255, 0), 3)
結果展示:
2. 輪廓點等比例縮放
def perimeter(poly):
p = 0
nums = poly.shape[0]
for i in range(nums):
p += abs(np.linalg.norm(poly[i % nums] - poly[(i + 1) % nums]))
return p
def proportional_zoom_contour(contour, ratio):
"""
多邊形輪廓點按照比例進行縮放
:param contour: 一個圖形的輪廓格式[[[x1, x2]],...],shape是(-1, 1, 2)
:param ratio: 縮放的比例,如果大於1是放大小於1是縮小
:return:
"""
poly = contour[:, 0, :]
area_poly = abs(pyclipper.Area(poly))
perimeter_poly = perimeter(poly)
poly_s = []
pco = pyclipper.PyclipperOffset()
pco.MiterLimit = 10
if perimeter_poly:
d = area_poly * (1 - ratio * ratio) / perimeter_poly
pco.AddPath(poly, pyclipper.JT_MITER, pyclipper.ET_CLOSEDPOLYGON)
poly_s = pco.Execute(-d)
poly_s = np.array(poly_s).reshape(-1, 1, 2).astype(int)
return poly_s
測試用例:
import pyclipper
import math
from shapely.geometry import LineString, Polygon, MultiLineString, Point, MultiPoint
poly = np.array([[[200, 200]], [[200, 300]], [[400, 350]], [[350, 200]], [[300, 200]], [[200, 100]]])
contour1 = proportional_zoom_contour(poly, 1.5)
img = np.zeros((500, 500, 3))
cv2.polylines(img, [contour1], True, (0, 255, 0), 3)
cv2.polylines(img, [poly], True, (0, 0, 255), 3)
其中, pco.MiterLimit = 10這個參數默認是2,如果是默認的值結果圖第一個,改成10的話,結果圖就是第二個,是一個尖角的區別
3. 圖形輪廓的旋轉
# 獲取一個形狀的質心
def get_centroid(coord):
coord = np.array(coord)
shape = coord.shape
if len(shape) == 1 and len(coord) == 2: # point
return coord
if len(shape) == 1 and len(coord) == 4: # bounding box
return tuple([(coord[0] + coord[2]) // 2, (coord[1] + coord[3]) // 2])
elif len(shape) == 2 and shape[-1] == 2:
if shape[0] == 2: # 如果是直線
cen = LineString(coord).centroid
else:
cen = Polygon(coord).centroid
return tuple(map(int, [cen.x, cen.y]))
elif len(shape) == 3 and shape[1:] == (1, 2): # contour
cen = Polygon(coord.squeeze()).centroid
return tuple(map(int, [cen.x, cen.y]))
else:
raise Exception('coordinate error, must be bbox or contour shape:{}'.format(coord))
def point_Srotate(im_w, im_h, angle, spin_point, origin_point):
"""
:param im_w: 原始點所在的圖片的寬度
:param im_h: 原始點所在的圖片的高度
:param angle: 旋轉的角度
:param spin_point: 旋轉的點
:param origin_point: 參考點
:return: 旋轉過后的點
"""
row, col = im_h, im_w
# P(x1, y1),繞某個像素點Q(x2, y2)
x1, y1 = spin_point
x2, y2 = origin_point
y1 = row - y1
y2 = row - y2
x = (x1 - x2) * math.cos(math.pi / 180.0 * angle) - (y1 - y2) * math.sin(math.pi / 180.0 * angle) + x2
y = (x1 - x2) * math.sin(math.pi / 180.0 * angle) + (y1 - y2) * math.cos(math.pi / 180.0 * angle) + y2
x = x
y = row - y
return [x, y]
調用示例:
import pyclipper
import math
from shapely.geometry import LineString, Polygon, MultiLineString, Point, MultiPoint
# 以多邊形輪廓的質心為參照點進行旋轉
poly = np.array([[[200, 200]], [[200, 300]], [[400, 350]], [[350, 200]], [[300, 200]], [[200, 100]]])
origin_point = get_centroid(poly)
spin_list = []
for con in poly:
print('con', con)
new = point_Srotate(500, 500, 50, con[0], origin_point)
spin_list.append(new)
spin_con = np.array(spin_list).reshape(-1, 1, 2).astype(int)
img = np.zeros((500, 500, 3))
cv2.polylines(img, [spin_con], True, (0, 255, 0), 3)
cv2.polylines(img, [poly], True, (0, 0, 255), 3)
結果:
4. 其他外擴函數
def extend_contour2(contour, margin):
# 每個點相對於質心進行外擴一定的距離
"""
:param contour: 輪廓點集合
:param margin: 外擴的距離
:return: 外擴后的輪廓點集
"""
#### 求該輪廓的質心 ####
gravity_point = get_centroid(contour)
#### 獲取最左下的點 ####
# min_x = np.minimum(contour)
#### 計算所有的輪廓點與質心所組成的向量,計算向量的模
vector_arr = contour - np.array(gravity_point)
vector_length = np.linalg.norm(vector_arr, axis=2)
#### 計算所有的點針對對外擴的像素需要放大多少倍
ratio = 1 + margin / vector_length
ratio = np.concatenate([ratio, ratio], axis=1)
#### 進行坐標的縮放
contour_ext = (vector_arr[:, 0, :] * ratio + np.array(gravity_point)).reshape(-1, 1, 2)
contour_ext = contour_ext.astype(int)
return contour_ext
def coordinate_conversion(reference_point, contour, ratio):
# 對凸多邊形有用,對凹多邊形容易變形,成比例縮放輪廓
"""
:param reference_point: 參照點的坐標
:param contour: 圖像的輪廓點
:param ratio: 縮放的比例
:return: 以參照點不變將輪廓點獲取縮放后的輪廓點坐標
"""
contour_trans_array = (contour - np.array(reference_point)) * ratio + np.array(reference_point)
contour_trans_array = contour_trans_array.astype(int)
return contour_trans_array