点击上方“AI搞事情”关注我们



> 本文转载自:

https://www.cnblogs.com/zl03jsj/p/8051912.html


通过前面两篇文章, 我们已经解决了在手写笔迹中的平滑问题. 本篇将讲解如何让手写笔迹能够有笔锋效果.

想要让笔迹能够有笔锋的效果, 那么整个笔迹肯定不可能是等宽的.也就是说, 要让我们绘制出来的笔迹线条必须要有一定的粗细变化.

所有人都能够很自然的想到 粗细变化的原理: 运动快的地方肯定线条应该更细, 运动慢的的地方细条应该更粗.是的, 这是最基本的原理, 这个想法完全正确.

说点题外话, 最近在看机器学习, 神经网络模型里面有一个叫:激活函数的东西, 它的作用就是为了让神经网络具有分层的非线性映射学习的能力,
如果完全是线性的, 在处理某些复杂情况时,得到的结果会很糟糕.

同样, 我们在处理手写笔迹的时候, 线条的粗细也不应该完全和速度是一种线性的变化关系,.

事实上, 如果完全按照一种线性的变化关系, 绘制出来的线条会看起来非常奇怪(亲测完全如此).

所以, 在计算线条粗细的时候, 在遵循"速度越快的地方,线条更细; 速度慢的地方,线条更粗" 这一条基本准则的前提下,
也应该根据一些具体情况, 让线条的粗细变化具有"非线性"的能力.

下面我基于手写的实际情况,提出一些问题, 大家可以稍微思考一下:

1) 假设我们的计算线条笔宽的函数就是: W_current = k * s, 其中s为当前线段笔迹点移动的速度, k为将速度映射为笔宽的一个固定系数.
用户移动时, 得到了3个相邻的点依次分别为:a,b,c, 在ab线段, 移动速度接近无限大, 而在bc段移动的速度无限接近0,
事实上, 在手写笔迹的时候,完全有可能 前一段移动速度非常快, 到下一段距离的时候, 移动速度就立刻变得非常慢了.
我只是把可能遇到的情况进行了夸张, 那么, 在这种情况下,我们应该如何处理线条的粗细呢?

2) 同样假设3个点, 两条线段ab, bc, cd, 并且假设ab, bc有足够的长度, 使得我们计算出来的宽度变化看起来也是比较合理的, 这样说可能不太容易理解.
举个栗子: ab, bc的线段长度都为100个像素, 计算出ab线段线条的宽度应该是5, bc线段的线条宽度该是10,
从真实手写的情况来看, 200个像素长度的笔迹, 只有5个像素的宽度变化,这是完全合理的.
那么,如果我们用宽度5来绘制线段ab, 然后用宽度10来绘制线段bc, 我们绘制出来的笔迹是什么样子的? (发挥一下想象力)

3) 在真实手写的情况下, 文字的笔迹的笔锋主要体现在文字的哪些部位?

下面我们分别讨论这3个问题.
问题一:
如果完全按照线性函数W_current = k * s 来计算宽度, 相邻两端线条的笔宽变化有可能非常大, 大到超出了我们可以的接受范围.
所以我们考虑通过某种方式来限制这种突然的变化, 让线条宽度的变化看起来更自然.
下面是我修正以后的计算线条宽度的函数:
W_current =
  W_previous + min( abs(k*s - W_previous), distance * K_width_unit_change) (k * s-W_previous) >= 0
  W_previous - min( abs(k*s - W_previous), distance * K_width_unit_change) (k * s-W_previous) < 0
  W_current       当前线段的宽度
  W_previous    与当前线条相邻的前一条线段的宽度
  distance          当前线条的长度
  w_k         设定的一个固定阈值,表示:单位距离内, 笔迹的线条宽度可以变化的最大量.
  distance * w_k     即为当前线段的长度内, 笔宽可以相对于前一条线段笔宽的基础上, 最多能够变宽或者可以变窄多少.
这个函数多引入了2个变量(前一条线段的宽度, 还有当前的线段的距离), 在计算线条宽度时, 考虑了更多的可能性.
增加了一种非线性的变化机制, min.这个min就是我们的"激活函数".让我们的线宽不再只具有线性的变化了.
现在, 这个计算线宽的函数,看起来已经比较完美了.

