Python 为什么kivy read_像素没有返回预期颜色?

Python 为什么kivy read_像素没有返回预期颜色?,python,kivy,Python,Kivy,我正在尝试在图像上创建标记,允许用户选择颜色、标记功能等。最终,我希望通过opencv获得相应的图像像素以供进一步使用 我在触摸下获得期望的颜色时遇到了很多麻烦,有时它会返回像洋红这样的颜色,这些颜色甚至不在示例图像中 我很确定问题在于我如何将触摸位置转换为我正在传递给read_pixel函数的值 我尝试了许多不同的解决方案,但都没有成功,所以我认为我在这里缺少了一些东西 main.py from kivy.app import App from kivy.properties import L

我正在尝试在图像上创建标记,允许用户选择颜色、标记功能等。最终,我希望通过opencv获得相应的图像像素以供进一步使用

我在触摸下获得期望的颜色时遇到了很多麻烦,有时它会返回像洋红这样的颜色,这些颜色甚至不在示例图像中

我很确定问题在于我如何将触摸位置转换为我正在传递给read_pixel函数的值

我尝试了许多不同的解决方案,但都没有成功,所以我认为我在这里缺少了一些东西

main.py

from kivy.app import App
from kivy.properties import ListProperty, ObjectProperty
from kivy.uix.image import AsyncImage
from kivy.uix.relativelayout import RelativeLayout
from kivy.uix.widget import Widget
from kivy.uix.screenmanager import ScreenManager, Screen


class Marker(Widget):
    selected_color = ListProperty([0,1,0])

    def __init__(self, **kwargs):
        super(Marker, self).__init__(**kwargs)
        self.selected_pos = None

    def on_touch_down(self, touch):
        if self.collide_point(*touch.pos):
            print("Touched at Marker: {0}".format(touch.spos))

    def on_touch_move(self, touch):
        self.set_position_from_touch(touch.spos)

    def set_position_from_touch(self, spos):
        # print("touch: {0}".format(touch))
        self.image = self.parent.parent.image
        x = spos[0] * self.image.width
        y = spos[1] * self.image.height

        # setting position of the widget relative to touch
        self.pos = (x-self.width/2, y-self.height*(2/3))
        # self.pos = (x, y)

        print("widget position : {0}".format(self.pos))
        # converting widget position to pixel(row, column of
        selected_pixel = self.image.to_row_col(self.pos)
        print("selected Pixel: {0}".format(selected_pixel))

        try:
            self.selected_color = self.image._coreimage.read_pixel(
                selected_pixel[0],
                selected_pixel[1])
                # this skips conversion and just uses pos
                # self.pos[0],
                # self.pos[1])
        except IndexError:
            print("position out of range")


class MarkerManager(RelativeLayout):
    def __init__(self, **kwargs):
        super(MarkerManager, self).__init__(**kwargs)
        self.marker_mode = None
        self.features = []

    def on_touch_down(self, touch):
        if self.collide_point(*touch.pos):
            child_touched = False
            print("Touched: {0}".format(touch))
            if self.children:
                for child in self.children[:]:
                    if child.collide_point(touch.pos[0], touch.pos[1]):
                        child_touched = True
                        child.dispatch('on_touch_down', touch)
            if not child_touched:
                print("Touched only Image at: {0}".format(touch.spos))
                marker = Marker()
                self.features.append(marker)
                self.add_widget(marker)
                marker.set_position_from_touch(touch.spos)

class SelectedImage(AsyncImage):
    def __init__(self, **kwargs):
        super(SelectedImage, self).__init__(**kwargs)
        self.allow_stretch=True
        self.keep_ratio=False

    def to_row_col(self, pos):
        pixels_x = self._coreimage.width
        pixels_y = self._coreimage.height
        pixel_x = (pos[0] / self.width) * pixels_x
        # pixel_y = (pos[1] / self.height) * self.pixels_y
        pixel_y = (1 - (pos[1] / self.height)) * pixels_y
        # should correspond to row column of image
        return [int(pixel_x), int(pixel_y)]

