You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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()

错误原因

  1. 触摸事件绑定逻辑错误:将触摸事件放在App类中处理,未确保事件能正确传递到显示视频的Image组件;
  2. 帧读取错位:update和on_touch_down方法都调用cap.read(),导致触摸时跳过当前显示帧,读取下一帧,ROI显示内容与触摸画面不符;
  3. 坐标系统不匹配: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 22:59:57