如何基于对象位置旋转图像?

2024-10-06 11:19:13 发布

您现在位置:Python中文网/ 问答频道 /正文

首先,对不起,这篇文章太长了

我正在做一个项目,根据叶子的图像对植物进行分类。为了减少数据的差异,我需要旋转图像,使茎在图像底部水平对齐(270度)

到目前为止我所处的位置…

到目前为止,我所做的是创建一个阈值图像,然后从中找到轮廓并围绕对象绘制一个椭圆(在许多情况下,它无法涉及整个对象,因此忽略了茎…),然后,我创建4个区域(带有椭圆的边),并尝试计算最小值区域,这是因为假设在任何一点必须找到茎,因此它将是人口较少的区域(主要是因为它将被0包围),这显然不是我想要的工作

之后,我以两种不同的方式计算旋转角度,第一种涉及atan2函数,这只需要我想从(人口最少区域的质心)移动的点,以及x=image width / 2y = height的点。这种方法在某些情况下有效,但在大多数情况下,我没有得到所需的角度,有时需要一个负角度,它会产生一个正角度,最终茎在顶部。在其他一些情况下,它只是以一种可怕的方式失败

我的第二种方法是尝试根据3个点计算角度:图像中心、人口最少区域的重心和270º点。然后使用arccos函数,并将其结果转换为度

这两种方法对我来说都失败了

问题

  • 你认为这是一个正确的方法,还是我只是把事情弄得比我应该做的更复杂
  • 我如何找到叶子的茎(这不是可选的,它必须是茎)?因为我的想法不太管用
  • 如何以稳健的方式确定角度?因为第二个问题中的相同原因

下面是一些示例和我得到的结果(二进制掩码)。矩形表示我正在比较的区域,穿过椭圆的红线是椭圆的长轴,粉色圆圈是最小区域内的质心,红色圆圈表示270º参考点(角度),白色圆点表示图像的中心

Original imageenter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

我当前的解决方案

    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

sample

sample

sample

sample

sample

sample

sample

sample

sample

sample

sample

sample

sample

sample

sample

sample

sample

sample

sample

sample

sample

sample


Tags: rectsizeifnpmincv2elsemax
3条回答

这就是我的意思,这需要改进。这将沿上边缘每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))

概念

这适用于大多数叶子,只要它们有茎。因此,以下是检测旋转和旋转一个叶片图像的概念:

  1. 找到叶片的近似轮廓。由于茎尖点通常属于叶片的凸面外壳(外部点),请找到轮廓的凸面外壳

  2. 循环通过属于叶片凸面外壳的轮廓索引。对于每个索引,计算3个点之间的角度:索引前等高线中的点、索引处等高线中的点和索引后等高线中的点

  3. 计算出的最小角度是茎尖。每次循环找到较小的角度时,将三个点存储在一个元组中,当检测到最小角度时,使用茎尖两侧的两个坐标中心和茎尖计算茎所指向的角度

  4. 检测到茎的角度后,我们可以相应地旋转图像

代码

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)

输出

您提供的每个示例图像的输出:

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

解释

分解代码:

  1. 导入必要的库:
import cv2
import numpy as np
  1. 定义一个函数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
  1. 使用前面定义的process函数定义函数get_contours,以获得图像中最大轮廓的近似轮廓:
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)
  1. 定义一个函数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))
  1. 定义函数get_rot_angle,以获取图像需要旋转的度数。它通过使用前面定义的get_angle函数,找到叶片凸包的点,其中该点与叶片轮廓中的2个周围点之间的角度最小,其中3个点之间的角度最小,从而确定该角度:
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)))
  1. 使用前面定义的get_rot_angle函数,定义一个函数rotate沿图像中心旋转图像:
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)
  1. 最后,读入图像,应用前面定义的rotate函数并显示旋转后的图像:
img = cv2.imread("leaf.jpg")
cv2.imshow("Image", rotate(img))
cv2.waitKey(0)

Bilateral Symmetry

旋转图像。找到最大的轮廓。使用矩,找到该轮廓的中心。将图像分成左右两部分(注意:应用cv2.blur(img, 5,5))会产生更好的结果):

rotated leaf

向右翻转。覆盖左右部分:

overlay

使用cv2.absDiff()测量左侧和(翻转)右侧之间的差异。因为叶子具有双侧对称性,当叶子的茎(或刺)垂直时,差异将最小

注:有两个最小值;一次阀杆向上,一次阀杆向下

plot

相关问题 更多 >