class ImageScreen(Screen):
    image = ObjectProperty()
    manager = ObjectProperty()
    def __init__(self, **kwargs):
        super(ImageScreen, self).__init__(**kwargs)

class PointsSelectorApp(App):
    def build(self):
        return ImageScreen()

if __name__ == "__main__":
    PointsSelectorApp().run()
点选择器.kv

<ImageScreen>:
    image: image_id
    manager: manager_id

    SelectedImage:
        id: image_id
        source: "rainbow_checkerboard.jpg"
        keep_data: True

    MarkerManager:
        id: manager_id


<Marker>:
    size_hint: None, None
    size: "40dp", "40dp"
    canvas:
        Color:
            rgb: self.selected_color
        Ellipse:
            pos: self.pos[0]+self.width/4, self.pos[1]+self.height/3
#            pos: self.pos[0], self.pos[1]
            size: self.width*.6, self.height*.6
这是我用来测试rainbow_checkboard.jpg的图片


我相信这是Kivy本身的一个缺陷。特别是我认为

索引=y*数据宽度*大小+x*大小 raw=bytearraydata.data[索引:索引+大小] 颜色=[c/255.0表示未加工的c] 这是错误的。应该是

索引=y*data.rowlength+x*size 相反

这里重要的一点是,出于性能原因,内存中的位图图像被分配到4字节的地址。因此有一个明确的data.rowlength字段。通常,这条线可以很好地工作,因为图像通常排列得很好,因此data.rowlength=data.width*size。但您的特定图像不同:它使用3字节RGB格式,无alpha,其宽度为奇数561,即data.width*size=1683,因此data.rowlength应该四舍五入到1684,实际上是这样,但代码没有考虑它。这意味着您通常从两个连续像素读取颜色,并随机旋转RGB组件

此外,由于图像使用JPEG格式,因此单元格之间的边界并不严格。若你们真的放大,你们可以在左下角看到一些压缩效果,比如这些

上面提到的随机颜色分量旋转错误所产生的这些伪影会给你非常奇怪的看起来不存在的颜色

可能的解决办法

更改图像,使其在内存中预先对齐,并且错误不会影响您,例如,使用4的mutliple作为宽度 向Kivy提交一个bug甚至一个pull请求,然后等待修复 本地补丁kivy/core/image/_init__u;.py文件我试过了,似乎效果不错。
to_row_col函数是否正常工作?你手动检查过了吗?不要忘记,您的图像可能会拉伸到与它所持有的小部件相同的大小。考虑AuthoRealStudio:FALSE帮助Debug进行拉伸,图像占据整个屏幕,不能点击图像。我认为to_row_col方法是有效的。也就是说,它标准化了相对于小部件大小的位置,然后乘以原始图像的尺寸。这是否是我应该做的是另一个问题…如果你仍然需要帮助,我明天会看一看。谢谢你的回答。对于解决方案1,如果我只是以不同的格式保存图像,并避免不严格的边界情况,是否可行?@mkrinblk,仅使用不同的格式可能不够。我还没有深入挖掘源代码,以了解每像素3字节对4字节的图像格式是如何确定的。在我的实验中,仅仅改变格式的成功取决于我使用哪个工具将JPEG转换为PNG,所以您可能需要尝试一些不同的工具。此外,我还希望将大小更改为4的倍数对除黑白BMP或类似格式之外的任何格式都有帮助。非常感谢@SergGr。我现在使用3,而我想知道如何使用2。你是说图像源本身jpg必须是4的倍数还是kivy image widget的宽度?@mkrinblk,我的意思是图像源本身jpg的宽度应该是4的倍数。如果是这样,内存中未压缩的位图数据应与额外填充字节的需要相一致,即data.rowlength=data.width*size。小部件的大小并不重要