提取圆形ROI并在Tkinter标签中显示圆的半径
我正在向该社区的python专家寻求帮助。我在Stackexchange以及Github社区中搜索了有关我的问题的信息。但是我没有发现任何帮助。我创建了一个Tkinter GUI。在此GUI中,我可以从目标文件夹上载我的图像。在“评估”的“选择”部分,我编写了一个脚本,通过该脚本,可以自动查看圆形部分中的ROI区域。 GUI显示在该问题的底部。
需要帮助的部分:我在创建脚本时遇到了麻烦:
import os
import shutil
import tkinter as tk
from tkinter import filedialog
from tkinter import messagebox
import cv2
import numpy as np
from PIL import Image
from PIL import ImageTk
class Button:
def __init__(self, root, frame3):
self.root = root
self.frame3 = frame3
self.radio_var = tk.IntVar()
self.path_selected = 'none'
self.paths = []
self.radio_handle = []
self.check_value = []
def on_click_select_button(self, fname_label):
print('select button clicked')
fileType = [('jpg/png file', ('*.jpg', '*.png'))]
self.path_selected = filedialog.askopenfilename(filetypes=fileType)
fname_label['text'] = os.path.basename(self.path_selected)
def on_click_upload_button(self, path='None', image='None'):
print('upload button clicked')
if path == 'None':
path = self.path_selected
else:
cv2.imwrite(path, image)
if path in self.paths:
messagebox.showerror('Upload Error', '"'
+ path
+ '"' + ' is already uploaded.')
else:
self.paths.append(path)
self.create_radio_button(path)
def on_click_show_button(self, method):
print('showButton clicked')
image = cv2.imread(self.paths[self.radio_var.get()])
image = self.ROI(image, method)
file_name = os.path.basename(self.paths[self.radio_var.get()])
name, ext = os.path.splitext(file_name)
path = 'Data/images/' + name + '_' + method + ext
self.open_image_window(path, image)
def ROI(self, image, method):
if method == 'ROI':
image = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
blimage = cv2.medianBlur(image, 15)
circles = cv2.HoughCircles(blimage, cv2.HOUGH_GRADIENT, 1, 255, param1=100, param2=60, minRadius=0,
maxRadius=0)
if circles is not None:
circles = np.uint16(np.around(circles))
for i in circles[0, :]:
cv2.circle(image, (i[0], i[1]), i[2], (0, 255, 0), 6)
cv2.circle(image, (i[0], i[1]), 2, (0, 0, 255), 3)
cv2.waitKey()
else:
print('method is wrong')
return image
def create_radio_button(self, path):
image = cv2.imread(path)
# image = cv2.resize(image,(120,120))
image = self.scale_to_height(image, 120)
image_tk = self.to_tk_image(image)
radio_button = tk.Radiobutton(self.frame3, image=image_tk,
value=len(self.radio_handle),
variable=self.radio_var)
self.radio_var.set(0)
self.radio_handle.append(radio_button)
self.check_value.append(self.radio_var)
radio_button.grid(row=(len(self.radio_handle) - 1) // 3,
column=(len(self.radio_handle) - 1) % 3)
self.root.mainloop()
def open_image_window(self, path, image):
if image.shape[0] > 300:
image = self.scale_to_height(image, 300)
img_win = tk.Toplevel(self.root)
fname = os.path.basename(path)
img_win.title(fname)
img_canvas = tk.Canvas(img_win, width=image.shape[1],
height=image.shape[0])
img_canvas.pack()
image_tk = self.to_tk_image(image)
img_canvas.create_image(0, 0, image=image_tk, anchor='nw')
uploadButton2 = tk.Button(img_win, text='Upload ROI',
command=lambda: self.on_click_upload_button(path, image))
uploadButton2.pack()
self.root.mainloop()
def to_tk_image(self, image_bgr):
image_rgb = cv2.cvtColor(image_bgr, cv2.COLOR_BGR2RGB)
image_pil = Image.fromarray(image_rgb)
image_tk = ImageTk.PhotoImage(image_pil)
return image_tk
def scale_to_height(self, img, height):
scale = height / img.shape[0]
return cv2.resize(img, dsize=None, fx=scale, fy=scale)
if __name__ == '__main__':
root = tk.Tk()
root.title('Image GUI')
root.geometry('1280x960')
# scrollbar = Scrollbar(root)
# scrollbar.pack(side='right', fill=Y)
#######################################Reference#########################################################
dir = 'Data/images'
if os.path.exists(dir):
shutil.rmtree(dir)
os.makedirs(dir)
# os.makedirs('Data/images', exist_ok=True)
pw_left = tk.Frame(root, relief='ridge', borderwidth=6)
pw_left.pack(side='left', anchor='nw')
pw_right = tk.Frame(root, relief='ridge', borderwidth=6)
pw_right.pack(side='top', anchor='e')
frame1 = tk.Frame(pw_left, bd=2, relief="ridge")
frame1.pack(fill=tk.X)
frame2 = tk.LabelFrame(pw_left, bd=2, relief="ridge", text='options')
frame2.pack(fill=tk.X)
frame3 = tk.LabelFrame(pw_right, bd=1, text='Uploaded images')
frame3.pack(fill=tk.Y)
button = Button(root, frame3)
label = tk.Label(frame1, text='File:')
label.pack(fill=tk.X)
file_name_label = tk.Label(frame1, text='-----not selected-----', width=50, bg='white')
file_name_label.pack(fill=tk.X)
select_button = tk.Button(frame1, text='select', command=lambda: button.on_click_select_button(file_name_label))
select_button.pack(side='left', padx=5, pady=5)
uploadButton = tk.Button(frame1, text='Upload', command=lambda: button.on_click_upload_button())
uploadButton.pack(side='left', padx=5, pady=5)
# gray button
ROI_label = tk.Label(frame2, text='Select for Evaluation')
ROI_label.grid(row=0, column=0)
ROI_show = tk.Button(frame2, text='show', command=lambda: button.on_click_show_button('ROI'))
ROI_show.grid(row=0, column=1)
root.mainloop()
UPDATE:
我添加了变量border
以计算x1,y1,x2,y2
,因此现在它会以边界线进行裁剪。图片显示了不带border
如果只有一个圆圈(x,y,r)
,则可以使用它来裁剪图像
image = image[y-r:y+r, x-r:x+r]
我在比图像大一圈的图像上进行测试,并且我必须使用int16
而不是unit16
来获得-1
(65535
)的170-171
而不是y-r
。添加我必须使用min()
,max()to get
0instead
-1`
def ROI(self, image, method):
if method == 'ROI':
image = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
blimage = cv2.medianBlur(image, 15)
circles = cv2.HoughCircles(blimage, cv2.HOUGH_GRADIENT, 1, 255, param1=100, param2=60, minRadius=0,
maxRadius=0)
if circles is not None:
#print(circles)
# need `int` instead of `uint` to correctly calculate `y-r` (to get `-1` instead of `65535`)
circles = np.int16(np.around(circles))
for x,y,r in circles[0, :]:
print('x, y, r:', x, y, r)
border = 6
cv2.circle(image, (x, y), r, (0, 255, 0), border)
cv2.circle(image, (x, y), 2, (0, 0, 255), 3)
height, width = image.shape
print('height, width:', height, width)
# calculate region to crop
x1 = max(x-r - border//2, 0) # eventually -border//2-1
x2 = min(x+r + border//2, width) # eventually +border//2+1
y1 = max(y-r - border//2, 0) # eventually -border//2-1
y2 = min(y+r + border//2, height) # eventually +border//2+1
print('x1, x2:', x1, x2)
print('y1, y2:', y1, y2)
# crop image
image = image[y1:y2,x1:x2]
print('height, width:', image.shape)
else:
print('method is wrong')
return image
对于更多的圆,您必须首先计算用于所有圆的区域(获取drom所有圆的最小值x-r
,y-r
和最大值x+r
,y+r
)和下一个裁剪图像。
稍后我将尝试使用Alpha通道删除圆圈外的背景。
用于测试的图像(如果其他人想测试代码)
EDIT:我添加了创建带有白色圆圈的黑色图像以删除背景的代码。
def ROI(self, image, method):
if method == 'ROI':
image = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
blimage = cv2.medianBlur(image, 15)
circles = cv2.HoughCircles(blimage, cv2.HOUGH_GRADIENT, 1, 255, param1=100, param2=60, minRadius=0,
maxRadius=0)
if circles is not None:
print(circles)
circles = np.int16(np.around(circles)) # need int instead of uint to correctly calculate y-r (to get -1 instead of 65535)
for x,y,r in circles[0, :]:
print('x, y, r:', x, y, r)
height, width = image.shape
print('height, width:', height, width)
border = 6
cv2.circle(image, (x, y), r, (0, 255, 0), border)
cv2.circle(image, (x, y), 2, (0, 0, 255), 3)
mask = np.zeros(image.shape, np.uint8) # black background
cv2.circle(mask, (x, y), r, (255), border) # white mask for black border
cv2.circle(mask, (x, y), r, (255), -1) # white mask for (filled) circle
#image = cv2.bitwise_and(image, mask) # image with black background
image = cv2.bitwise_or(image, ~mask) # image with white background
x1 = max(x-r - border//2, 0) # eventually -border//2-1
x2 = min(x+r + border//2, width) # eventually +border//2+1
y1 = max(y-r - border//2, 0) # eventually -border//2-1
y2 = min(y+r + border//2, height) # eventually +border//2+1
print('x1, x2:', x1, x2)
print('y1, y2:', y1, y2)
image = image[y1:y2,x1:x2]
print('height, width:', image.shape)
else:
print('method is wrong')
return image