- VisualStudio2022插件的安装及使用-编程手把手系列文章
- pprof-在现网场景怎么用
- C#实现的下拉多选框,下拉多选树,多级节点
- 【学习笔记】基础数据结构:猫树
白板软件书写速度是其最核心的功能,注册StylusPlugin从触摸线程拿触摸点数据并在另一UI线程绘制渲染是比较稳妥的方案,具体的可以查看小伙伴德熙的2019-1-28-WPF-高性能笔 - lindexi - 博客园 (cnblogs.com) 。
上面StylusPlugin方案能提升在大屏目前如富创通、华欣触摸框的主要产品版本上,1帧16ms左右的书写性能。除了这个跳过一些流程来减少延时,我们还能继续优化书写性能么?答案肯定是可以的 。
本文我们介绍下书写加速的一类实现方向,通过预测下一个甚至N个点,提前绘制笔迹来降低书写延迟.
书写预测,这里介绍下曲线拟合的方案:
取N个点拟合成一条曲线,算出它的曲线公式,然后下一个点可以输入它的X位置得到Y -- 以X方法或者Y方法为基准,拟合出以X为参数的曲线.
这里采用的是开源组件MathNet.Numerics,也可以使用其它的拟合曲线方案,目标就是先输出一条曲线公式。先安装其Nuget包MathNet.Numerics:
<PackageReference Include="MathNet.Numerics" Version="5.0.0" />
引用using MathNet.Numerics;我们以X坐标为基准预测Y坐标值,已经写好函数:
1 private static Point[] PredictPoints(Point[] pointArray, int degree, int predictCount) 2 { 3 Debug.WriteLine("输入:" + string.Join(",", pointArray.Select(i => $"({i.X},{i.Y})"))); 4 var xList = pointArray.Select(i => i.X).ToArray(); 5 var yList = pointArray.Select(i => i.Y).ToArray(); 6 var lastX = xList[xList.Length - 1]; 7 var lastX1 = xList[xList.Length - 2]; 8 var lastPointLength = lastX - lastX1; 9 double[] parameters = Fit.Polynomial(xList, yList, degree); 10 var predictPoints = new Point[predictCount]; 11 for (int i = 0; i < predictCount; i++) 12 { 13 var currentX = lastX + (i + 1) * lastPointLength; 14 var currentY = 0d; 15 for (int j = 0; j < degree + 1; j++) 16 { 17 var parameterJ = parameters[j]; 18 for (int k = 0; k < j; k++) 19 { 20 parameterJ *= currentX; 21 } 22 23 currentY += parameterJ; 24 } 25 26 var newPoint = new Point(currentX, currentY); 27 predictPoints[i] = newPoint; 28 } 29 Debug.WriteLine("输出:" + string.Join(",", predictPoints.Select(i => $"({i.X},{i.Y})"))); 30 return predictPoints; 31 }
这里的double[] parameters = Fit.Polynomial(xList, yList, degree),表示通过X以及Y系列数据,以阶数degree(如二阶曲线)计算出当前多项式参数值parameters.
如果degree是2阶,可以计算得到y值:
y = parameters[0] + parameters[1]*x + parameters[2]*x^2 。
好,曲线公式有了,那下面就是塞x坐标得到y值,也就是point.
如果是Y轴向上递增的二队曲线,如下图,从左到右绿色点是已知点列表,黄色为预测的点。这里我们依次预测4个点:
这类场景,预测结果是比较正常的.
我们再看看抛物线的场景,沿X方向Y坐标值依次递减,即顺时针角度值增加,预测点如下:
从上图可以看出,Y方向值递减时预测结果是抛物线的另一半,Y轴值另一侧反向加速递减。但我们的期望肯定不是像这类抛物线,我们期望它能延着绿色轨迹向斜上方走.
为何它会快速弹回来呢?原因就是拟合的2阶曲线,公式如此.
这类方法,只能做到一维的预测,即遇到X、Y方向值不变或者值变化较少时,曲线变化就会像抛物线一样到达它的顶端后,就会快速弹回来.
以拟合成曲线公式来计算,会有一定缺陷。这个我们也是能优化的,上方的函数里我们是以X轴为基准,得到的公式中X为变化量,下面换一下以Y轴为基准:
1 public static Point[] PredictPointsY(Point[] pointArray, int degree, int predictCount) 2 { 3 Debug.WriteLine("输入:" + string.Join(",", pointArray.Select(i => $"({i.X},{i.Y})"))); 4 var xList = pointArray.Select(i => i.X).ToArray(); 5 var yList = pointArray.Select(i => i.Y).ToArray(); 6 var lastY = yList[yList.Length - 1]; 7 var lastY1 = yList[yList.Length - 2]; 8 var lastPointLength = lastY - lastY1; 9 double[] parameters = Fit.Polynomial(yList, xList, degree); 10 var predictPoints = new Point[predictCount]; 11 for (int i = 0; i < predictCount; i++) 12 { 13 var currentY = lastY + (i + 1) * lastPointLength; 14 var currentX = 0d; 15 for (int j = 0; j < degree + 1; j++) 16 { 17 var parameterJ = parameters[j]; 18 for (int k = 0; k < j; k++) 19 { 20 parameterJ *= currentY; 21 } 22 23 currentX += parameterJ; 24 } 25 26 var newPoint = new Point(currentX, currentY); 27 predictPoints[i] = newPoint; 28 } 29 Debug.WriteLine("输出:" + string.Join(",", predictPoints.Select(i => $"({i.X},{i.Y})"))); 30 return predictPoints; 31 }
这里的二阶拟合曲线就变成了:
x = parameters[0] + parameters[1]*y + parameters[2]*y^2 。
同样预测4个点,看下结果:
这个预测趋势就符合我们的期望了.
所以上方X方向以及Y方向分别为基准,拟合二队曲线,然后输出预测点,我们综合取一个比较适合的点即可.
如何取呢?可以看到我们期望的点是变化趋势变化小的一个。即以最后俩个数据点的角度A为基准,预测点与最后数据点的向量角度B1与B2,顺时针角度变化较小的点是我们期望输出的.
上面我们解决了坐标点场景,单方向拟合时输出点快速弹回的问题。在验证书写时,我们还发现一类预测失准问题:
如上图,黄色点往上偏了(黄色点是直线,并且角度不符合原有趋势),真实期望我们是想要沿着原有角度减少的趋势,预测点为角度略偏下的方向:
这里预测失准的原因是,X方向值无法正常预测。因为按我们上面曲线拟合的方案,这类抛物线场景是以Y轴为基准,输入Y得到X方向值,但按曲线变化的方向输入一个最后俩点之间Y轴变化量,预测点的X值应该是接近无限大的,超出了曲线范围.
这里可以根据曲线点角度变化量来处理,看上图点与点之间角度是按顺时针依次增加的,所以预测出来的点也应该要继续顺时针增加角度。所以可以将输出的点按最后俩点Point(n)、Point(n-1)之间向量角度值,或者再增加最后三点之间角度的差值angleChange.
我使用的是增加角度变化量,如下图输出效果:
我们做的毕竟是预测,预测点肯定不能完美代替真实书写触摸点。尤其是当我们按照设置下一个预测点太远,很大概率是会偏离原有曲线的 。
在上面PredictPoints预测函数内,使用了var lastPointLength = lastX - lastX1;来作为下一个预测点X方向的位置。但这其实是不符合实际情况的,因为你并不清楚下一个预测点也变化了 lastX - lastX1的X方向距离,如果强行用此X变化量确定预测点,预测点偏离曲线的概率会很大。那如何解决呢?
我的解决方法是,通过上面PredictPoints计算出下一预测点的角度变化量changedAnge即可,然后再以lastPoint-lastPoint1之间的距离作为半径围绕lastPoint进行旋转180+changedAnge至一个新的点。这个新点作为最后预测点 。
为何以lastPoint-lastPoint1之间的距离作为半径?因为在快速书写过程中,触摸框帧率是固定的,书写速度不怎么变化的情况下,点与点之间的距离很接近,所以我们只要预测出下一点的changedAnge就行了.
最后,我们按上面的方案验证下真实书写预测的效果。输出书写痕迹,我们换个颜色,蓝色为真实点、红色为预测点.
预测1个点,红色与真实书写曲线重合:
预测2个点,红色是第一个预测点,粉色是第二个预测点:
这里预测效果是在我的开发触摸屏设备(Dell P2418HT,1080p)上验证的,看上面效果粉色点偏移的较多。在这台触摸屏上,不建议预测2个点 。
需要注意的是,书写预测与屏幕报点率(帧率)强相关。一般情况下,输出俩点之间间隔时间越长,俩点之间间距越大,预测点的误差也会变大,但相应的预测距离变远了即书写延迟会降低很大.
我这Dell触摸屏触摸移动时,输入平均间隔33.3ms,一次输入包含2-3个点,点平均间隔在16.6ms。俩点平均间隔16.6ms,说明触摸框是60fps报点。另外,详细的触摸框报点率数据以及开启WM_POINTER消息提升应用层的触摸输入帧率可以看下:白板书写延迟-触摸屏报点率 - 唐宋元明清2188 - 博客园 (cnblogs.com) 。
根据上面触摸报点数据,上面书写预测方案预测1个点,在这台Dell触摸屏上书写延迟可以降低16.67ms 。
最后此篇关于.NET白板书写延迟-曲线拟合预测的文章就讲到这里了,如果你想了解更多关于.NET白板书写延迟-曲线拟合预测的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我需要(我必须)将大量 float 写入 qdatastream 并且我只使用 4 个字节是必要的。setFloatingPointPrecision 或为 float 和 double 写入 4 或
我有一些 C 代码,我用 Python 对其进行了扩展。扩展的 C 代码有一个将一些结构附加到二进制文件的函数: void writefunction(const struct struct1* so
我正在用 C 语言开发一个小软件,用于在布告栏中读取和写入消息。每条消息都是一个以渐进数字命名的 .txt。 软件是多线程的,有很多用户可以并发操作。 用户可以进行的操作有: 阅读整个公告板(所有 .
我有 2 个线程同时访问同一个大文件 (.txt)。 第一个线程正在从文件中读取。第二个线程正在写入文件。 两个线程都访问同一个 block ,例如(开始:0, block 大小:10),但具有不同的
我做了很多谷歌搜索,但我仍然不确定如何继续。 Linux 下最常见的剪贴板读写方式是什么?我想要同时支持 Gnome 和 KDE 桌面。 更新:我是否认为没有简单的解决方案,必须将多个来源(gnome
1. 定义配置文件信息 有时候我们为了统一管理会把一些变量放到 yml 配置文件中 例如 图片 用 @ConfigurationProperties 代替 @Value 使用方法 定义对应字段的实体
在开始之前,我必须先声明我是 FORTRAN 的新手。我正在维护 1978 年的一段遗留代码。它的目的是从文件中读取一些数据值,处理这些值,然后将处理过的值输出到另一个文本文件。 给定以下 FORTR
我正在制作一个应用程序,我需要存储用户提供的一些信息。我尝试使用 .plist 文件来存储信息,我发现: NSString *filePath = @"/Users/Denis/Documents/X
在delphi类中声明属性时是否可能有不同类型的结果? 示例: 属性月份:字符串读取monthGet(字符串)写入monthSet(整数); 在示例中,我希望在属性(property)月份中,当我:读
我正在以二进制形式将文件加载到数组中,这似乎需要一段时间有没有更好更快更有效的方法来做到这一点。我正在使用类似的方法写回文件。 procedure openfile(fname:string); va
我想实现一个运行模拟的C#控制台应用程序。另外,我想给用户机会在控制台上按“+”或“-”来加速/减速模拟的速度。 有没有办法在编写控制台时读取控制台?我相信我可以为此使用多线程,但是我却不怎么做(我对
这是我的代码: use std::fs::File; use std::io::Write; fn main() { let f = File::create("").unwrap();
我有一个应用程序可以访问 csv 文本文件中的单词。由于它们通常不会更改,因此我将它们放置在 .jar 文件中,并使用 .getResourceAsStream 调用读取它们。我真的很喜欢这种方法,因
我使用kubeadm,docker 17.12.1-ce和法兰绒网络安装了Kubernetes 1.13.1集群 但是,我发现Kubernetes主服务器上有许多空文件,权限为666,该文件允许任何用
我的工作区中有一些 java 文件。现在我想编写一个java程序,它可以读取来自不同源的文本文件,一次一个,一行一行,并将这些行插入到工作区中各自的java文件中。 文本文件会告诉我将哪个文件插入到哪
用户A要求系统读取文件foo,同时用户B想要将他或她的数据保存到同一个文件中。在文件系统级别如何处理这种情况? 最佳答案 大多数文件系统(但不是全部)使用锁定来保护对同一文件的并发访问。锁可以是独占的
我对保护移动应用程序的 firebase 数据库有一些疑问。 例如,在反编译Android应用程序后,黑客可以获取firebase api key 然后访问firebase数据库,这是正确的吗? 假设
我想让文件从外部不可删除,并希望使用java从程序对该文件进行读/写操作。 S0,我使用以下代码使用java创建了不可删除的文件: Process pcs = Runtime.getRunti
当 Selector.select() 以阻塞模式等待读/写操作时,是否可以将写消息推送到客户端?如何将选择器从阻塞模式移至写入模式?触发器可以是一个后台线程,用于放置需要写入给定 channel 的
我目前正在学习在 Linux 环境中使用 C 进行套接字编程。作为一个项目,我正在尝试编写一个基本的聊天服务器和客户端。 目的是让服务器为每个连接的客户端派生一个进程。 我遇到的问题是读取一个 chi
我是一名优秀的程序员,十分优秀!