问题二:
我们直接看一个我故意做得比较不好的示范图:

虽然这个效果大致看起来看还行, 作为一名追求完美的程序员, 始终觉得什么地方不对劲.

那么我们再把这个问题放大到一种极端的情况:

这样问题就很明显了.

我解决这个问题的方式是:利用微分的思想, 把线段再次细分成多条子线段, 宽度的变化均匀的分布在这些细分的子线段上.

这样, 我们的线条宽度变化看起来就更加自然了.

问题三:

在平时的书写过程中, 笔锋主要体现在笔画的起始位置, 转角位置, 和笔画结束的位置.

在笔迹转角的位置体现出笔锋看起来比较困难, 但是在笔迹开始和结束的地方做一些文章还是比较容易.

我的具体做法是这样, 在笔迹开始的前5个点(这个'5', 是我随便想出来的, 也可4,6,7,8), 让笔迹的宽细变化更加明显

在笔迹的结束位置, 不管之前的线段宽度是多少, 都让其在最后位置收缩为最小笔宽. 

更好的理由我也说不出来为什么,应该是属于程序员的第七感吧, 感觉这样做会比较好. 而且事实证明效果的确不错.

其实这里也体现了"非线性"变化的思想, 因为我觉得这一点比较重要, 所以单独提出来.

解决以上3个问题, 离这个算法的成功, 就还差一些细节的问题了.(虽然是细节问题, 但是以下这几个需要注意的细节非常非常非常重要, 说三遍!!!!!!!)

下面我就不买关子, 直接告诉大家需要注意的地方:

1)  在实际的情况中, 移动 定位设备(鼠标,手写笔,或者触摸屏)时, 设备发送给我们的mouse_move消息会非常的多, 需要设立一个

  时间阈值, 比如前一次消息到这一次消息的间隔时间小于30毫秒, 就把这个点废弃掉. 否则, 点太多, 每个都处理, 基本上都是多余的计算..

2) 在实际情况中, 需要设立一个距离阈值, 当本次得到的点, 到上一个点的距离小于这个阈值时, 把这个点舍弃掉, 距离太近, 也是多余的计算.

以上内容,  差不多就是手写笔迹算法中所有技术难点和需要注意的细节了.

 

下面把C++实现的源代码分享给大家, 其中z_math.h, z_math.c使用纯c实现,除了c标准库,没有其它任何依赖,可以毫无压力的移植到各个支持c/c++语言的任何平台.

z_math是算法的核心实现部分:

