把视力表搬到线上,需要几步?

1,523 阅读6分钟

这是我参与更文挑战的第5天,活动详情查看:更文挑战


最近需要做一个线上视力表的小程序,看了一些现有的产品,大部分比较粗糙,看起来也比较简单,心想:就是一个E字随机转向的问题。

后来发现想要做得准确,还是有不少问题需要研究。

image.png

1. 能自动检测距离吗?

线下我们通用的视力表一般是5米表、3米表,即距离5米或3米来读数。线上的一般有两种:近距离(30-50厘米)和远距离(2.5-5米),对应的是单人单手操作,和双人配合操作两种形式。

技术上对于距离的判断,即手机测距的技术方案,目前有几种情况:

iOS系统可以通过 TrueDepth API 的深度传感器组件来测定人脸和摄像头之间的距离,在iPhone X以上的版本可以支持。

这个技术的应用其实很广泛,比如Face ID, 比如通过ARkit可以做3D面部追踪,引申出来的animoji动画等等。在护眼方面的应用例子有:锻炼眼部运动的App、腾讯视频的护眼实验室等。

安卓也有类似功能,可以通过 Proximity Sensor 近距离传感器来测距。(具体没有详细了解)

但问题来了,我们的产品是基于小程序平台的,无法调用原生api,小程序目前开放的生物认证相关API也无法实现测距。那么还有没有其他办法实现呢?我查到一些曲线救国的例子,主要是基于图片计算来处理的,比如:

例1:输入已知物体的固定高度,拍照并标记出物体的范围,推算出距离,准确度不高,并且需要输入、标记等人工操作。

例2:拍摄多张照片,对图片进行分析提取3d模型,计算出对应距离,准确度不高,但是开发成本较高。

以上技术有一些适用的场景,但显然不适合我。最后决定放弃自动测距这个性价比不高的功能。

参考:

2. 视力表怎么换算?

第二个问题是,视力表的各种信息,在不同的测试距离下怎么换算?

参考 GB11533-2011《标准对数视力表》 给出的标准,可以将视力表信息整理如下:

{
  "chart": [
    { "log": "4.0", "decimal": "0.1", "size": 72.72, "number": 2 },
    { "log": "4.1", "decimal": "0.12", "size": 57.76, "number": 2 },
    { "log": "4.2", "decimal": "0.15", "size": 45.88, "number": 2 },
    { "log": "4.3", "decimal": "0.2",  "size": 36.45, "number": 3 },
    { "log": "4.4", "decimal": "0.25", "size": 28.95, "number": 3 },
    { "log": "4.5", "decimal": "0.3", "size": 23, "number": 4 },
    { "log": "4.6", "decimal": "0.4", "size": 18.27, "number": 4 },
    { "log": "4.7", "decimal": "0.5", "size": 14.51, "number": 5 },
    { "log": "4.8", "decimal": "0.6", "size": 11.53, "number": 6 },
    { "log": "4.9", "decimal": "0.8", "size": 9.16, "number": 7 },
    { "log": "5.0", "decimal": "1.0", "size": 7.27 , "number": 8},
    { "log": "5.1", "decimal": "1.2", "size": 5.78 , "number": 8},
    { "log": "5.2", "decimal": "1.5", "size": 4.59, "number": 8 },
    { "log": "5.3", "decimal": "2.0", "size": 3.64, "number": 8 }
  ]
}

其中log是对数值,decimal是小数值,其实二者可以通过公式转换:

对数值等于5减去视角α的对数,即 L = 5 - lgα
小数值等于视角α的倒数,即 d = 1 / α

但计算后发现里面有一些误差,比如有的小数四舍五入了,有的小数保留了,猜测可能是方便理解和传播,人为对数据做了一些修正。所以就保留了约定俗成的值。

number是指该行的视标个数,虽然线上测试不需要将E全部列出,但是测试流程中要求正确个数大于当前行总数的一半,因此视标个数也需要记录。

size是视标大小,单位为mm,这里是标准5米距离下的视标大小,其他距离的计算方法是:

视标大小 = 标准视标大小 * ( 实际距离 / 标准距离 )

3. 1mm是多少px?物理大小怎么换算成像素大小?

得到了视标大小,单位是毫米,如何将毫米转换成像素又是一个问题。这里面涉及到几个概念:

屏幕物理尺寸

屏幕物理尺寸就是屏幕在物理上的真实大小,常用的物理单位有厘米(cm)、毫米(mm)、英寸(inch)等。比如5.8寸的手机,就是手机屏幕对角线长5.8英寸。

分辨率

分辨率是指单位长度(物理长度)内,所包含的像素点的数量。简单来说,就是1英寸的屏幕上,包含多少个像素点。 例如常见的1920 * 1080 的分辨率,就是指屏幕的款包含1920个像素,高包含1080个像素。

物理像素(device pixels)

通常我们说的分辨率是指物理分辨率,也就是它包含的像素是设备屏幕实际拥有的像素点,是真实的分辨率。

同一类设备上的物理像素是固定的,比如iPhone 12的真实分辨率是1170 * 2532,就代表屏幕在宽度方向有1170个像素点,高度方向有2532个像素点。

设备包含的像素点越多,就代表显示出来的东西越清晰。

逻辑像素 / 设备独立像素(device independent pixels)

最早的时候,只有物理像素的概念,1px就是设备上真实的一个像素点。但当设备越来越多元,分辨率的差异非常大,比如一块手机屏幕的分辨率甚至大于有的电脑屏幕,如果按照原始像素来计算,一张 300 * 300 的图片,在电脑非常大,在手机上非常小,带来很大的不便。

为了达到相对统一,使300px在手机和电脑上显示的大小不会差太多,就需要引入逻辑像素的概念,也叫“设备独立像素”(DIP)。1个逻辑像素就是我们在css中用到的1px。

设备像素比 (devicePixelRatio)

设备像素和逻辑像素的比,就叫设备像素比,即 dpr。 比如iPhone 12 的dpr为3,物理分辨率为1170 * 2532,逻辑分辨率为390 * 844。 在浏览器中,我们可以通过 window.devicePixelRatio 来获取dpr。

PPI(Pixels Per Inch)

PPI,顾名思义,是指每英寸像素点的个数。例如iPhone 12 的ppi是460,就是说每英寸的屏幕上,有460个像素点,即460个物理像素。

结合以上概念,以iPhone 12为例,可以计算:

1 inch (25.4mm) = 460个物理像素 ( 460 / 3 px)

=> 1mm = 6.02px

由于每个设备的各项参数都不一致,要想在不同屏幕上精确展示出实际尺寸还是很困难的,最后我的方案是:

  1. 收集主流设备参数(分辨率、ppi、dpr);
  2. 获取设备名称,自动匹配计算;
  3. 无法匹配的设备,给出默认参数;
  4. 提供实物校准功能,例如拿一枚硬币,在屏幕上调整对应的尺寸。

此处插播一条没用的冷知识:不同版本的1元硬币大小是不一样的。

版本发行时间直径
第三套(长城图案)1980年30mm
第四套(牡丹图案)1992年25mm
第五套(菊花图案)2000年25mm
第五套(菊花图案)2019年22.25mm

image.png

最终,完成了一个不能自动检测距离,能基本准确显示视标,符合国家视力测试标准要求的在线测视力小程序。总结就是小需求里常常有冷知识。