如何在Kivy中对图像的指定绘制区域应用OpenCV滤镜?
Kivy图像编辑:在选定区域应用OpenCV滤镜问题
我正在开发一款Kivy应用,该应用可显示图像并允许用户使用Kivy的Rectangle属性在图像上绘制矩形。以下是实现此功能的代码:
from kivy.app import App from kivy.uix.widget import Widget from kivy.graphics import Color, Rectangle from kivy.uix.image import Image class CanvasExample(Widget): def __init__(self, **kwargs): super().__init__(**kwargs) self.image = Image(source='sampleImg.jpg', size_hint=(None, None), size=(800, 600)) self.add_widget(self.image) # Initialize variables self.start_pos = None self.rectangles = [] def on_touch_down(self, touch): if self.collide_point(*touch.pos): self.start_pos = touch.pos def on_touch_up(self, touch): if self.collide_point(*touch.pos): if self.start_pos is not None: # Compute the rectangle x, y = self.start_pos width = touch.pos[0] - x height = touch.pos[1] - y rect = Rectangle(pos=(x, y), size=(width, height)) # Add the rectangle to the list self.rectangles.append(rect) # Add the rectangle to the image with self.image.canvas: Color(1, 0, 0, 0.13) # red color Rectangle(pos=(x, y), size=(width, height)) self.start_pos = None class TheLab(App): def build(self): return CanvasExample() if __name__ == '__main__': TheLab().run()
现在我希望隔离绘制的特定区域,并对其应用OpenCV滤镜(如Canny边缘检测),更新图像使该区域显示滤镜处理后的效果,其余部分保持原样。我尝试通过位置属性隔离该区域,但得到了负坐标,尝试的代码如下:
def on_touch_up(self, touch): if self.selected_rect: # Get coordinates of selected rectangle x, y = self.selected_rect.pos w, h = self.selected_rect.size x, y, w, h = int(x), int(y), int(w), int(h) # Extract portion of image within the selected rectangle img_subset = self.img[y:y+h, x:x+w] # Apply canny edge detection to the subset edges = cv2.Canny(img_subset, 100, 200) # Create mask for edges edge_mask = np.zeros((h, w, 3), dtype=np.uint8) edge_mask[:, :, 0] = edges edge_mask[:, :, 1] = edges edge_mask[:, :, 2] = edges # Replace subset in original image with the edge mask self.img[y:y+h, x:x+w] = np.where(edge_mask > 0, edge_mask, img_subset) # Update Kivy texture and Image widget with modified image self.texture = self.convert_to_texture(self.img) self.image.texture = self.texture # Reset selected rectangle self.selected_rect = None
解决方法
问题核心在于矩形坐标的正负处理和Kivy与OpenCV的坐标系差异,以下是修正后的完整实现:
完整代码
from kivy.app import App from kivy.uix.widget import Widget from kivy.graphics import Color, Rectangle from kivy.uix.image import Image from kivy.graphics.texture import Texture import cv2 import numpy as np class CanvasExample(Widget): def __init__(self, **kwargs): super().__init__(**kwargs) # 用OpenCV加载图像,保存原始副本避免多次修改失真 self.original_img = cv2.imread('sampleImg.jpg') self.img = self.original_img.copy() img_h, img_w = self.img.shape[:2] # 初始化Kivy Image组件,匹配原始图像尺寸 self.image = Image(size_hint=(None, None), size=(img_w, img_h)) self.image.texture = self.convert_to_texture(self.img) self.add_widget(self.image) # 初始化变量 self.start_pos = None self.rectangles = [] def convert_to_texture(self, cv_img): # 转换OpenCV BGR图像到Kivy支持的纹理格式 buf = cv2.flip(cv_img, 0).tostring() texture = Texture.create(size=(cv_img.shape[1], cv_img.shape[0]), colorfmt='bgr') texture.blit_buffer(buf, colorfmt='bgr', bufferfmt='ubyte') return texture def on_touch_down(self, touch): if self.image.collide_point(*touch.pos): # 转换触摸坐标为图像组件内部的本地坐标 local_x = touch.pos[0] - self.image.x local_y = touch.pos[1] - self.image.y self.start_pos = (local_x, local_y) def on_touch_up(self, touch): if self.image.collide_point(*touch.pos) and self.start_pos is not None: # 获取触摸结束的本地坐标 end_x = touch.pos[0] - self.image.x end_y = touch.pos[1] - self.image.y # 计算正确的矩形左上角、右下角坐标,确保宽高为正 x1 = min(self.start_pos[0], end_x) y1 = min(self.start_pos[1], end_y) x2 = max(self.start_pos[0], end_x) y2 = max(self.start_pos[1], end_y) # 转换为整数坐标 x1, y1, x2, y2 = int(x1), int(y1), int(x2), int(y2) w = x2 - x1 h = y2 - y1 # 转换Kivy坐标系到OpenCV坐标系(Kivy y轴从下往上,OpenCV从上往下) img_h = self.img.shape[0] cv_y1 = img_h - y2 cv_y2 = img_h - y1 # 提取目标区域图像 img_subset = self.img[cv_y1:cv_y2, x1:x2] # 应用Canny边缘检测并转换为3通道格式 edges = cv2.Canny(img_subset, 100, 200) edge_3ch = cv2.cvtColor(edges, cv2.COLOR_GRAY2BGR) # 将处理后的区域放回原图像 self.img[cv_y1:cv_y2, x1:x2] = edge_3ch # 更新Kivy图像纹理 self.image.texture = self.convert_to_texture(self.img) # 在图像上绘制半透明红色矩形标记 with self.image.canvas: Color(1, 0, 0, 0.13) Rectangle(pos=(self.image.x + x1, self.image.y + y1), size=(w, h)) self.start_pos = None class TheLab(App): def build(self): return CanvasExample() if __name__ == '__main__': TheLab().run()
关键修正点
坐标标准化处理
- 用
min和max计算矩形的左上角与右下角,确保宽高为正,彻底解决负索引问题。 - 将触摸坐标转换为图像组件内部的本地坐标,排除布局偏移带来的误差。
- 用
坐标系转换
- Kivy的y轴原点在屏幕底部,OpenCV的y轴原点在图像顶部,通过
img_h - y完成坐标映射,保证提取区域准确。
- Kivy的y轴原点在屏幕底部,OpenCV的y轴原点在图像顶部,通过
图像加载与更新优化
- 直接用OpenCV加载原始图像并保存副本,避免多次修改导致图像失真。
- 实现
convert_to_texture函数,完成OpenCV图像到Kivy纹理的格式转换(含图像翻转与颜色通道适配)。
滤镜应用适配
- 将单通道的Canny检测结果转换为3通道图像,确保和原图像格式一致,避免拼接时的维度错误。
内容的提问来源于stack exchange,提问作者Gaurav
相关产品推荐
相关产品推荐