/*
=====================================================================================*       Filename:  z_math.h*    Description:*        Version:  2.0*        Created:  06/23/2016 14:53:43*       Revision:  none*       Compiler:  gcc*         Author:  zl(88911562@qq.com),*   Organization:* =====================================================================================*/
//_________________________________
// stl on mac location is:
// /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1
// Mac OS X 10.9+ no longer uses GCC/libstdc++ but uses libc++ and Clang.
//---------------------------------
//td c++ (STL, streams, ...) : Modified libstdc++ headers for use with ctags
//use ctags for c++(stl,streams....)
//www.vim.org/scripts/script.php?script_id=2358
//ctags -R --c++-kinds=+p --fields=+ias --extra=+q --language-force=c++ cpp_src
//sudo ctags -R --c++-kinds=+p --fields=+ias --extra=+q --language-force=c++ ./
//================================= */
#ifndef z_math_h_
#define z_math_h_#ifdef __cplusplus
extern "C" {
#endif#include <stdint.h>typedef struct z_point_s  z_point;
typedef struct z_fpoint_s z_fpoint;
typedef struct z_ipoint_s z_ipoint;
typedef struct z_fpoint_array_s z_fpoint_array;
typedef struct z_fpoint_arraylist_node_s  z_fpoint_arraylist_node;
typedef struct z_fpoint_arraylist_s z_fpoint_arraylist;struct z_point_s {float x, y;
};struct z_fpoint_s{z_point p;float w;
};struct z_ipoint_s {z_point p;int64_t t;
};struct z_fpoint_array_s {z_fpoint *point;float maxwidth;float minwidth;int ref;int len;int cap;z_point last_point;float last_width;int64_t last_ms;
};struct z_fpoint_arraylist_node_s {z_fpoint_array *a;z_fpoint_arraylist_node *n;
};struct z_fpoint_arraylist_s {int ref;z_fpoint_arraylist_node *first;z_fpoint_arraylist_node *end;z_fpoint_arraylist_node *cur;
};z_fpoint_array *z_keep_fpoint_array(z_fpoint_array *a);
void z_drop_fpoint_array(z_fpoint_array *a);z_fpoint_arraylist* z_keep_fpoint_arraylist(z_fpoint_arraylist *l);
void z_drop_fpoint_arraylist(z_fpoint_arraylist *l);z_fpoint_array *z_new_fpoint_array(int initsize, float maxwidth, float minwidth);
z_fpoint_array *z_resize_fpoints_array(z_fpoint_array* a, int size);z_fpoint_arraylist *z_new_fpoint_arraylist();
void z_fpoint_arraylist_append(z_fpoint_arraylist *l, z_fpoint_array *a);
// must be drop after used
z_fpoint_array *z_fpoint_arraylist_append_new(z_fpoint_arraylist *l, float maxwidth, float minwidth);
void z_fpoint_arraylist_removelast(z_fpoint_arraylist *l);float z_movespeed(z_ipoint s, z_ipoint e);
float z_distance(z_point s, z_point e);
void  z_fpoint_add_xyw(z_fpoint_array *a, float x, float y, float w);
void  z_fpoint_add(z_fpoint_array *a, z_fpoint p);
void  z_fpoint_differential_add(z_fpoint_array *a, z_fpoint p);
void  z_square_bezier(z_fpoint_array *a, z_fpoint b, z_point c, z_fpoint e);
float z_linewidth(z_ipoint b, z_ipoint e, float w, float step);float z_insert_point(z_fpoint_array *arr, z_point point);
void  z_insert_last_point(z_fpoint_array *arr, z_point e);typedef struct z_list_node_s z_list_node;
struct z_list_node_s {void *data;z_list_node *n;z_list_node *p;
};
typedef void*(*z_list_node_alloc_fun)();
typedef void(*z_list_node_drop_fun) (void *data);struct z_list_s {z_list_node_alloc_fun alloc;z_list_node_drop_fun  drop;z_list_node *first;z_list_node *last;
};
typedef struct z_list_s z_list;z_list *z_list_new(z_list_node_alloc_fun allocfun, z_list_node_drop_fun dropfun);
void *z_list_append_new(z_list *zlist);
void *z_list_remove_last(z_list *zlist);
void z_list_clear(z_list *zlist);
void z_list_free(z_list *zlist);/* digest must be 33 char size  */
// void z_text_md5(const char* str, char *digest);#ifdef __cplusplus
}
#endif#endif
/*
=====================================================================================*       Filename:  z_math.c*    Description:*        Version:  1.0*        Created:  06/23/2016 14:53:43*       Revision:  none*       Compiler:  gcc*         Author:  zl(88911562@qq.com),*   Organization:* =====================================================================================*/
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include "z_math.h"#define z_malloc_struct(t) (t*)calloc(1, sizeof(t))
static void* z_malloc_array(unsigned int count, unsigned int size);
static void* z_resize_array(void *p, size_t count, size_t size);static void z_fpoint_array_set_last_info(z_fpoint_array *arr, z_point last_point, float last_width);/***************************** mac stdlib location:
Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/usr/include/stdio.h
*/
static float z_square(float f){ return (float)f*f; };
static float z_cubic_(float f){ return (float)powf(f, 3); };typedef struct z_bezier_factors_s {float bezier_step;      // must be divisible by 1.0ffloat max_width_diff;   // max width diff between two near linesfloat max_move_speed;   //float max_linewith;
} z_bezier_factors ;int z_point_equals(z_point *p1, z_point *p2) {return (p1->x==p2->x&&p1->y==p2->y) ? 1 : 0;
}z_fpoint_array *z_keep_fpoint_array(z_fpoint_array *a) {if(a) a->ref++;return a;
}void z_drop_fpoint_array(z_fpoint_array *a) {if(!a) return;if( !(--(a->ref)) ) {free(a);}
}z_fpoint_arraylist *z_keep_fpoint_arraylist(z_fpoint_arraylist *l) {if(!l) return NULL;l->ref++;return l;
}void z_drop_fpoint_arraylist(z_fpoint_arraylist *l) {if(!l) return;if( !(--(l->ref)) ) {z_fpoint_arraylist_node *c = l->first;z_fpoint_arraylist_node *n;while(c) {z_drop_fpoint_array(c->a);n = c->n;free(c);c = n;}}
}static const float defualt_max_width = 5.0f;
static const float default_min_width = 1.0f;z_fpoint_array *z_new_fpoint_array(int initsize, float maxwidth, float minwidth) {if(initsize<=0) return NULL;z_fpoint_array *a = malloc(sizeof(z_fpoint_array));a->point = z_malloc_array(initsize, sizeof(z_fpoint));a->ref = 1;a->len = 0;if(maxwidth<0 || minwidth<0 || maxwidth<minwidth ){maxwidth = defualt_max_width;minwidth = default_min_width;}a->maxwidth = maxwidth;a->minwidth = minwidth;a->cap = initsize;return a;
}z_fpoint_array *z_resize_fpoints_array(z_fpoint_array* a, int count){if(!a || count<=0) return NULL;a->point = (z_fpoint*)z_resize_array(a->point, count, sizeof(z_fpoint));a->cap = count;a->len = min(a->cap, a->len);return a;
}z_fpoint_arraylist *z_new_fpoint_arraylist() {z_fpoint_arraylist *l = z_malloc_struct(z_fpoint_arraylist);l->ref = 1;l->first = l->end = NULL;return l;
}void z_fpoint_arraylist_append(z_fpoint_arraylist *l, z_fpoint_array *a) {z_fpoint_arraylist_node *node = z_malloc_struct(z_fpoint_arraylist_node);node->a = z_keep_fpoint_array(a);node->n = NULL;if(!l->first) {l->first = node;}else {l->end->n = node;}l->end = node;
}z_fpoint_array *z_fpoint_arraylist_append_new(z_fpoint_arraylist *l, float max, float min) {z_fpoint_array *a = z_new_fpoint_array(24, max, min);z_fpoint_arraylist_append(l, a);printf("append new points array\n");return a;
}void z_fpoint_arraylist_removelast(z_fpoint_arraylist *l) {z_fpoint_arraylist_node *c = l->first;z_drop_fpoint_array(l->end->a);free(l->end);while(c->n != l->end) { c = c->n; }c->n = NULL;l->end = c;
}z_fpoint_array *z_auto_increase_fpoints_array(z_fpoint_array *a) {int cap = a->cap + (a->cap+3)/4;return z_resize_fpoints_array(a, cap);
}float z_movespeed(z_ipoint s, z_ipoint e) {float d = z_distance(s.p, e.p);return (0==d) ? 0 : d/(e.t-s.t);
}float z_distance(z_point b, z_point e){return (float)sqrtf( z_square(e.x-b.x) + z_square(e.y-b.y) );
}void  z_fpoint_add_xyw(z_fpoint_array *a, float x, float y, float w)  {if( !a || (a->point[a->len-1].p.x==x && a->point[a->len-1].p.y==y) ) return;if(a->len==a->cap)z_auto_increase_fpoints_array(a);z_fpoint *p = a->point + (a->len++);p->p.x = x; p->p.y = y; p->w = w;
}void  z_fpoint_add(z_fpoint_array *a, z_fpoint p) {z_fpoint_add_xyw(a, p.p.x, p.p.y, p.w);
}void  z_fpoint_differential_add(z_fpoint_array *a, z_fpoint p) {if(!a) return;if( a->len==0 ) {z_fpoint_add(a, p);return;}// #define bad_show
#ifdef bad_showz_fpoint_add(a, p);return;
#endiffloat max_diff = 0.1f;z_fpoint *last = a->point + (a->len-1);z_point sp = last->p;float sw = last->w;int n = (int)((fabsf(p.w - last->w) / max_diff) + 1);float x_step = (p.p.x - sp.x) / n;float y_step = (p.p.y - sp.y) / n;float w_step = (p.w - sw)      / n;int i;for(i=0; i<(n-1); i++ ){sp.x += x_step;sp.y += y_step;sw += w_step;z_fpoint_add_xyw(a, sp.x, sp.y, sw);}z_fpoint_add(a, p);
}void  z_square_bezier(z_fpoint_array *a, z_fpoint b, z_point c, z_fpoint e){if(!a) return;const float f = 0.1f;for(float t=f; t<=1.0; t+=f ) {float x1 = z_square(1-t)*b.p.x + 2*t*(1-t)*c.x + z_square(t)*e.p.x;float y1 = z_square(1-t)*b.p.y + 2*t*(1-t)*c.y + z_square(t)*e.p.y;float w = b.w + (t* (e.w-b.w));z_fpoint pw = { {x1, y1}, w};z_fpoint_differential_add(a, pw);}
}float z_linewidth(z_ipoint b, z_ipoint e, float bwidth, float step) {const float max_speed = 2.0f;float d = z_distance(b.p, e.p);float s = d / (e.t - b.t); s = s > max_speed ? max_speed : s;float w = (max_speed-s) / max_speed;float max_dif = d * step;if( w<0.05f ) w = 0.05f;if( fabs( w-bwidth ) > max_dif ) {if( w > bwidth )w = bwidth + max_dif;elsew = bwidth - max_dif;}// printf("d:%.4f, time_diff:%lld, speed:%.4f, width:%.4f\n", d, e.t-b.t, s, w);return w;
}float z_insert_point(z_fpoint_array *arr, z_point point) {if(!arr) return 0;int len = arr->len;z_point zp = {point.x, point.y};if( 0==len ){z_fpoint p = {zp, 0.4f};z_fpoint_add(arr, p);z_fpoint_array_set_last_info(arr, point, p.w);return p.w;}int64_t cur_ms = clock();float last_width = arr->last_width;int64_t last_ms = arr->last_ms;z_point last_point = arr->last_point;printf("cur_ms - last_ms = 0x%llx\n", cur_ms - last_ms);// 两次采样时间小于25毫秒, 或者距离小于2个像素, 不采样计算!!!float distance = z_distance(point, last_point);if( (cur_ms-last_ms) < 50 || distance < 3) {return 0;}float step = arr->len > 4 ? 0.05f : 0.2f;z_ipoint bt = { {last_point.x,last_point.y}, last_ms};z_ipoint et = { zp, cur_ms};float w = (z_linewidth(bt, et, last_width, step) + last_width) / 2;z_fpoint_array *points = z_new_fpoint_array(51, arr->maxwidth, arr->minwidth);z_fpoint tmppoint = arr->point[len-1];z_fpoint_add(points, tmppoint);if( 1==len ) {z_fpoint p = { {(bt.p.x + et.p.x + 1) / 2, (bt.p.y + et.p.y +1) / 2}, w};z_fpoint_differential_add(points, p);w = p.w;}else {z_fpoint bw = tmppoint;z_point c =  {last_point.x,last_point.y};z_fpoint ew = {{(last_point.x + point.x)/2, (last_point.y + point.y)/2}, w};z_square_bezier(points, bw, c, ew);}// escape the first pointint i;for(i=1; i<points->len; i++) {z_fpoint_add(arr, points->point[i]);}z_drop_fpoint_array(points);z_fpoint_array_set_last_info(arr, point, w);return w;
}void z_insert_last_point(z_fpoint_array *arr, z_point e) {if(!arr) return;long len= arr->len;if(len==0 ) return;z_fpoint_array *points = z_new_fpoint_array(51, arr->maxwidth, arr->minwidth);z_fpoint zb = arr->point[len-1];z_fpoint_add(points, zb);z_fpoint ze = { {e.x, e.y}, 0.1f};z_fpoint_differential_add(points, ze);int i;for(i=1; i<points->len; i++) {z_fpoint_add(arr, points->point[i]);}z_drop_fpoint_array(points);
}z_list *z_list_new(z_list_node_alloc_fun allocfun, z_list_node_drop_fun dropfun)
{z_list *l = NULL;l = z_malloc_struct(z_list);l->alloc = allocfun;l->drop = dropfun;l->first = l->last = NULL;return l;
}void *z_list_append_new(z_list *zlist)
{z_list_node *node = NULL;void *data = NULL;if(!zlist->alloc || !zlist->drop)return NULL;node = z_malloc_struct(z_list_node);node->data = zlist->alloc();node->n = NULL;node->p = NULL;if(node) {if(!zlist->first) {zlist->first = zlist->last = node;}else {node->n = NULL;node->p = zlist->last;zlist->last->n = node;zlist->last = node;}data = node->data;}return data;
}
void *z_list_remove_last(z_list *zlist)
{void *data = NULL;z_list_node *tmp = zlist->last;if(zlist->last) {tmp = zlist->last;if(zlist->last==zlist->first){zlist->last = zlist->first = NULL;}else {zlist->last = tmp->p;zlist->last->n = NULL;}}if(tmp) {data = tmp->data;free(tmp);}return data;
}void z_list_clear(z_list *zlist)
{while (zlist->first)zlist->drop(z_list_remove_last(zlist));
}void z_list_free(z_list *zlist)
{z_list_clear(zlist);free(zlist);
}/* digest must be 33 char size  */
// void
// z_text_md5(const char* str, char *digest)
// {
//     int len = strlen(str);
//     unsigned char d[16];
//     fz_md5 state;
//     fz_md5_init(&state);
//     fz_md5_update(&state, (const unsigned char*)str, len);
//     fz_md5_final(&state, d);
//
//     int i;
//     for(i=0; i<(int)sizeof(d); i++) {
//         sprintf(digest, "%02x", d[i]);
//         digest+=2;
//     }
//     *digest = '\0';
// }void* z_malloc_array(unsigned int count, unsigned int size) {unsigned int totalsize = count * size;if (totalsize <= 0) return 0;void *buffer = malloc(count * size);if(buffer) memset(buffer, 0, count * size);return buffer;
}void* z_resize_array(void *p, size_t count, size_t size) {void *np = 0;size_t total_size = count * size;if (total_size <= 0)return np;np = realloc(p, total_size);return np;
}void z_fpoint_array_set_last_info(z_fpoint_array *arr, z_point last_point, float last_width) {if (!arr) return;arr->last_point = last_point;arr->last_ms = clock();arr->last_width = last_width;printf("reset last ms to 0x%llx\n", arr->last_ms);
}

 演示程序下载 (原文链接可下载)

