使用cv.VideoCapture()时Kivy应用无法检测触摸事件求助
Kivy触摸事件不响应及ROI生成问题修复
问题描述
开发Kivy应用时,需要用户触摸屏幕标记网球场角点以生成ROI,视频可正常显示,但触摸事件完全无响应(print语句未执行),同时存在触摸时读取帧导致帧错位的问题。原Kivy代码如下:
import cv2 from kivy.app import App from kivy.uix.image import Image from kivy.graphics.texture import Texture from kivy.clock import Clock import numpy as np def get_roi(frame, x, y): roi_corners = np.array([[x-50, y-50], [x+50, y-50], [x+50, y+50], [x-50, y+50]]) mask = np.zeros(frame.shape[:2], np.uint8) cv2.drawContours(mask, [roi_corners], 0, (255, 255, 255), -1) return cv2.bitwise_and(frame, frame, mask=mask) class VideoPlayerApp(App): def build(self): self.image = Image(allow_stretch=True, keep_ratio=False) Clock.schedule_interval(self.update, 1.0/30.0) self.cap = cv2.VideoCapture('./partido/06_centro.mp4') return self.image def on_touch_down(self, touch): print('Touch down') if self.image.collide_point(*touch.pos): x, y = touch.pos x = int(x / self.image.width * self.cap.get(cv2.CAP_PROP_FRAME_WIDTH)) y = int(y / self.image.height * self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) ret, frame = self.cap.read() if ret: roi_frame = get_roi(frame, x, y) cv2.imshow('ROI Frame', roi_frame) def update(self, dt): ret, frame = self.cap.read() if ret: buf = cv2.flip(frame, 0).tobytes() image_texture = Texture.create(size=(frame.shape[1], frame.shape[0]), colorfmt='bgr') image_texture.blit_buffer(buf, colorfmt='bgr', bufferfmt='ubyte') self.image.texture = image_texture def on_stop(self): self.cap.release() cv2.destroyAllWindows() if __name__ == '__main__': app = VideoPlayerApp().run()
错误原因
- 触摸事件绑定逻辑错误:将触摸事件放在
App类中处理,未确保事件能正确传递到显示视频的Image组件; - 帧读取错位:
update和on_touch_down方法都调用cap.read(),导致触摸时跳过当前显示帧,读取下一帧,ROI显示内容与触摸画面不符; - 坐标系统不匹配:Kivy的Y轴原点在屏幕底部,OpenCV的Y轴原点在图像顶部,直接转换会导致ROI位置偏移。
修复后的代码
import cv2 from kivy.app import App from kivy.uix.image import Image from kivy.graphics.texture import Texture from kivy.clock import Clock import numpy as np def get_roi(frame, x, y): roi_corners = np.array([[x-50, y-50], [x+50, y-50], [x+50, y+50], [x-50, y+50]]) mask = np.zeros(frame.shape[:2], np.uint8) cv2.drawContours(mask, [roi_corners], 0, (255, 255, 255), -1) return cv2.bitwise_and(frame, frame, mask=mask) class VideoPlayerImage(Image): # 自定义Image组件,直接绑定触摸事件 def __init__(self, cap, **kwargs): super().__init__(**kwargs) self.cap = cap self.current_frame = None # 缓存当前显示的帧 def on_touch_down(self, touch): if self.collide_point(*touch.pos): print('Touch down') if self.current_frame is not None: # 适配Kivy与OpenCV的坐标差异 frame_width = self.cap.get(cv2.CAP_PROP_FRAME_WIDTH) frame_height = self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT) # 转换X坐标 x = int(touch.x / self.width * frame_width) # 反转Y轴,匹配OpenCV的坐标系统 y = int(frame_height - (touch.y / self.height * frame_height)) roi_frame = get_roi(self.current_frame, x, y) cv2.imshow('ROI Frame', roi_frame) return True # 标记事件已处理完成 return super().on_touch_down(touch) class VideoPlayerApp(App): def build(self): self.cap = cv2.VideoCapture('./partido/06_centro.mp4') self.image = VideoPlayerImage(self.cap, allow_stretch=True, keep_ratio=False) Clock.schedule_interval(self.update, 1.0/30.0) return self.image def update(self, dt): ret, frame = self.cap.read() if ret: # 缓存当前帧,供触摸事件使用 self.image.current_frame = frame.copy() # 转换为Kivy支持的纹理格式 buf = cv2.flip(frame, 0).tobytes() image_texture = Texture.create(size=(frame.shape[1], frame.shape[0]), colorfmt='bgr') image_texture.blit_buffer(buf, colorfmt='bgr', bufferfmt='ubyte') self.image.texture = image_texture def on_stop(self): self.cap.release() cv2.destroyAllWindows() if __name__ == '__main__': app = VideoPlayerApp().run()
关键改动说明
- 自定义Image组件:将触摸事件逻辑封装到
VideoPlayerImage类中,直接绑定到显示视频的组件,确保事件被正确捕获; - 缓存当前帧:在
update方法中缓存当前显示的帧,触摸时直接使用缓存帧,避免帧读取错位; - 修正坐标转换:调整Y轴计算逻辑,适配Kivy与OpenCV的坐标系统差异,保证ROI位置准确;
- 事件处理标记:触摸事件处理完成后返回
True,明确标记事件已处理,避免不必要的事件传递。
内容的提问来源于stack exchange,提问作者Rubén Pérez
相关产品推荐
相关产品推荐

