探索视觉的边界:用 Manim 重现有趣的知觉错觉

张开发
2026/4/10 12:10:20 15 分钟阅读

分享文章

探索视觉的边界:用 Manim 重现有趣的知觉错觉
1. 静态的欺骗这三种错觉不需要动画仅仅通过静态的排列和色彩对比就能欺骗我们的大脑。1.1. 彩纸屑错觉这是David Novick创作的Munker错觉的变体。下面图中所有的圆圈颜色完全相同唯一不同的是围绕它们的线条颜色。这个错觉生动地证明我们并非直接感知物体在现实中的颜色。相反知觉系统会根据物体周围的环境做出一个有根据的猜测。1.2. 米饭波浪错觉这看起来像是一个动态GIF但其实不是。所有的运动都发生在你的大脑中。它的作者Akiyoshi Kitaoka。黄色斑块的阴影和排列顺序会触发大脑的运动感知区域从而在一个实际静止的图像中产生运动的知觉。有趣的是大约5%的人似乎对这个错觉免疫。1.3. 倾斜道路错觉这看起来像是从不同角度拍摄的同一道路的两张照片。但实际上这只是同一张照片复制了两次。显然视觉系统将这张图像当作两张独立道路的照片来处理。在二维图像中两条道路的轮廓是相互平行的。如果现实世界中的两条道路在图像中呈现这种效果那么它们在现实中必须是强烈地向外倾斜的。因此视觉系统便做出了这样的推断。2. 动态的魔法接下来我们使用Manim来制作后两种动态错觉。2.1. 动态艾宾浩斯错觉图中的橙色圆圈实际上并没有改变大小。与颜色和明度一样我们并非直接感知物体的大小。知觉系统会根据感官数据中的线索包括附近其他物体的相对大小来推断物体的尺寸。Manim代码from manim import * config.background_color WHITE class DynamicEbbinghaus(Scene): def construct(self): # 中心圆圈实际大小不变 center_circle Circle(radius0.3, colorORANGE, fill_opacity1) center_circle.set_stroke(width0) center_circle.move_to(LEFT * 2 UP * 2) center_circle2 center_circle.copy() center_circle2.move_to(ORIGIN) # 周围圆圈 surrounding_circles VGroup() surrounding_circles2 VGroup() num_circles 6 radius 0.1 distance 0.4 radius2 0.7 distance2 1.5 for i in range(num_circles): angle i * (360 / num_circles) * DEGREES circle Circle(radiusradius, colorPURE_BLUE, fill_opacity1) circle.set_stroke(width0) circle.move_to( center_circle.get_center() distance * np.array([np.cos(angle), np.sin(angle), 0]) ) surrounding_circles.add(circle) circle2 Circle(radiusradius2, colorPURE_BLUE, fill_opacity1) circle2.set_stroke(width0) circle2.move_to( center_circle2.get_center() distance2 * np.array([np.cos(angle), np.sin(angle), 0]) ) surrounding_circles2.add(circle2) self.add(center_circle, surrounding_circles) self.wait(0.5) a_group VGroup(center_circle, surrounding_circles) a_group2 a_group.copy() b_group VGroup(center_circle2, surrounding_circles2) # 正常移动 self.play(a_group.animate.move_to(b_group.get_center()), run_time2) self.play(a_group.animate.move_to(a_group2.get_center()), run_time2) self.wait(1) # 放大蓝色小圆 # 动画周围圆圈变大使中心圆圈看起来变小 self.play( ReplacementTransform(a_group, b_group), run_time2, ) # 动画周围圆圈变小使中心圆圈看起来变大 self.play( ReplacementTransform(b_group, a_group2), run_time2, ) self.wait(1)2.2. 动态穆勒-莱尔错觉这是我见过最棒的错觉之一。蓝色和红色的线条长度完全相同没有任何线条在移动或改变大小它们都处于同一水平线上。只有两端的箭头在移动。这个错觉是经典穆勒-莱尔错觉的新变体。关于它的原理有许多理论但没有人能100%确定。甚至还存在争议这种错觉是适用于全人类还是某种特定文化下的现象Manim代码from manim import * import numpy as np config.background_color WHITE class DynamicMullerLyer(Scene): def construct(self): self.vertexes [] count 11 # 所有线都一样长蓝色和红色的线段也是一样长。 lines self.create_lines(count) self.play(Create(lines)) self.wait(1) self.clear() wings self.create_wings(self.vertexes) self.add(*wings) self.rotate_wings( wings, self.vertexes, list(np.random.uniform(0.5, 1.5, len(wings))), repeat4, ) self.wait(1) # 放在一起 self.add(lines) self.rotate_wings( wings, self.vertexes, list(np.random.uniform(0.5, 1.5, len(wings))), repeat8, ) self.wait(0.5) def create_lines(self, count11, interval0.4) - VGroup: l_group VGroup() for i in range(count // 2 1): vertical_l_group VGroup() vertical_l_group.add( Line(UP * 2.5, UP * 1.5, stroke_width2, colorPURE_BLUE) ) vertical_l_group.add( Line(UP * 1.5, UP * 0.5, stroke_width2, colorPURE_RED) ) vertical_l_group.add( Line(UP * 0.5, DOWN * 0.5, stroke_width2, colorPURE_BLUE) ) vertical_l_group.add( Line(DOWN * 0.5, DOWN * 1.5, stroke_width2, colorPURE_RED) ) vertical_l_group.add( Line(DOWN * 1.5, DOWN * 2.5, stroke_width2, colorPURE_BLUE) ) vertical_l_group.shift(LEFT * i * interval) self.vertexes.append(UP * 2.5 LEFT * i * interval) self.vertexes.append(UP * 1.5 LEFT * i * interval) self.vertexes.append(UP * 0.5 LEFT * i * interval) self.vertexes.append(DOWN * 0.5 LEFT * i * interval) self.vertexes.append(DOWN * 1.5 LEFT * i * interval) self.vertexes.append(DOWN * 2.5 LEFT * i * interval) l_group.add(vertical_l_group) for i in range(1, count // 2 1): vertical_l_group VGroup() vertical_l_group.add( Line(UP * 2.5, UP * 1.5, stroke_width2, colorPURE_BLUE) ) vertical_l_group.add( Line(UP * 1.5, UP * 0.5, stroke_width2, colorPURE_RED) ) vertical_l_group.add( Line(UP * 0.5, DOWN * 0.5, stroke_width2, colorPURE_BLUE) ) vertical_l_group.add( Line(DOWN * 0.5, DOWN * 1.5, stroke_width2, colorPURE_RED) ) vertical_l_group.add( Line(DOWN * 1.5, DOWN * 2.5, stroke_width2, colorPURE_BLUE) ) vertical_l_group.shift(RIGHT * i * interval) self.vertexes.append(UP * 2.5 RIGHT * i * interval) self.vertexes.append(UP * 1.5 RIGHT * i * interval) self.vertexes.append(UP * 0.5 RIGHT * i * interval) self.vertexes.append(DOWN * 0.5 RIGHT * i * interval) self.vertexes.append(DOWN * 1.5 RIGHT * i * interval) self.vertexes.append(DOWN * 2.5 RIGHT * i * interval) l_group.add(vertical_l_group) return l_group def create_wings(self, vertexes, wing_radio0.1): groups [] # 创建两条线呈V字形 for vertex in vertexes: left_line Line( vertex, vertex (UP LEFT) * wing_radio, stroke_width2, colorBLACK ) right_line Line( vertex, vertex (UP RIGHT) * wing_radio, stroke_width2, colorBLACK ) groups.append(VGroup(left_line, right_line)) return groups def rotate_wings(self, wings, vertexes, run_times, repeat4): anims [] for i in range(len(wings)): ag1 AnimationGroup( Rotate( wings[i][0], angle90 * DEGREES, about_pointvertexes[i] ).set_run_time(run_times[i]), Rotate( wings[i][1], angle-90 * DEGREES, about_pointvertexes[i] ).set_run_time(run_times[i]), ) ag2 AnimationGroup( Rotate( wings[i][0], angle-90 * DEGREES, about_pointvertexes[i] ).set_run_time(run_times[i]), Rotate( wings[i][1], angle90 * DEGREES, about_pointvertexes[i] ).set_run_time(run_times[i]), ) anim Succession([ag1, ag2] * repeat) anims.append(anim) self.play( AnimationGroup(*anims), run_timemax(run_times) * repeat, )3. 总结这些错觉共同揭示了一个深刻的事实——我们的知觉并非对世界的“直接复制”而是大脑基于有限感官信息、结合经验与期望所构建的“最佳猜测模型”。通过Manim重现这些错觉我们不仅理解了视觉心理学也掌握了如何用代码精确控制视觉元素来传达信息。

更多文章