前几天有个公司让我优化一下我的算法, 我又优化了一下, 提供出了一个 :

新的演示程序下载 (原文链接或者点击阅读原文可下载)

快乐的时光总是过得那么快, 留下的总是无尽的唏嘘和感叹, 又到时间和朋友们讲拜拜了!!!

最后展示一张在解决这些问题时,留下的真迹:

手写笔迹这一个系列总算是写完了.在这几篇原创文章中, 我有很多地方没有办法完全把自己的想法表达清楚,这可能也是很多程序员攻城狮的通病, 表达能力不怎么样.

长按二维码关注我们

有趣的灵魂在等你

原笔迹手写实现平滑和笔锋效果之:笔锋效果(三)[完结篇]相关推荐

  1. python模拟手写笔迹_原笔迹手写实现平滑和笔锋效果之:笔迹的平滑(一)

    之前研究过一种用于 模拟真实 手写笔迹签名 的算法,  要求能够保持原笔迹平滑,并有笔锋的效果. 在网上看了一些资料, 资料很多, 能够达到用于正式产品中的效果的一个都没有找到. 但是即使按照这篇文章 ...

  2. 手写原笔迹输入_原笔迹手写软件 - 随意写 V1.1

    双指模式只在双框和全屏模式下支持,如果用双指切换到其它模式就不能再用双指切换回来,如果切换到浏览模式可以用手机摇动切换回来,因为浏览模式的双指已被定义为放大缩小了,文字模式只要点一下非键盘位置就会切换 ...

  3. 基于Visual Studio2012实现Windows8的metro界面笔迹手写识别文档

    手写识别,是指将在手写设备上书写时产生的有序轨迹信息化转化为汉字内码的过程,实际上是手写轨迹的坐标序列到汉字的内码的一个映射过程,是人机交互最自然.最方便的手段之一. 随着智能手机.掌上电脑等移动信息 ...

  4. 手写识别转文字怎么弄?用这三个手写识别转文字的软件就够了

    我有很多从事教师行业的朋友,他们每天都需要手写教案,写完还要再手动输出为电子版.他们表示,这样的录入过程很花费时间.其实,我们可以通过一些智能软件来解决这一难题.那么,手写识别转文字的软件哪个好呢?今 ...

  5. 手写文字识别软件哪个好?安利这三款

    随着人工智能技术的不断发展,手写文字识别技术也被广泛应用于各个领域.手写文字识别软件可以帮助人们快速输入和编辑手写文字,提高文字处理效率和准确性.例如,在智能阅卷方面,手写文字识别软件可以帮助老师们快 ...

  6. 从无到有写一个运维APP(三)完结篇

    前言:自己的挖的坑还得填,此篇为完结篇. 环境的搭建参考第一篇 从无到有写一个运维APP(一),至于第二篇就跳过吧,写个 APP 没那么复杂. 由于自己现在无业游民,所以没有什么现成的环境,环境就随便 ...

  7. 安卓手写字迹源码(毛笔,喷枪,马克笔等效果)

    之前项目需要,要在手机上实现笔迹的效果,这类的应用多了,但是源码在全球最大局域网内却找不到~~ 一年前是想着自己做的,当时知道安卓设备的touch是可以获取伪压力感应值 即手指压力越大,皮肤接触面积也 ...

  8. vue手写一个卡片化层叠轮播 五张 三张

    项目需求,需要写一个卡片化层叠的轮播,找了下插件都没有合适的,于是写了一个展示5个卡片的轮播 先看效果图: 5个卡片要计算各自的高度,宽度,利用相对定位计算出各自的位置 然后transition过渡来 ...

  9. php 手写签批 手机办公_好签小程序手写签名组件/在线手写签批系统

    支持多种文档格式 好签原笔迹手写技术可以在任何版式文档格式上进行手写与笔迹的展示,与文档类型无关,它是一款真正意义的跨文档格式的手写批示引擎,在常用的版式文件上都可以进行手写批示,如PDF.JGP. ...

  10. pcie ep 应该支持哪种interrupt_7寸国产笔记本评测,酷睿处理器+8G+256G,还支持手写笔...

    在大多数的人的传统印象里,可能都会认为便携式笔记本应该搭载12.5英寸屏幕,然后把重量控制在2公斤以内就行了,而实际上,我认为的便携式笔记本则应该拥有更小的屏幕,如果能够像智能手机一样装进衣服口袋,那 ...

