如何根据物体位置旋转图像?
How can I rotate an image based on object position?
首先,对 post 的长度表示抱歉。
我正在开展一个项目,根据叶子的图像对植物进行分类。为了减少数据的方差,我需要旋转图像,使词干在图像底部水平对齐(270 度)。
目前我在哪里...
到目前为止我所做的是创建一个阈值图像并从那里找到轮廓并在对象周围绘制一个椭圆(在许多情况下它无法涉及整个对象所以茎被遗漏......) ,在那之后,我创建了 4 个区域(带有椭圆的边缘)并尝试计算最小值区域,这是由于假设在任何一个点都必须找到茎,因此它的人口较少区域(主要是因为它将被 0 包围),这显然无法正常工作。
之后我用两种不同的方式计算旋转的角度,第一种涉及到 atan2
函数,这只需要我要移动的点(人口最少的区域的质心) 以及 x=image width / 2
和 y = height
。这种方法在某些情况下有效,但在大多数情况下,我没有得到所需的角度,有时需要一个负角度,但它会产生一个正角度,最终使茎位于顶部。在其他一些情况下,它会以非常糟糕的方式失败。
我的第二种方法是尝试根据 3 个点计算角度:图像中心、人口最少区域的质心和 270º 点。然后使用 arccos
函数,并将其结果转换为度数。
这两种方法对我来说都失败了。
问题
- 您认为这是一种正确的方法还是我只是让事情变得比我应该的更复杂?
- 如何找到叶子的茎(这个不是可选的,必须是茎)?因为我的想法不太行得通...
- 如何可靠地确定角度?因为第二个问题也是一样的原因...
这里有一些示例和我得到的结果(二进制掩码)。矩形表示我正在比较的区域,穿过椭圆的红线是椭圆的长轴,粉红色圆圈是最小区域内的质心,红色圆圈表示 270º 参考点(角度) , 白点代表图像的中心。
我目前的解决方案
def brightness_distortion(I, mu, sigma):
return np.sum(I*mu/sigma**2, axis=-1) / np.sum((mu/sigma)**2, axis=-1)
def chromacity_distortion(I, mu, sigma):
alpha = brightness_distortion(I, mu, sigma)[...,None]
return np.sqrt(np.sum(((I - alpha * mu)/sigma)**2, axis=-1))
def bwareafilt ( image ):
image = image.astype(np.uint8)
nb_components, output, stats, centroids = cv2.connectedComponentsWithStats(image, connectivity=4)
sizes = stats[:, -1]
max_label = 1
max_size = sizes[1]
for i in range(2, nb_components):
if sizes[i] > max_size:
max_label = i
max_size = sizes[i]
img2 = np.zeros(output.shape)
img2[output == max_label] = 255
return img2
def get_thresholded_rotated(im_path):
#read image
img = cv2.imread(im_path)
img = cv2.resize(img, (600, 800), interpolation = Image.BILINEAR)
sat = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)[:,:,1]
val = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)[:,:,2]
sat = cv2.medianBlur(sat, 11)
val = cv2.medianBlur(val, 11)
#create threshold
thresh_S = cv2.adaptiveThreshold(sat , 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 401, 10);
thresh_V = cv2.adaptiveThreshold(val , 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 401, 10);
#mean, std
mean_S, stdev_S = cv2.meanStdDev(img, mask = 255 - thresh_S)
mean_S = mean_S.ravel().flatten()
stdev_S = stdev_S.ravel()
#chromacity
chrom_S = chromacity_distortion(img, mean_S, stdev_S)
chrom255_S = cv2.normalize(chrom_S, chrom_S, alpha=0, beta=255, norm_type=cv2.NORM_MINMAX).astype(np.uint8)[:,:,None]
mean_V, stdev_V = cv2.meanStdDev(img, mask = 255 - thresh_V)
mean_V = mean_V.ravel().flatten()
stdev_V = stdev_V.ravel()
chrom_V = chromacity_distortion(img, mean_V, stdev_V)
chrom255_V = cv2.normalize(chrom_V, chrom_V, alpha=0, beta=255, norm_type=cv2.NORM_MINMAX).astype(np.uint8)[:,:,None]
#create different thresholds
thresh2_S = cv2.adaptiveThreshold(chrom255_S , 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 401, 10);
thresh2_V = cv2.adaptiveThreshold(chrom255_V , 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 401, 10);
#thresholded image
thresh = cv2.bitwise_and(thresh2_S, cv2.bitwise_not(thresh2_V))
#find countours and keep max
contours = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
big_contour = max(contours, key=cv2.contourArea)
# fit ellipse to leaf contours
ellipse = cv2.fitEllipse(big_contour)
(xc,yc), (d1,d2), angle = ellipse
print('thresh shape: ', thresh.shape)
#print(xc,yc,d1,d2,angle)
rmajor = max(d1,d2)/2
rminor = min(d1,d2)/2
origi_angle = angle
if angle > 90:
angle = angle - 90
else:
angle = angle + 90
#calc major axis line
xtop = xc + math.cos(math.radians(angle))*rmajor
ytop = yc + math.sin(math.radians(angle))*rmajor
xbot = xc + math.cos(math.radians(angle+180))*rmajor
ybot = yc + math.sin(math.radians(angle+180))*rmajor
#calc minor axis line
xtop_m = xc + math.cos(math.radians(origi_angle))*rminor
ytop_m = yc + math.sin(math.radians(origi_angle))*rminor
xbot_m = xc + math.cos(math.radians(origi_angle+180))*rminor
ybot_m = yc + math.sin(math.radians(origi_angle+180))*rminor
#determine which region is up and which is down
if max(xtop, xbot) == xtop :
x_tij = xtop
y_tij = ytop
x_b_tij = xbot
y_b_tij = ybot
else:
x_tij = xbot
y_tij = ybot
x_b_tij = xtop
y_b_tij = ytop
if max(xtop_m, xbot_m) == xtop_m :
x_tij_m = xtop_m
y_tij_m = ytop_m
x_b_tij_m = xbot_m
y_b_tij_m = ybot_m
else:
x_tij_m = xbot_m
y_tij_m = ybot_m
x_b_tij_m = xtop_m
y_b_tij_m = ytop_m
print('-----')
print(x_tij, y_tij)
rect_size = 100
"""
calculate regions of edges of major axis of ellipse
this is done by creating a squared region of rect_size x rect_size, being the edge the center of the square
"""
x_min_tij = int(0 if x_tij - rect_size < 0 else x_tij - rect_size)
x_max_tij = int(thresh.shape[1]-1 if x_tij + rect_size > thresh.shape[1] else x_tij + rect_size)
y_min_tij = int(0 if y_tij - rect_size < 0 else y_tij - rect_size)
y_max_tij = int(thresh.shape[0] - 1 if y_tij + rect_size > thresh.shape[0] else y_tij + rect_size)
x_b_min_tij = int(0 if x_b_tij - rect_size < 0 else x_b_tij - rect_size)
x_b_max_tij = int(thresh.shape[1] - 1 if x_b_tij + rect_size > thresh.shape[1] else x_b_tij + rect_size)
y_b_min_tij = int(0 if y_b_tij - rect_size < 0 else y_b_tij - rect_size)
y_b_max_tij = int(thresh.shape[0] - 1 if y_b_tij + rect_size > thresh.shape[0] else y_b_tij + rect_size)
sum_red_region = np.sum(thresh[y_min_tij:y_max_tij, x_min_tij:x_max_tij])
sum_yellow_region = np.sum(thresh[y_b_min_tij:y_b_max_tij, x_b_min_tij:x_b_max_tij])
"""
calculate regions of edges of minor axis of ellipse
this is done by creating a squared region of rect_size x rect_size, being the edge the center of the square
"""
x_min_tij_m = int(0 if x_tij_m - rect_size < 0 else x_tij_m - rect_size)
x_max_tij_m = int(thresh.shape[1]-1 if x_tij_m + rect_size > thresh.shape[1] else x_tij_m + rect_size)
y_min_tij_m = int(0 if y_tij_m - rect_size < 0 else y_tij_m - rect_size)
y_max_tij_m = int(thresh.shape[0] - 1 if y_tij_m + rect_size > thresh.shape[0] else y_tij_m + rect_size)
x_b_min_tij_m = int(0 if x_b_tij_m - rect_size < 0 else x_b_tij_m - rect_size)
x_b_max_tij_m = int(thresh.shape[1] - 1 if x_b_tij_m + rect_size > thresh.shape[1] else x_b_tij_m + rect_size)
y_b_min_tij_m = int(0 if y_b_tij_m - rect_size < 0 else y_b_tij_m - rect_size)
y_b_max_tij_m = int(thresh.shape[0] - 1 if y_b_tij_m + rect_size > thresh.shape[0] else y_b_tij_m + rect_size)
#value of the regions, the names of the variables are related to the color of the rectangles drawn at the end of the function
sum_red_region_m = np.sum(thresh[y_min_tij_m:y_max_tij_m, x_min_tij_m:x_max_tij_m])
sum_yellow_region_m = np.sum(thresh[y_b_min_tij_m:y_b_max_tij_m, x_b_min_tij_m:x_b_max_tij_m])
#print(sum_red_region, sum_yellow_region, sum_red_region_m, sum_yellow_region_m)
min_arg = np.argmin(np.array([sum_red_region, sum_yellow_region, sum_red_region_m, sum_yellow_region_m]))
print('min: ', min_arg)
if min_arg == 1: #sum_yellow_region < sum_red_region :
left_quartile = x_b_tij < thresh.shape[0] /2
upper_quartile = y_b_tij < thresh.shape[1] /2
center_x = x_b_min_tij + ((x_b_max_tij - x_b_min_tij) / 2)
center_y = y_b_min_tij + (y_b_max_tij - y_b_min_tij / 2)
center_x = x_b_min_tij + np.argmax(thresh[y_b_min_tij:y_b_max_tij, x_b_min_tij:x_b_max_tij].mean(axis=0))
center_y = y_b_min_tij + np.argmax(thresh[y_b_min_tij:y_b_max_tij, x_b_min_tij:x_b_max_tij].mean(axis=1))
elif min_arg == 0:
left_quartile = x_tij < thresh.shape[0] /2
upper_quartile = y_tij < thresh.shape[1] /2
center_x = x_min_tij + ((x_b_max_tij - x_b_min_tij) / 2)
center_y = y_min_tij + ((y_b_max_tij - y_b_min_tij) / 2)
center_x = x_min_tij + np.argmax(thresh[y_min_tij:y_max_tij, x_min_tij:x_max_tij].mean(axis=0))
center_y = y_min_tij + np.argmax(thresh[y_min_tij:y_max_tij, x_min_tij:x_max_tij].mean(axis=1))
elif min_arg == 3:
left_quartile = x_b_tij_m < thresh.shape[0] /2
upper_quartile = y_b_tij_m < thresh.shape[1] /2
center_x = x_b_min_tij_m + ((x_b_max_tij_m - x_b_min_tij_m) / 2)
center_y = y_b_min_tij_m + (y_b_max_tij_m - y_b_min_tij_m / 2)
center_x = x_b_min_tij_m + np.argmax(thresh[y_b_min_tij_m:y_b_max_tij_m, x_b_min_tij_m:x_b_max_tij_m].mean(axis=0))
center_y = y_b_min_tij_m + np.argmax(thresh[y_b_min_tij_m:y_b_max_tij_m, x_b_min_tij_m:x_b_max_tij_m].mean(axis=1))
else:
left_quartile = x_tij_m < thresh.shape[0] /2
upper_quartile = y_tij_m < thresh.shape[1] /2
center_x = x_min_tij_m + ((x_b_max_tij_m - x_b_min_tij_m) / 2)
center_y = y_min_tij_m + ((y_b_max_tij_m - y_b_min_tij_m) / 2)
center_x = x_min_tij_m + np.argmax(thresh[y_min_tij_m:y_max_tij_m, x_min_tij_m:x_max_tij_m].mean(axis=0))
center_y = y_min_tij_m + np.argmax(thresh[y_min_tij_m:y_max_tij_m, x_min_tij_m:x_max_tij_m].mean(axis=1))
# draw ellipse on copy of input
result = img.copy()
cv2.ellipse(result, ellipse, (0,0,255), 1)
cv2.line(result, (int(xtop),int(ytop)), (int(xbot),int(ybot)), (255, 0, 0), 1)
cv2.circle(result, (int(xc),int(yc)), 10, (255, 255, 255), -1)
cv2.circle(result, (int(center_x),int(center_y)), 10, (255, 0, 255), 5)
cv2.circle(result, (int(thresh.shape[1] / 2),int(thresh.shape[0] - 1)), 10, (255, 0, 0), 5)
cv2.rectangle(result,(x_min_tij,y_min_tij),(x_max_tij,y_max_tij),(255,0,0),3)
cv2.rectangle(result,(x_b_min_tij,y_b_min_tij),(x_b_max_tij,y_b_max_tij),(255,255,0),3)
cv2.rectangle(result,(x_min_tij_m,y_min_tij_m),(x_max_tij_m,y_max_tij_m),(255,0,0),3)
cv2.rectangle(result,(x_b_min_tij_m,y_b_min_tij_m),(x_b_max_tij_m,y_b_max_tij_m),(255,255,0),3)
plt.imshow(result)
plt.figure()
#rotate the image
rot_img = Image.fromarray(thresh)
#180
bot_point_x = int(thresh.shape[1] / 2)
bot_point_y = int(thresh.shape[0] - 1)
#poi
poi_x = int(center_x)
poi_y = int(center_y)
#image_center
im_center_x = int(thresh.shape[1] / 2)
im_center_y = int(thresh.shape[0] - 1) / 2
#a - adalt, b - abaix, c - dreta
#ba = a - b
#bc = c - a(b en realitat)
ba = np.array([im_center_x, im_center_y]) - np.array([bot_point_x, bot_point_y])
bc = np.array([poi_x, poi_y]) - np.array([im_center_x, im_center_y])
#angle 3 punts
cosine_angle = np.dot(ba, bc) / (np.linalg.norm(ba) * np.linalg.norm(bc))
cos_angle = np.arccos(cosine_angle)
cos_angle = np.degrees(cos_angle)
print('cos angle: ', cos_angle)
print('print: ', abs(poi_x- bot_point_x))
m = (int(thresh.shape[1] / 2)-int(center_x) / int(thresh.shape[0] - 1)-int(center_y))
ttan = math.tan(m)
theta = math.atan(ttan)
print('theta: ', theta)
result = Image.fromarray(result)
result = result.rotate(cos_angle)
plt.imshow(result)
plt.figure()
#rot_img = rot_img.rotate(origi_angle)
rot_img = rot_img.rotate(cos_angle)
return rot_img
rot_img = get_thresholded_rotated(im_path)
plt.imshow(rot_img)
提前致谢
--- 编辑 ---
我根据要求在这里留下了一些原始图像。
sample
这就是我的意思,这需要改进。这会沿顶部边缘每隔 5 个像素绘制一条穿过图像中心的假想线,然后沿左侧边缘每隔 5 个像素绘制一条假想线,将线两侧的像素值相加,并打印最小和最大比率。元组的第四个值应该是旋转的角度。
from PIL import Image
import numpy as np
import math
from pprint import pprint
def rads(degs):
return degs * math.pi / 180.0
clr = Image.open('20210210_155311.jpg').resize((640,480)).convert('L')
data = np.asarray(clr)
def ratio_lr( data, left, right ):
x0 = left
dx = (right-left) / data.shape[0]
lsum = 0
rsum = 0
for row in range(data.shape[0]):
lsum += data[row,:int(x0)].sum()
rsum += data[row,int(x0):].sum()
x0 += dx
return lsum / rsum
def ratio_tb( data, top, bottom ):
y0 = top
dy = (bottom - top) / data.shape[1]
tsum = 0
bsum = 0
for col in range(data.shape[1]):
tsum += data[:int(y0),col].sum()
bsum += data[int(y0):,col].sum()
y0 += dy
return tsum / bsum
midx = data.shape[1] // 2
midy = data.shape[0] // 2
track = []
for dx in range(-midx, midx, 5 ):
if dx == 0:
angle = 90
else:
angle = math.atan( midy / dx ) * 180 / math.pi
track.append( (ratio_lr( data, midx+dx, midx-dx ), dx, 0, angle) )
for dy in range(-midy, midy, 5 ):
angle = math.atan( dy / midx ) * 180 / math.pi
track.append((ratio_tb( data, midy+dy, midy-dy ), 0, dy, angle))
pprint(track)
print(min(track))
print(max(track))
旋转图像。找到最大的轮廓。使用时刻,找到该轮廓的中心。将图像拆分为左右两部分(注意:应用 cv2.blur(img, 5,5))
会产生更好的结果):
翻转右侧。叠加左右两部分:
使用 cv2.absDiff() 测量左和(翻转)右之间的差异。因为叶子是双侧对称的,当叶子的茎(或刺)垂直时,差异最小。
注意:将有两个最小值;一次当杆向上,一次当杆向下...
概念
这适用于大多数叶子,只要它们有茎。所以这里是检测旋转和旋转一张叶子图像的概念:
找到叶子的近似轮廓。由于茎的尖端点通常属于叶子的凸包(外部点),因此找到轮廓的凸包。
遍历属于叶凸包的轮廓索引。对于每个索引,计算 3 个点之间的角度:索引之前轮廓中的点、索引处轮廓中的点和索引之后轮廓中的点。
计算出的最小角度将是杆的尖端。每次循环找到一个较小的角度,将这三个点存储在一个元组中,当检测到最小角度时,使用尖端两侧的 2 个坐标的中心计算杆指向的角度茎和茎尖。
根据检测到的茎的角度,我们可以相应地旋转图像。
代码
import cv2
import numpy as np
def process(img):
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img_blur = cv2.GaussianBlur(img_gray, (3, 3), 2)
img_canny = cv2.Canny(img_blur, 127, 47)
kernel = np.ones((5, 5))
img_dilate = cv2.dilate(img_canny, kernel, iterations=2)
img_erode = cv2.erode(img_dilate, kernel, iterations=1)
return img_erode
def get_contours(img):
contours, _ = cv2.findContours(process(img), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
cnt = max(contours, key=cv2.contourArea)
peri = cv2.arcLength(cnt, True)
return cv2.approxPolyDP(cnt, 0.01 * peri, True)
def get_angle(a, b, c):
ba, bc = a - b, c - b
cos_angle = np.dot(ba, bc) / (np.linalg.norm(ba) * np.linalg.norm(bc))
return np.degrees(np.arccos(cos_angle))
def get_rot_angle(img):
contours = get_contours(img)
length = len(contours)
min_angle = 180
for i in cv2.convexHull(contours, returnPoints=False).ravel():
a, b, c = contours[[i - 1, i, (i + 1) % length], 0]
angle = get_angle(a, b, c)
if angle < min_angle:
min_angle = angle
pts = a, b, c
a, b, c = pts
return 180 - np.degrees(np.arctan2(*(np.mean((a, c), 0) - b)))
def rotate(img):
h, w, _ = img.shape
rot_mat = cv2.getRotationMatrix2D((w / 2, h / 2), get_rot_angle(img), 1)
return cv2.warpAffine(img, rot_mat, (w, h), flags=cv2.INTER_LINEAR)
img = cv2.imread("leaf.jpg")
cv2.imshow("Image", rotate(img))
cv2.waitKey(0)
输出
您提供的每个样本图像的输出:
解释
分解代码:
- 导入必要的库:
import cv2
import numpy as np
- 定义一个函数,
process
,将图像处理成二值图像,使程序能够准确检测树叶的轮廓:
def process(img):
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img_blur = cv2.GaussianBlur(img_gray, (3, 3), 2)
img_canny = cv2.Canny(img_blur, 127, 47)
kernel = np.ones((5, 5))
img_dilate = cv2.dilate(img_canny, kernel, iterations=2)
img_erode = cv2.erode(img_dilate, kernel, iterations=1)
return img_erode
- 定义一个函数,
get_contours
,获取图像中最大轮廓的近似轮廓,使用之前定义的process
函数:
def get_contours(img):
contours, _ = cv2.findContours(process(img), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
cnt = max(contours, key=cv2.contourArea)
peri = cv2.arcLength(cnt, True)
return cv2.approxPolyDP(cnt, 0.01 * peri, True)
- 定义一个函数,
get_angle
,获取 3 个点之间的角度:
def get_angle(a, b, c):
ba, bc = a - b, c - b
cos_angle = np.dot(ba, bc) / (np.linalg.norm(ba) * np.linalg.norm(bc))
return np.degrees(np.arccos(cos_angle))
- 定义一个函数,
get_rot_angle
,以获取图像需要旋转的度数。它通过找到叶子凸包的点来确定这个角度,该点与叶子轮廓中的 2 个周围点之间的角度,其中 3 个点之间的角度最小,使用 get_angle
函数之前定义:
def get_rot_angle(img):
contours = get_contours(img)
length = len(contours)
min_angle = 180
for i in cv2.convexHull(contours, returnPoints=False).ravel():
a, b, c = contours[[i - 1, i, (i + 1) % length], 0]
angle = get_angle(a, b, c)
if angle < min_angle:
min_angle = angle
pts = a, b, c
a, b, c = pts
return 180 - np.degrees(np.arctan2(*(np.mean((a, c), 0) - b)))
- 定义函数
rotate
,使用之前定义的 get_rot_angle
函数沿其中心旋转图像:
def rotate(img):
h, w, _ = img.shape
rot_mat = cv2.getRotationMatrix2D((w / 2, h / 2), get_rot_angle(img), 1)
return cv2.warpAffine(img, rot_mat, (w, h), flags=cv2.INTER_LINEAR)
- 最后,读入你的图像,应用之前定义的
rotate
函数并显示旋转后的图像:
img = cv2.imread("leaf.jpg")
cv2.imshow("Image", rotate(img))
cv2.waitKey(0)
首先,对 post 的长度表示抱歉。
我正在开展一个项目,根据叶子的图像对植物进行分类。为了减少数据的方差,我需要旋转图像,使词干在图像底部水平对齐(270 度)。
目前我在哪里...
到目前为止我所做的是创建一个阈值图像并从那里找到轮廓并在对象周围绘制一个椭圆(在许多情况下它无法涉及整个对象所以茎被遗漏......) ,在那之后,我创建了 4 个区域(带有椭圆的边缘)并尝试计算最小值区域,这是由于假设在任何一个点都必须找到茎,因此它的人口较少区域(主要是因为它将被 0 包围),这显然无法正常工作。
之后我用两种不同的方式计算旋转的角度,第一种涉及到 atan2
函数,这只需要我要移动的点(人口最少的区域的质心) 以及 x=image width / 2
和 y = height
。这种方法在某些情况下有效,但在大多数情况下,我没有得到所需的角度,有时需要一个负角度,但它会产生一个正角度,最终使茎位于顶部。在其他一些情况下,它会以非常糟糕的方式失败。
我的第二种方法是尝试根据 3 个点计算角度:图像中心、人口最少区域的质心和 270º 点。然后使用 arccos
函数,并将其结果转换为度数。
这两种方法对我来说都失败了。
问题
- 您认为这是一种正确的方法还是我只是让事情变得比我应该的更复杂?
- 如何找到叶子的茎(这个不是可选的,必须是茎)?因为我的想法不太行得通...
- 如何可靠地确定角度?因为第二个问题也是一样的原因...
这里有一些示例和我得到的结果(二进制掩码)。矩形表示我正在比较的区域,穿过椭圆的红线是椭圆的长轴,粉红色圆圈是最小区域内的质心,红色圆圈表示 270º 参考点(角度) , 白点代表图像的中心。
我目前的解决方案
def brightness_distortion(I, mu, sigma):
return np.sum(I*mu/sigma**2, axis=-1) / np.sum((mu/sigma)**2, axis=-1)
def chromacity_distortion(I, mu, sigma):
alpha = brightness_distortion(I, mu, sigma)[...,None]
return np.sqrt(np.sum(((I - alpha * mu)/sigma)**2, axis=-1))
def bwareafilt ( image ):
image = image.astype(np.uint8)
nb_components, output, stats, centroids = cv2.connectedComponentsWithStats(image, connectivity=4)
sizes = stats[:, -1]
max_label = 1
max_size = sizes[1]
for i in range(2, nb_components):
if sizes[i] > max_size:
max_label = i
max_size = sizes[i]
img2 = np.zeros(output.shape)
img2[output == max_label] = 255
return img2
def get_thresholded_rotated(im_path):
#read image
img = cv2.imread(im_path)
img = cv2.resize(img, (600, 800), interpolation = Image.BILINEAR)
sat = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)[:,:,1]
val = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)[:,:,2]
sat = cv2.medianBlur(sat, 11)
val = cv2.medianBlur(val, 11)
#create threshold
thresh_S = cv2.adaptiveThreshold(sat , 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 401, 10);
thresh_V = cv2.adaptiveThreshold(val , 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 401, 10);
#mean, std
mean_S, stdev_S = cv2.meanStdDev(img, mask = 255 - thresh_S)
mean_S = mean_S.ravel().flatten()
stdev_S = stdev_S.ravel()
#chromacity
chrom_S = chromacity_distortion(img, mean_S, stdev_S)
chrom255_S = cv2.normalize(chrom_S, chrom_S, alpha=0, beta=255, norm_type=cv2.NORM_MINMAX).astype(np.uint8)[:,:,None]
mean_V, stdev_V = cv2.meanStdDev(img, mask = 255 - thresh_V)
mean_V = mean_V.ravel().flatten()
stdev_V = stdev_V.ravel()
chrom_V = chromacity_distortion(img, mean_V, stdev_V)
chrom255_V = cv2.normalize(chrom_V, chrom_V, alpha=0, beta=255, norm_type=cv2.NORM_MINMAX).astype(np.uint8)[:,:,None]
#create different thresholds
thresh2_S = cv2.adaptiveThreshold(chrom255_S , 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 401, 10);
thresh2_V = cv2.adaptiveThreshold(chrom255_V , 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 401, 10);
#thresholded image
thresh = cv2.bitwise_and(thresh2_S, cv2.bitwise_not(thresh2_V))
#find countours and keep max
contours = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
big_contour = max(contours, key=cv2.contourArea)
# fit ellipse to leaf contours
ellipse = cv2.fitEllipse(big_contour)
(xc,yc), (d1,d2), angle = ellipse
print('thresh shape: ', thresh.shape)
#print(xc,yc,d1,d2,angle)
rmajor = max(d1,d2)/2
rminor = min(d1,d2)/2
origi_angle = angle
if angle > 90:
angle = angle - 90
else:
angle = angle + 90
#calc major axis line
xtop = xc + math.cos(math.radians(angle))*rmajor
ytop = yc + math.sin(math.radians(angle))*rmajor
xbot = xc + math.cos(math.radians(angle+180))*rmajor
ybot = yc + math.sin(math.radians(angle+180))*rmajor
#calc minor axis line
xtop_m = xc + math.cos(math.radians(origi_angle))*rminor
ytop_m = yc + math.sin(math.radians(origi_angle))*rminor
xbot_m = xc + math.cos(math.radians(origi_angle+180))*rminor
ybot_m = yc + math.sin(math.radians(origi_angle+180))*rminor
#determine which region is up and which is down
if max(xtop, xbot) == xtop :
x_tij = xtop
y_tij = ytop
x_b_tij = xbot
y_b_tij = ybot
else:
x_tij = xbot
y_tij = ybot
x_b_tij = xtop
y_b_tij = ytop
if max(xtop_m, xbot_m) == xtop_m :
x_tij_m = xtop_m
y_tij_m = ytop_m
x_b_tij_m = xbot_m
y_b_tij_m = ybot_m
else:
x_tij_m = xbot_m
y_tij_m = ybot_m
x_b_tij_m = xtop_m
y_b_tij_m = ytop_m
print('-----')
print(x_tij, y_tij)
rect_size = 100
"""
calculate regions of edges of major axis of ellipse
this is done by creating a squared region of rect_size x rect_size, being the edge the center of the square
"""
x_min_tij = int(0 if x_tij - rect_size < 0 else x_tij - rect_size)
x_max_tij = int(thresh.shape[1]-1 if x_tij + rect_size > thresh.shape[1] else x_tij + rect_size)
y_min_tij = int(0 if y_tij - rect_size < 0 else y_tij - rect_size)
y_max_tij = int(thresh.shape[0] - 1 if y_tij + rect_size > thresh.shape[0] else y_tij + rect_size)
x_b_min_tij = int(0 if x_b_tij - rect_size < 0 else x_b_tij - rect_size)
x_b_max_tij = int(thresh.shape[1] - 1 if x_b_tij + rect_size > thresh.shape[1] else x_b_tij + rect_size)
y_b_min_tij = int(0 if y_b_tij - rect_size < 0 else y_b_tij - rect_size)
y_b_max_tij = int(thresh.shape[0] - 1 if y_b_tij + rect_size > thresh.shape[0] else y_b_tij + rect_size)
sum_red_region = np.sum(thresh[y_min_tij:y_max_tij, x_min_tij:x_max_tij])
sum_yellow_region = np.sum(thresh[y_b_min_tij:y_b_max_tij, x_b_min_tij:x_b_max_tij])
"""
calculate regions of edges of minor axis of ellipse
this is done by creating a squared region of rect_size x rect_size, being the edge the center of the square
"""
x_min_tij_m = int(0 if x_tij_m - rect_size < 0 else x_tij_m - rect_size)
x_max_tij_m = int(thresh.shape[1]-1 if x_tij_m + rect_size > thresh.shape[1] else x_tij_m + rect_size)
y_min_tij_m = int(0 if y_tij_m - rect_size < 0 else y_tij_m - rect_size)
y_max_tij_m = int(thresh.shape[0] - 1 if y_tij_m + rect_size > thresh.shape[0] else y_tij_m + rect_size)
x_b_min_tij_m = int(0 if x_b_tij_m - rect_size < 0 else x_b_tij_m - rect_size)
x_b_max_tij_m = int(thresh.shape[1] - 1 if x_b_tij_m + rect_size > thresh.shape[1] else x_b_tij_m + rect_size)
y_b_min_tij_m = int(0 if y_b_tij_m - rect_size < 0 else y_b_tij_m - rect_size)
y_b_max_tij_m = int(thresh.shape[0] - 1 if y_b_tij_m + rect_size > thresh.shape[0] else y_b_tij_m + rect_size)
#value of the regions, the names of the variables are related to the color of the rectangles drawn at the end of the function
sum_red_region_m = np.sum(thresh[y_min_tij_m:y_max_tij_m, x_min_tij_m:x_max_tij_m])
sum_yellow_region_m = np.sum(thresh[y_b_min_tij_m:y_b_max_tij_m, x_b_min_tij_m:x_b_max_tij_m])
#print(sum_red_region, sum_yellow_region, sum_red_region_m, sum_yellow_region_m)
min_arg = np.argmin(np.array([sum_red_region, sum_yellow_region, sum_red_region_m, sum_yellow_region_m]))
print('min: ', min_arg)
if min_arg == 1: #sum_yellow_region < sum_red_region :
left_quartile = x_b_tij < thresh.shape[0] /2
upper_quartile = y_b_tij < thresh.shape[1] /2
center_x = x_b_min_tij + ((x_b_max_tij - x_b_min_tij) / 2)
center_y = y_b_min_tij + (y_b_max_tij - y_b_min_tij / 2)
center_x = x_b_min_tij + np.argmax(thresh[y_b_min_tij:y_b_max_tij, x_b_min_tij:x_b_max_tij].mean(axis=0))
center_y = y_b_min_tij + np.argmax(thresh[y_b_min_tij:y_b_max_tij, x_b_min_tij:x_b_max_tij].mean(axis=1))
elif min_arg == 0:
left_quartile = x_tij < thresh.shape[0] /2
upper_quartile = y_tij < thresh.shape[1] /2
center_x = x_min_tij + ((x_b_max_tij - x_b_min_tij) / 2)
center_y = y_min_tij + ((y_b_max_tij - y_b_min_tij) / 2)
center_x = x_min_tij + np.argmax(thresh[y_min_tij:y_max_tij, x_min_tij:x_max_tij].mean(axis=0))
center_y = y_min_tij + np.argmax(thresh[y_min_tij:y_max_tij, x_min_tij:x_max_tij].mean(axis=1))
elif min_arg == 3:
left_quartile = x_b_tij_m < thresh.shape[0] /2
upper_quartile = y_b_tij_m < thresh.shape[1] /2
center_x = x_b_min_tij_m + ((x_b_max_tij_m - x_b_min_tij_m) / 2)
center_y = y_b_min_tij_m + (y_b_max_tij_m - y_b_min_tij_m / 2)
center_x = x_b_min_tij_m + np.argmax(thresh[y_b_min_tij_m:y_b_max_tij_m, x_b_min_tij_m:x_b_max_tij_m].mean(axis=0))
center_y = y_b_min_tij_m + np.argmax(thresh[y_b_min_tij_m:y_b_max_tij_m, x_b_min_tij_m:x_b_max_tij_m].mean(axis=1))
else:
left_quartile = x_tij_m < thresh.shape[0] /2
upper_quartile = y_tij_m < thresh.shape[1] /2
center_x = x_min_tij_m + ((x_b_max_tij_m - x_b_min_tij_m) / 2)
center_y = y_min_tij_m + ((y_b_max_tij_m - y_b_min_tij_m) / 2)
center_x = x_min_tij_m + np.argmax(thresh[y_min_tij_m:y_max_tij_m, x_min_tij_m:x_max_tij_m].mean(axis=0))
center_y = y_min_tij_m + np.argmax(thresh[y_min_tij_m:y_max_tij_m, x_min_tij_m:x_max_tij_m].mean(axis=1))
# draw ellipse on copy of input
result = img.copy()
cv2.ellipse(result, ellipse, (0,0,255), 1)
cv2.line(result, (int(xtop),int(ytop)), (int(xbot),int(ybot)), (255, 0, 0), 1)
cv2.circle(result, (int(xc),int(yc)), 10, (255, 255, 255), -1)
cv2.circle(result, (int(center_x),int(center_y)), 10, (255, 0, 255), 5)
cv2.circle(result, (int(thresh.shape[1] / 2),int(thresh.shape[0] - 1)), 10, (255, 0, 0), 5)
cv2.rectangle(result,(x_min_tij,y_min_tij),(x_max_tij,y_max_tij),(255,0,0),3)
cv2.rectangle(result,(x_b_min_tij,y_b_min_tij),(x_b_max_tij,y_b_max_tij),(255,255,0),3)
cv2.rectangle(result,(x_min_tij_m,y_min_tij_m),(x_max_tij_m,y_max_tij_m),(255,0,0),3)
cv2.rectangle(result,(x_b_min_tij_m,y_b_min_tij_m),(x_b_max_tij_m,y_b_max_tij_m),(255,255,0),3)
plt.imshow(result)
plt.figure()
#rotate the image
rot_img = Image.fromarray(thresh)
#180
bot_point_x = int(thresh.shape[1] / 2)
bot_point_y = int(thresh.shape[0] - 1)
#poi
poi_x = int(center_x)
poi_y = int(center_y)
#image_center
im_center_x = int(thresh.shape[1] / 2)
im_center_y = int(thresh.shape[0] - 1) / 2
#a - adalt, b - abaix, c - dreta
#ba = a - b
#bc = c - a(b en realitat)
ba = np.array([im_center_x, im_center_y]) - np.array([bot_point_x, bot_point_y])
bc = np.array([poi_x, poi_y]) - np.array([im_center_x, im_center_y])
#angle 3 punts
cosine_angle = np.dot(ba, bc) / (np.linalg.norm(ba) * np.linalg.norm(bc))
cos_angle = np.arccos(cosine_angle)
cos_angle = np.degrees(cos_angle)
print('cos angle: ', cos_angle)
print('print: ', abs(poi_x- bot_point_x))
m = (int(thresh.shape[1] / 2)-int(center_x) / int(thresh.shape[0] - 1)-int(center_y))
ttan = math.tan(m)
theta = math.atan(ttan)
print('theta: ', theta)
result = Image.fromarray(result)
result = result.rotate(cos_angle)
plt.imshow(result)
plt.figure()
#rot_img = rot_img.rotate(origi_angle)
rot_img = rot_img.rotate(cos_angle)
return rot_img
rot_img = get_thresholded_rotated(im_path)
plt.imshow(rot_img)
提前致谢 --- 编辑 ---
我根据要求在这里留下了一些原始图像。
sample
这就是我的意思,这需要改进。这会沿顶部边缘每隔 5 个像素绘制一条穿过图像中心的假想线,然后沿左侧边缘每隔 5 个像素绘制一条假想线,将线两侧的像素值相加,并打印最小和最大比率。元组的第四个值应该是旋转的角度。
from PIL import Image
import numpy as np
import math
from pprint import pprint
def rads(degs):
return degs * math.pi / 180.0
clr = Image.open('20210210_155311.jpg').resize((640,480)).convert('L')
data = np.asarray(clr)
def ratio_lr( data, left, right ):
x0 = left
dx = (right-left) / data.shape[0]
lsum = 0
rsum = 0
for row in range(data.shape[0]):
lsum += data[row,:int(x0)].sum()
rsum += data[row,int(x0):].sum()
x0 += dx
return lsum / rsum
def ratio_tb( data, top, bottom ):
y0 = top
dy = (bottom - top) / data.shape[1]
tsum = 0
bsum = 0
for col in range(data.shape[1]):
tsum += data[:int(y0),col].sum()
bsum += data[int(y0):,col].sum()
y0 += dy
return tsum / bsum
midx = data.shape[1] // 2
midy = data.shape[0] // 2
track = []
for dx in range(-midx, midx, 5 ):
if dx == 0:
angle = 90
else:
angle = math.atan( midy / dx ) * 180 / math.pi
track.append( (ratio_lr( data, midx+dx, midx-dx ), dx, 0, angle) )
for dy in range(-midy, midy, 5 ):
angle = math.atan( dy / midx ) * 180 / math.pi
track.append((ratio_tb( data, midy+dy, midy-dy ), 0, dy, angle))
pprint(track)
print(min(track))
print(max(track))
旋转图像。找到最大的轮廓。使用时刻,找到该轮廓的中心。将图像拆分为左右两部分(注意:应用 cv2.blur(img, 5,5))
会产生更好的结果):
翻转右侧。叠加左右两部分:
使用 cv2.absDiff() 测量左和(翻转)右之间的差异。因为叶子是双侧对称的,当叶子的茎(或刺)垂直时,差异最小。
注意:将有两个最小值;一次当杆向上,一次当杆向下...
概念
这适用于大多数叶子,只要它们有茎。所以这里是检测旋转和旋转一张叶子图像的概念:
找到叶子的近似轮廓。由于茎的尖端点通常属于叶子的凸包(外部点),因此找到轮廓的凸包。
遍历属于叶凸包的轮廓索引。对于每个索引,计算 3 个点之间的角度:索引之前轮廓中的点、索引处轮廓中的点和索引之后轮廓中的点。
计算出的最小角度将是杆的尖端。每次循环找到一个较小的角度,将这三个点存储在一个元组中,当检测到最小角度时,使用尖端两侧的 2 个坐标的中心计算杆指向的角度茎和茎尖。
根据检测到的茎的角度,我们可以相应地旋转图像。
代码
import cv2
import numpy as np
def process(img):
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img_blur = cv2.GaussianBlur(img_gray, (3, 3), 2)
img_canny = cv2.Canny(img_blur, 127, 47)
kernel = np.ones((5, 5))
img_dilate = cv2.dilate(img_canny, kernel, iterations=2)
img_erode = cv2.erode(img_dilate, kernel, iterations=1)
return img_erode
def get_contours(img):
contours, _ = cv2.findContours(process(img), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
cnt = max(contours, key=cv2.contourArea)
peri = cv2.arcLength(cnt, True)
return cv2.approxPolyDP(cnt, 0.01 * peri, True)
def get_angle(a, b, c):
ba, bc = a - b, c - b
cos_angle = np.dot(ba, bc) / (np.linalg.norm(ba) * np.linalg.norm(bc))
return np.degrees(np.arccos(cos_angle))
def get_rot_angle(img):
contours = get_contours(img)
length = len(contours)
min_angle = 180
for i in cv2.convexHull(contours, returnPoints=False).ravel():
a, b, c = contours[[i - 1, i, (i + 1) % length], 0]
angle = get_angle(a, b, c)
if angle < min_angle:
min_angle = angle
pts = a, b, c
a, b, c = pts
return 180 - np.degrees(np.arctan2(*(np.mean((a, c), 0) - b)))
def rotate(img):
h, w, _ = img.shape
rot_mat = cv2.getRotationMatrix2D((w / 2, h / 2), get_rot_angle(img), 1)
return cv2.warpAffine(img, rot_mat, (w, h), flags=cv2.INTER_LINEAR)
img = cv2.imread("leaf.jpg")
cv2.imshow("Image", rotate(img))
cv2.waitKey(0)
输出
您提供的每个样本图像的输出:
解释
分解代码:
- 导入必要的库:
import cv2
import numpy as np
- 定义一个函数,
process
,将图像处理成二值图像,使程序能够准确检测树叶的轮廓:
def process(img):
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img_blur = cv2.GaussianBlur(img_gray, (3, 3), 2)
img_canny = cv2.Canny(img_blur, 127, 47)
kernel = np.ones((5, 5))
img_dilate = cv2.dilate(img_canny, kernel, iterations=2)
img_erode = cv2.erode(img_dilate, kernel, iterations=1)
return img_erode
- 定义一个函数,
get_contours
,获取图像中最大轮廓的近似轮廓,使用之前定义的process
函数:
def get_contours(img):
contours, _ = cv2.findContours(process(img), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
cnt = max(contours, key=cv2.contourArea)
peri = cv2.arcLength(cnt, True)
return cv2.approxPolyDP(cnt, 0.01 * peri, True)
- 定义一个函数,
get_angle
,获取 3 个点之间的角度:
def get_angle(a, b, c):
ba, bc = a - b, c - b
cos_angle = np.dot(ba, bc) / (np.linalg.norm(ba) * np.linalg.norm(bc))
return np.degrees(np.arccos(cos_angle))
- 定义一个函数,
get_rot_angle
,以获取图像需要旋转的度数。它通过找到叶子凸包的点来确定这个角度,该点与叶子轮廓中的 2 个周围点之间的角度,其中 3 个点之间的角度最小,使用get_angle
函数之前定义:
def get_rot_angle(img):
contours = get_contours(img)
length = len(contours)
min_angle = 180
for i in cv2.convexHull(contours, returnPoints=False).ravel():
a, b, c = contours[[i - 1, i, (i + 1) % length], 0]
angle = get_angle(a, b, c)
if angle < min_angle:
min_angle = angle
pts = a, b, c
a, b, c = pts
return 180 - np.degrees(np.arctan2(*(np.mean((a, c), 0) - b)))
- 定义函数
rotate
,使用之前定义的get_rot_angle
函数沿其中心旋转图像:
def rotate(img):
h, w, _ = img.shape
rot_mat = cv2.getRotationMatrix2D((w / 2, h / 2), get_rot_angle(img), 1)
return cv2.warpAffine(img, rot_mat, (w, h), flags=cv2.INTER_LINEAR)
- 最后,读入你的图像,应用之前定义的
rotate
函数并显示旋转后的图像:
img = cv2.imread("leaf.jpg")
cv2.imshow("Image", rotate(img))
cv2.waitKey(0)