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

Kivy中如何获取点击位置对应的图像像素坐标

Kivy获取图像像素坐标问题

我正在使用Kivy开发一款应用,旨在在实时视频流上绘制n边形以标记感兴趣区域。当前遇到的问题是Kivy提供的是相对于整个窗口的坐标,而非仅图像的像素坐标。我希望获取点击位置的x、y像素坐标,尝试过to_local()方法但未得到理想效果,以下是最小可复现示例代码:

from kivy.app import App
from kivy.uix.image import Image
from kivy.uix.boxlayout import BoxLayout
from kivy.graphics import Color, Ellipse, Line
from random import random

class ImageView(Image):

    def on_touch_down(self, touch):
        ##
        # This provides touch cords for the entire app and not just the location of the pixel clicked#
        print("Touch Cords", touch.x, touch.y)
        ##
        color = (random(), 1, 1)
        with self.canvas:
            Color(*color, mode='hsv')
            d = 30.
            Ellipse(pos=(touch.x - d / 2, touch.y - d / 2), size=(d, d))
            touch.ud['line'] = Line(points=(touch.x, touch.y))

    def on_touch_move(self, touch):
        touch.ud['line'].points += [touch.x, touch.y]


class DMSApp(App):
    def build(self):     
        imagewidget = ImageView(source="/home/red/Downloads/600.png")
        imagewidget.size_hint = (1, .5)
        imagewidget.pos_hint = {"top": 1}
        layout = BoxLayout(size_hint=(1, 1))
        layout.add_widget(imagewidget)
        return layout

if __name__ == '__main__':
    DMSApp().run()

解决方案

要将窗口坐标转换为图像的像素坐标,需要考虑Image组件的缩放规则和偏移位置(图像默认会保持宽高比居中显示)。修改后的代码如下:

from kivy.app import App
from kivy.uix.image import Image
from kivy.uix.boxlayout import BoxLayout
from kivy.graphics import Color, Ellipse, Line
from random import random

class ImageView(Image):

    def on_touch_down(self, touch):
        # 过滤组件外的触摸事件
        if not self.collide_point(*touch.pos):
            return super().on_touch_down(touch)
        
        # 转换为组件本地坐标(相对于组件左上角)
        local_x, local_y = self.to_local(*touch.pos)
        
        # 获取图像原始像素尺寸
        img_width, img_height = self.norm_image_size
        
        # 计算实际缩放比例(适配保持宽高比的显示逻辑)
        scale_x = self.width / img_width
        scale_y = self.height / img_height
        scale = min(scale_x, scale_y)
        
        # 计算图像在组件内的居中偏移量
        offset_x = (self.width - img_width * scale) / 2
        offset_y = (self.height - img_height * scale) / 2
        
        # 转换为图像像素坐标,反转y轴(Kivy坐标系y轴向上,图像像素y轴向下)
        pixel_x = (local_x - offset_x) / scale
        pixel_y = img_height - (local_y - offset_y) / scale
        
        # 确保坐标在图像有效范围内
        pixel_x = max(0, min(pixel_x, img_width))
        pixel_y = max(0, min(pixel_y, img_height))
        
        print("图像像素坐标", pixel_x, pixel_y)
        
        color = (random(), 1, 1)
        with self.canvas:
            Color(*color, mode='hsv')
            d = 30.
            # 绘制图形仍使用窗口坐标
            Ellipse(pos=(touch.x - d/2, touch.y - d/2), size=(d, d))
            touch.ud['line'] = Line(points=(touch.x, touch.y))
        
        return True

    def on_touch_move(self, touch):
        if 'line' in touch.ud and self.collide_point(*touch.pos):
            touch.ud['line'].points += [touch.x, touch.y]
            return True
        return super().on_touch_move(touch)


class DMSApp(App):
    def build(self):     
        imagewidget = ImageView(source="/home/red/Downloads/600.png")
        imagewidget.size_hint = (1, .5)
        imagewidget.pos_hint = {"top": 1}
        # 显式声明保持宽高比
        imagewidget.allow_stretch = False
        imagewidget.keep_ratio = True
        layout = BoxLayout(size_hint=(1, 1))
        layout.add_widget(imagewidget)
        return layout

if __name__ == '__main__':
    DMSApp().run()

关键说明:

  • self.collide_point(*touch.pos):确保只处理当前Image组件内的触摸事件
  • self.norm_image_size:获取图像的原始像素尺寸,不受缩放影响
  • 缩放比例计算:取宽、高缩放值的最小值,匹配Kivy默认的保持宽高比显示逻辑
  • 偏移量计算:处理图像在组件内居中显示的空白区域,修正坐标起点
  • y轴反转:转换Kivy坐标系与图像像素坐标系的y轴方向差异

内容的提问来源于stack exchange,提问作者Hood Khizer

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 13:25:15