最新文章

  1. 云迹科技:站在酒店场景服务机器人的风口
  2. Android中文API(130) —— Html
  3. bzoj1951 组合数取模 中国剩余定理
  4. Javascript实例:Select的OnChange()事件
  5. COJ 1006 树上操作
  6. 无线路由器和计算机怎么连接网络连接,华为无线路由器怎么连接宽带上网
  7. c语言循环链表中设立尾链表,C语言实现双向非循环链表(带头结点尾结点)的节点插入...
  8. 12 [虚拟化] 进程抽象;fork,execve,exit
  9. 多表关联查询_【函数007】 EXCEL多表关联查询实战
  10. 非法控制计算机信息系统罪的标准,非法获取计算机信息系统数据、非法控制计算机信息系统罪立案标准...
  11. linux之软连接和硬连接的区别
  12. r求矩阵某一列的标准偏差_如何在R中找到标准偏差?
  13. 移动互联网时代的营销
  14. UE for Mac 破解方法
  15. 快速爬取腾讯招聘信息
  16. 药店管理系统|数据库设计
  17. 洛谷P1273 有线电视网 题解
  18. 网易杭研 java 校招_09网易杭研校园招聘面试题
  19. 红旗Linux网卡Bind,请教一下在红旗linux中安装网卡RTL8168/8111的问题
  20. 2022年初级经济师考试综合试题及答案

热门文章

  1. 猫和计算机连接网络,计算机路由器与猫的连接方法步骤
  2. 二、 在Sails中使用Typescript
  3. 一个屌丝程序员的青春(二四一)
  4. 转载 | 上汽集团云计算中心的开源之路
  5. 如何提高研发部门工作效率的探讨
  6. 计算机打字在哪点,电脑打字在哪里打开
  7. asp使用js时间控件,实现下拉日历 解决UTF-8和GB2312的编码问题
  8. Longhorn 云原生容器分布式存储 - Air Gap 安装
  9. 分区属性揭秘 Win8 电脑 OEM 分区创建 分区属性
  10. python编程长方形面积公式_求长方形面积-题解(Java代码)