数据专栏

智能大数据搬运工,你想要的我们都有

科技资讯:

科技学院:

科技百科:

科技书籍:

网站大全:

软件大全:

「深度学习福利」大神带你进阶工程师,立即查看>>>
最近想要学习一下ffmpeg,下载代码,配置环境,编译成功。一路下来到时没有问题,但是想要研读下代码的时候发现总归是有些不方便。
像我这样三流的程序员没有实在是需要一些类似ide的工具来方便跳转这样子。vim是不可能vim的,这辈子都不可能vim。只能用用ide这样子,ide的跳转又方便…………
幸运的是ffmpeg官方已经有介绍如何在eclipse上导入ffmpeg项目了,真是不错的消息。
https://trac.ffmpeg.org/wiki/Eclipse
用visual studio似乎也可以,但是这就非官方了,而且需要将makefile的打包方式转成visual c++。
多媒体
2018-04-12 17:26:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
关于 JAVA 学习 OpenCV 的内容,函数讲解。内容我均整理在 GitHubd的 OpenCV3-Study-JAVA
我们在学习OpenCV 时,发现在构造 Mat 时,会有2个关键参数,一个 CvType,一个 Scalar。很多人不明白这2个参数的意思,所以在把别人例子拿来使用或自己写的的时候发现这个参数不知道该怎么填。本文就将 Scalar 参数详细的说明下。
Scalar 是一个配合 CvType 使用的参数,如果你不了解 CvType,可以先看看我写的 OpenCV 3.4 读懂 CvType 类型说明 了解什么是图像通道,CvType 的构造。
本文下列实例均是 JAVA 代码,其他语言实现类同
Scalar 构造器 public class Scalar { public double[] val; public Scalar(double v0, double v1, double v2, double v3) { this.val = new double[]{v0, v1, v2, v3}; } public Scalar(double v0, double v1, double v2) { this.val = new double[]{v0, v1, v2, 0.0D}; } public Scalar(double v0, double v1) { this.val = new double[]{v0, v1, 0.0D, 0.0D}; } public Scalar(double v0) { this.val = new double[]{v0, 0.0D, 0.0D, 0.0D}; } public Scalar(double[] vals) { if (vals != null && vals.length == 4) { this.val = (double[])vals.clone(); } else { this.val = new double[4]; this.set(vals); } } ....... }
可以看到有5个构造函数,参数也不复杂,很容易理解。除开 public Scalar(double[] vals) 这个数组入参的构造函数,其他4个构造函数分别是传入1个、2个、3个、4个 double 类型的构造函数,那么这些参数代表什么意思,他的取值范围是什么?
Scalar 参数的含义
Scalar 一定是与 CvType 结合使用的,他代表 CvType 中指定通道的值。如果您不了解 CvType,可以先看看我写的 OpenCV 3.4 读懂 CvType 类型说明 了解什么是图像通道,CvType 的构造。
构造说明: 构造函数的入参(值)的数量 <= 通道数量 [如果是2通道图像,用4个参数的构造函数,后面2个参数不起作用] 构造函数的入参(值)的数量 < 通道数量 时,未传入值的通道值为0 多通道图像时,入参顺序 new Scalar(B,G,R,alpha)
下面来看示例: import org.opencv.core.CvType; import org.opencv.core.Mat; import org.opencv.core.Scalar; import org.junit.Test; import org.opencv.core.Size; public class TestScalar { /** * 创建灰色图像 */ @Test public void createGrayImage(){ //选择一个单通道类型 CV_8UC1 //单通道下 0 为黑,255为白,取中间值 125 ,灰色。 Mat sourceImage = new Mat(100,100, CvType.CV_8UC1, new Scalar(125)); Imgcodecs.imwrite("savePath/gary-image.png",sourceImage); } /** * 创建蓝色图像 */ @Test public void createBlueImage(){ //蓝色为彩色图,所以通道类型为3通道,CV_8UC3 //蓝色是 RGB 中的 B,其RGB值为 R:0 , G:0 , B:255 //Scalar 入参顺序为 new Scalar(B,G,R,A)。 //new Scalar(255) 的意思为 B 通道值为255 ,G、R 值不传递的话,默认为0 Mat sourceImage = new Mat(100,100, CvType.CV_8UC3, new Scalar(255)); Imgcodecs.imwrite("savePath/blue-image.png",sourceImage); } /** * 创建绿色图像 */ @Test public void createGreenImage(){ //绿色为彩色图,所以通道类型为3通道,CV_8UC3 //绿色是 RGB 中的 G,其RGB值为 R:0 , G:255 , B:0 //Scalar 入参顺序为 new Scalar(B,G,R,A)。 //new Scalar(0,255) 的意思为 B 通道值为0,G通道值为255,R 值不传递的话,默认为0 Mat sourceImage = new Mat(100,100, CvType.CV_8UC3, new Scalar(0,255)); Imgcodecs.imwrite("savePath/green-image.png",sourceImage); } /** * 创建红色图像 */ @Test public void createRedImage(){ //红色为彩色图,所以通道类型为3通道,CV_8UC3 //红色是 RGB 中的 R,其RGB值为 R:255 , G:0 , B:0 //Scalar 入参顺序为 new Scalar(B,G,R,A)。 //new Scalar(0,0,255) 的意思为 B 通道值为 0,G 通道值为 0,R 通道值为 255 Mat sourceImage = new Mat(100,100, CvType.CV_8UC3, new Scalar(0,0,255)); Imgcodecs.imwrite("savePath/red-image.png",sourceImage); } /** * 创建透明图像 */ @Test public void createAlphaImage(){ //透明图像为4通道图像,所以通道类型为4通道,CV_8UC4 //创建一个半透明的红色图像来试试 //红色是 RGB 中的 R,其RGB值为 R:255 , G:0 , B:0 //Scalar 入参顺序为 new Scalar(B,G,R,A)。 //new Scalar(0,0,255,125) 的意思为 B 通道值为 0,G 通道值为 0,R 通道值为 255,Alpha 通道值为125 (255为不透明,0为全透明会忽略所有的 RGB 颜色) Mat sourceImage = new Mat(100,100, CvType.CV_8UC4, new Scalar(0,0,255,125)); Imgcodecs.imwrite("savePath/alpha-image.png",sourceImage); } }
通过上边的例子,应该已经明白 Scalar 的意思了吧,就是通道的值
Scalar 参数的数据范围
处理 Alpha 通道值为 0~255 ,其他通道取值范围根据图像的位数(Bite)来决定。可以查看 OpenCV 3.4 读懂 CvType 类型说明 了解什么是图像通道,CvType 的构造,后面后各 Bite 下通道的取值范围。 广告栏: 欢迎关注我的 个人博客
多媒体
2018-04-12 11:56:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
关于 JAVA 学习 OpenCV 的内容,函数讲解。内容我均整理在 GitHubd的 OpenCV3-Study-JAVA CvType 可以说是 OpenCV 图像处理的基础常量参数。但是面对 CV_8U1C 、CV_8U2C、CV_8U3C、CV_8U4C 在初学阶段可以说一脸懵,他们代表的是什么呢?
关于这些参数名门的来历,个人觉得有点 [匈牙利命名法] 的影子。
1. 什么是图像的通道?
在了解 CvType 这个常量类之前,需要先知道图像的通道是什么。OpenCV 中,图像可以分别为1,2,3,4 通道。 1 通道为灰度图; 2 通道的图像是RGB555和RGB565。2通道图在程序处理中会用到,如傅里叶变换,可能会用到,一个通道为实数,一个通道为虚数,主要是编程方便。RGB555是16位的,2个字节,5+6+5,第一字节的前5位是R,后三位+第二字节是G,第二字节后5位是B,可见对原图像进行压缩了 3 通道为彩色图(RGB); 4 通道为 RGBA ,是RGB加上一个A通道,也叫alpha通道,表示透明度,PNG图像是一种典型的4通道图像。alpha通道可以赋值0到1,或者0到255,表示透明到不透明
大部分使用场景下,常使用的是1,3,4通道; 2通道不常见
2. CvType 类型常量组合规则 CV_[bite](U|S|F)C[channels] bite : 比特数,位数。 有 8bite,16bite,32bite,64bite,对应在 Mat 中,每个像素的所占的空间大小,8位即 CV_8 U|S|F : U : unsigned int , 无符号整形 S : signed int , 有符号整形 F : float , 单精度浮点型,float类型本身即有符号 这里的有符号、无符号是针对图像二进制编码来讲的。我在写的过程中大多数情况下都是使用的无符号,即 CV_8U ,CV_16U,当有计算时可能会介入有符号(存在负数),没学过 C++,对底层也一知半解,望高手解答。 C[channels] :图像的通道数
通过上边的解释,我想您已经明白了个大概,比如 CV_8UC3 即 8位无符号的3通道(RGB 彩色)图像
3. CvType 各 Bite 下可用参数说明 8U 说明:无符号的8位图 值:CV_8UC1,CV_8UC2,CV_8UC3,CV_8UC4 通道取值范围:0~255 8S 说明:有符号的8位图 值:CV_8SC1,CV_8SC2,CV_8SC3,CV_8SC4 通道取值范围:-128~127 16U 说明:无符号的16位图 值:CV_16UC1,CV_16UC2,CV_16UC3,CV_16UC4 通道取值范围:0~65535 16S 说明:有符号的16位图 值:CV_16SC1,CV_16SC2,CV_16SC3,CV_16SC4 通道取值范围:-32768~32767 32S 说明:无符号的32位图 值:CV_32SC1,CV_32SC2,CV_32SC3,CV_32SC4 通道取值范围:2147483648~2147483647 32F 说明:浮点型32位图 值:CV_32FC1,CV_32FC2,CV_32FC3,CV_32FC4 通道取值范围:1.18*(10[-38次方])~3.40*(10[38次方]) 64F 说明:浮点型64位图 值:CV_64FC1,CV_64FC2,CV_64FC3,CV_64FC4 通道取值范围:2.23*(10[-308次方])~1.79*(10[308次方]) 1U 说明:1位 值:IPL_DEPTH_1U 通道取值范围:0~1
这篇文章对8,16,32,64位下大小取值范围说明很详细 广告栏: 欢迎关注我的 个人博客
多媒体
2018-04-11 20:34:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
9. redis介绍 Redis和Memcached类似,也属于k-v数据存储 ,但是功能和操作性要比Memcached好很多。 Redis官网 redis.io,当前最新稳定版4.0.9,支持更多value类型,除了和string外, 还支持hash、lists(链表)、sets(集合)和sorted sets(有序集合) redis使用了两种文件格式:全量数据(RDB)和增量请求(aof)。 全量数据格式 是把内存中的数据写入磁盘,便于下次读取文件进行加载。 增量请求文件 则是把内存中的数据序列化为操作请求,用于读取文件进行replay得到数据,这种类似于mysql binlog。 为了节省资源,当我们手动保存一次全量数据,就可以删除当前的所有增量数据了,有些增量数据其实早就过期了,也可定期利用脚本做一些优化 。 redis的存储分为 内存存储、磁盘存储和log文件 三部分
10. redis安装
1 下载 官网地址 redis.io [root@yt-01 ~]# cd /usr/local/src [root@yt-01 src]# wget http://download.redis.io/releases/redis-4.0.9.tar.gz
2 安装 [root@yt-01 src]# tar zxvf redis-4.0.9.tar.gz //解压 [root@yt-01 src]# cd redis-4.0.9 //进入redis目录 [root@yt-01 redis-4.0.9]# make && make install //和其他软件不同,redis安装直接就可以编译 [root@yt-01 redis-4.0.9]# echo $? //确认编译是否成功 0 [root@yt-01 redis-4.0.9]# redis- //用tab查看redis的命令,确认是否安装成功 redis-benchmark redis-check-aof redis-check-rdb redis-cli redis-sentinel redis-server [root@yt-01 redis-4.0.9]# which redis-cli //查看redis的启动文件地址 /usr/local/bin/redis-cli [root@yt-01 redis-4.0.9]# ls //查看redis安装后的文件 00-RELEASENOTES BUGS CONTRIBUTING COPYING deps INSTALL Makefile MANIFESTO README.md redis.conf runtest runtest-cluster runtest-sentinel sentinel.conf src tests utils
3 配置文件 [root@yt-01 redis-4.0.9]# cp redis.conf /etc/redis.conf [root@yt-01 redis-4.0.9]# vim /etc/redis.conf //修改配置文件 …… daemonize yes #yes表示后台启动;no表示前台启动 pidfile /var/run/redis_6379.pid #pid存放位置 loglevel notice #指定日志级别:debug(调试,排错)、verbose(冗长的)、notice、warning #debug适合排查错误时使用,错误排查完毕后要换到notice,避免产生大量日志浪费系统空间 logfile "/var/log/redis.log" #定义日志存放路径 databases 16 #Redis有库的概念,默认在0库 dir /data/redis #定义rdb、aof文件的存放位置 appendonly yes #开启aof日志,开启后会在dir定义的目录生成appendonly.aof文件 appendfsync everysec #指定记录日志的规则:always(只要有变动就记录)、everysec(每秒记录一次)、no(不记录) 创建rdb、aof文件的存放文件夹 [root@yt-01 redis-4.0.9]# mkdir /data/redis 启动redis [root@yt-01 redis-4.0.9]# redis-server /etc/redis.conf 查看redis进程(因为虚拟机还运行着gitlab网站服务,所以进程会多几个) [root@yt-01 redis-4.0.9]# ps aux |grep redis root 5526 0.0 0.0 4184 16 ? Ss 10:10 0:00 runsv redis root 5527 0.0 0.0 4328 56 ? S 10:10 0:00 svlogd -tt /var/log/gitlab/redis gitlab-+ 5528 0.2 0.1 41484 3740 ? Ssl 10:10 0:36 /opt/gitlab/embedded/bin/redis-server 127.0.0.1:0 root 6041 0.0 0.0 4184 0 ? Ss 10:11 0:00 runsv redis-exporter root 6042 0.0 0.0 4328 36 ? S 10:11 0:00 svlogd -tt /var/log/gitlab/redis-exporter gitlab-+ 6043 0.0 0.4 53424 7976 ? Ssl 10:11 0:04 /opt/gitlab/embedded/bin/redis_exporter -web.listen-address=localhost:9121 -redis.addr=unix:///var/opt/gitlab/redis/redis.socket root 36317 0.0 0.1 145260 2184 ? Ssl 13:33 0:00 redis-server 127.0.0.1:6379 //默认监听端口是6379 查看redis日志 [root@yt-01 redis-4.0.9]# less /var/log/redis.log # 2条warning,提醒我们有2个内核参数需要调整 …… 36317:M 06 Apr 13:33:51.922 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect. 36317:M 06 Apr 13:33:51.922 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled. 36317:M 06 Apr 13:33:51.922 * Ready to accept connections /var/log/redis.log (END) 配置内核参数 [root@yt-01 redis-4.0.9]# sysctl vm.overcommit_memory=1 [root@yt-01 redis-4.0.9]# echo never > /sys/kernel/mm/transparent_hugepage/enabled 加入开机启动 [root@yt-01 redis-4.0.9]# vim /etc/rc.local sysctl vm.overcommit_memory=1 echo never > /sys/kernel/mm/transparent_hugepage/enabled 启动 [root@yt-01 redis-4.0.9]# redis-server /etc/redis.conf
4 关闭redis服务的方法 方法1: 查询到PID,kill -9 pid。 该方式相当于断电,非正常关闭,一般不用,会造成数据丢失 方法2 [root@yt-01 redis-4.0.9]# redis-cli shutdown
11. redis持久化 和Memcached服务一样,如果关闭RDB和aof数据就会存储在内存中,当重启服务或者重启机器 存储的数据就会丢失。如果数据很重要,我们就需要做持久化。 Redis提供了两种持久化的方式,分别是 RDB(Redis DataBase)和AOF(Append Only File) RDB,简而言之,就是在不同的时间点,将redis存储的数据生成快照并存储到磁盘等介质上。 AOF,则是换了一个角度来实现持久化,那就是将redis执行过的所有写指令记录下来,在下次redis重新启动时,只要把这些写指令从前到后再重复执行一遍,就可以实现数据恢复了。 其实RDB和AOF两种方式也可以同时使用,在这种情况下,如果redis重启的话,则会优先采用AOF方式来进行数据恢复,这是因为AOF方式的数据恢复完整度更高。 如果你没有数据持久化的需求,也完全可以关闭RDB和AOF方式,这样的话,redis将变成一个纯内存数据库,就像memcache一样。
1 redis持久化相关的参数 save 900 1 //表示每15分钟且至少有1个key改变,就触发一次持久化 save 300 10 //表示每5分钟且至少有10个key改变,就触发一次持久化 save 60 10000 //表示每60秒至少有10000个key改变,就触发一次持久 save " " //这样可以禁用rdb持久化 appendonly yes //如果是yes,则开启aof持久化 appendfilename “appendonly.aof” //指定aof文件名字 appendfsync everysec //每一秒写入aof文件,并完成磁盘同步。 # 指定fsync()调用模式,有三种 # no(不调用fsync),always(每次写都会调用fsync),everysec(每秒钟调用一次fsync)。 # 第一种最快,第二种数据最安全,但会占用磁盘I/O会差一些,第三种为这种方案最恰当,默认选第三种。
2 开启和关闭持久化 开启 [root@yt-01 ~]# vim /etc/redis.conf …… # save " " save 900 1 save 300 10 save 60 10000 关闭 [root@yt-01 ~]# vim /etc/redis.conf …… save " " # save 900 1 # save 300 10 # save 60 10000 设置完后,重启服务 # redis-cli -p 127.0.0.1 shutdown # redis-server /etc/redis.conf
12. redis数据类型 string list set sort set hash
1 Redis数据类型-string
string为最简单的类型,与Memcached一样的类型,一个key对应一个value,其支持的操作与Memcached的操作类似,它的功能更丰富。设置可以存二进制的对象。 [root@yt-01 ~]# redis-cli 127.0.0.1:6379> set mykey 123 OK 127.0.0.1:6379> get mykey "123" 127.0.0.1:6379> mset k1 a k2 b k3 c OK 127.0.0.1:6379> mget k1 k2 k3 1) "a" 2) "b" 3) "c" 127.0.0.1:6379> quit
2 Redis数据类型-list list是一个链表结构,主要功能是push、pop、获取一个范围的所有值等等。操作中key理解为链表的名字。 使用 list 结构,我们可以轻松地实现最新消息排行等功能(比如新浪微博的 TimeLine )。list 的另一个应用就是消息队列,可以利用 list 的 push操作,将任务存在 list 中,然后工作线程再用pop操作将任务取出进行执行。 [root@yt-01 redis-4.0.9]# redis-cli # 添加一条list数据 127.0.0.1:6379> lpush list1 "yuntai" (integer) 1 127.0.0.1:6379> lpush list1 "yuntai1" (integer) 2 127.0.0.1:6379> lpush list1 "yuntai2" (integer) 3 # 查看指定list的数据 127.0.0.1:6379> lrange list1 0 -1 1) "yuntai2" 2) "yuntai1" 3) "yuntai" # 取出(pop挤出)一条数据,挤出就没有了 127.0.0.1:6379> lpop list1 "yuntai2" # 取出数据list 从0到1 127.0.0.1:6379> lrange list1 0 -1 1) "yuntai1" 2) "yuntai" 127.0.0.1:6379> quit
3 Redis数据类型-set set是集合,和我们数学中的集合概念相似,对集合的操作有添加删除元素,有对多个集合求交并差等操作。操作中key理解为集合的名字。比如在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。因为 Redis 非常人性化的为集合提供了求交集、并集、差集等操作,那么就可以非常方便的实现如共同关注、共同喜好、二度好友等功能,对上面的所有集合操作,你还可以使用不同的命令选择将结果返回给客户端还是存集到一个新的集合中。 [root@yt-01 redis-4.0.9]# redis-cli # 添加一条set数据 127.0.0.1:6379> SADD set1 a (integer) 1 127.0.0.1:6379> SADD set1 b (integer) 1 127.0.0.1:6379> SADD set1 c (integer) 1 127.0.0.1:6379> SADD set1 d (integer) 1 # 查看set数据 127.0.0.1:6379> SMEMBERS set1 1) "d" 2) "c" 3) "a" 4) "b" 127.0.0.1:6379> SREM set1 c (integer) 1 127.0.0.1:6379> SADD set2 a 2 b (integer) 3 127.0.0.1:6379> SINTER set1 set2 1) "a" 2) "b" 127.0.0.1:6379> SUNION set1 set2 1) "d" 2) "a" 3) "2" 4) "b" 127.0.0.1:6379> SDIFF set1 set2 1) "d" 127.0.0.1:6379> quit
4 Redis数据类型-sort set sorted set是有序集合,它比set多了一个权重参数score,使得集合中的元素能够按score进行有序排列,比如一个存储全班同学成绩的Sorted Sets,其集合value可以是同学的学号,而score就可以是其考试得分,这样在数据插入集合的时候,就已经进行了天然的排序。 [root@yt-01 redis-4.0.9]# redis-cli # 添加sort set数据 127.0.0.1:6379> ZADD set3 12 abc (integer) 1 127.0.0.1:6379> ZADD set3 2 "cde 123" (integer) 1 127.0.0.1:6379> ZADD set3 24 "123-aaa" (integer) 1 127.0.0.1:6379> ZADD set3 4 "a123a" (integer) 1 # 查看数据set3 127.0.0.1:6379> ZRANGE set3 0 -1 1) "cde 123" 2) "a123a" 3) "abc" 4) "123-aaa" #数据的value根据score大小排序 # 倒序查看数据set3 127.0.0.1:6379> ZREVRANGE set3 0 -1 1) "123-aaa" 2) "abc" 3) "a123a" 4) "cde 123" 127.0.0.1:6379> quit
5 Redis数据类型- hash
在 Memcached 中,我们经常将一些结构化的信息打包成hashmap,在客户端序列化后存储为一个字符串的值(一般是JSON格式),比如用户的昵称、年龄、性别、积分等。 [root@yt-01 redis-4.0.9]# redis-cli # 添加一条hash数据hash1 k值为name,v值为yuntai 127.0.0.1:6379> hset hash1 name yuntai (integer) 1 # 查询数据hash1 k值为name的v值 127.0.0.1:6379> hget hash1 name "yuntai" 127.0.0.1:6379> hset hash1 age 27 (integer) 1 127.0.0.1:6379> hget hash1 age "27" # 查询hash1的所有值 127.0.0.1:6379> hgetall hash1 1) "name" 2) "yuntai" 3) "age" 4) "27" 127.0.0.1:6379> quit
多媒体
2018-04-08 14:03:00
「深度学习福利」大神带你进阶工程师,立即查看>>> MR.PS02 - Layer 2:
由长度和角度构建出的结构的容器(2-dim)
即由移动的长度作成和定点的角度作成可以遍历容器中的所有可能性.
允许: 直线作成, 定点定半径圆形作成
MR.PS02 - Layer 3:
直线作为线段的容器, 显式地表征了线段的标量特征
圆, 一中同长, 将标量特征传送到各个方向
有限的标量特征在平面上支撑起了作为有理形状的存在...
Details
Memory
直线作为线段的容器, 显式地表征了线段的标量特征, 该特确存在于记忆之海, 任许遍历.
First compute
圆作为容器的复现迁移媒介, 满足了手加减存在的合乎性.
Zero compute
平行线位似: 角元, 做出tan(y/x)
I.R. 0-cpt 不同于 ori-cpt , 不能表征本身相比出的纯粹数字1, 即模型只能通过第2层规则模拟以及解读第3层方法, 而不能表达第2层规则本身(5-层完备性: 层间不可迭代)
0.5 compute
直角三角形:斜边高的 平方 等于垂足分割两端线段乘积
以上
Example
17 - regutanger ~ cos (2π/17)
fig.1 cos(2π/17) = 1/16 · ( -1 + R2t(17) + R2t(34 - 2·R2t(17)))
+ 2·R2t( 17 + 3·R2t(17) - R2t(34 - 2·R2t(17)) - 2·R2t(34 + 2·R2t(17) ) )
To be continued..
多媒体
2018-04-08 12:46:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
感知哈希算法(perceptual hash algorithm),它的作用是对每张图像生成一个“指纹”(fingerprint)字符串,然后比较不同图像的指纹。结果越接近,就说明图像越相似。
实现步骤:
1. 缩小尺寸:将图像缩小到8*8的尺寸,总共64个像素。这一步的作用是去除图像的细节,只保留结构/明暗等基本信息,摒弃不同尺寸/比例带来的图像差异;
2. 简化色彩:将缩小后的图像,转为64级灰度,即所有像素点总共只有64种颜色;
3. 计算平均值:计算所有64个像素的灰度平均值;
4. 比较像素的灰度:将每个像素的灰度,与平均值进行比较,大于或等于平均值记为1,小于平均值记为0;
5. 计算哈希值:将上一步的比较结果,组合在一起,就构成了一个64位的整数,这就是这张图像的指纹。组合的次序并不重要,只要保证所有图像都采用同样次序就行了;
6. 得到指纹以后,就可以对比不同的图像,看看64位中有多少位是不一样的。在理论上,这等同于”汉明距离”(Hamming distance,在信息论中,两个等长字符串之间的汉明距离是两个字符串对应位置的不同字符的个数)。如果不相同的数据位数不超过5,就说明两张图像很相似;如果大于10,就说明这是两张不同的图像。 string strSrcImageName = "src.jpg"; cv::Mat matSrc, matSrc1, matSrc2; matSrc = cv::imread(strSrcImageName, CV_LOAD_IMAGE_COLOR); CV_Assert(matSrc.channels() == 3); cv::resize(matSrc, matSrc1, cv::Size(357, 419), 0, 0, cv::INTER_NEAREST); //cv::flip(matSrc1, matSrc1, 1); cv::resize(matSrc, matSrc2, cv::Size(2177, 3233), 0, 0, cv::INTER_LANCZOS4); cv::Mat matDst1, matDst2; cv::resize(matSrc1, matDst1, cv::Size(8, 8), 0, 0, cv::INTER_CUBIC); cv::resize(matSrc2, matDst2, cv::Size(8, 8), 0, 0, cv::INTER_CUBIC); cv::cvtColor(matDst1, matDst1, CV_BGR2GRAY); cv::cvtColor(matDst2, matDst2, CV_BGR2GRAY); int iAvg1 = 0, iAvg2 = 0; int arr1[64], arr2[64]; for (int i = 0; i < 8; i++) { uchar* data1 = matDst1.ptr(i); uchar* data2 = matDst2.ptr(i); int tmp = i * 8; for (int j = 0; j < 8; j++) { int tmp1 = tmp + j; arr1[tmp1] = data1[j] / 4 * 4; arr2[tmp1] = data2[j] / 4 * 4; iAvg1 += arr1[tmp1]; iAvg2 += arr2[tmp1]; } } iAvg1 /= 64; iAvg2 /= 64; for (int i = 0; i < 64; i++) { arr1[i] = (arr1[i] >= iAvg1) ? 1 : 0; arr2[i] = (arr2[i] >= iAvg2) ? 1 : 0; } int iDiffNum = 0; for (int i = 0; i < 64; i++) if (arr1[i] != arr2[i]) ++iDiffNum; cout<<"iDiffNum = "< 10) cout<<"they are two different images!"<
多媒体
2018-04-07 16:52:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
操作步骤:
1. 载入图像(灰度图或者彩色图),并使其大小一致;
2. 若为彩色图,增进行颜色空间变换,从RGB转换到HSV,若为灰度图则无需变换;
3. 若为灰度图,直接计算其直方图,并进行直方图归一化;
4. 若为彩色图,则计算其彩色直方图,并进行彩色直方图归一化;
5. 使用相似度公式,如相关系数、卡方、相交或巴氏距离,计算出相似度值。 string strSrcImageName = "src.jpg"; cv::Mat matSrc, matSrc1, matSrc2; matSrc = cv::imread(strSrcImageName, CV_LOAD_IMAGE_UNCHANGED); cv::resize(matSrc, matSrc1, cv::Size(357, 419), 0, 0, cv::INTER_NEAREST); cv::resize(matSrc, matSrc2, cv::Size(2177, 3233), 0, 0, cv::INTER_LANCZOS4); cv::Mat matDst1, matDst2; cv::Size sizeImage = cv::Size(500, 500); cv::resize(matSrc1, matDst1, sizeImage, 0, 0, cv::INTER_CUBIC); //cv::flip(matDst1, matDst1, 1); cv::resize(matSrc2, matDst2, sizeImage, 0, 0, cv::INTER_CUBIC); if (matSrc.channels() == 1) { int histSize = 256; float range[] = {0, 256}; const float* histRange = {range}; bool uniform = true; bool accumulate = false; cv::Mat hist1, hist2; cv::calcHist(&matDst1, 1, 0, cv::Mat(), hist1, 1, &histSize, &histRange, uniform, accumulate); cv::normalize(hist1, hist1, 0, 1, cv::NORM_MINMAX, -1, cv::Mat()); cv::calcHist(&matDst2, 1, 0, cv::Mat(), hist2, 1, &histSize, &histRange, uniform, accumulate); cv::normalize(hist2, hist2, 0, 1, cv::NORM_MINMAX, -1, cv::Mat()); double dSimilarity = cv::compareHist(hist1, hist2, CV_COMP_CORREL);//,CV_COMP_CHISQR,CV_COMP_INTERSECT,CV_COMP_BHATTACHARYYA cout<<"similarity = "<jar 包引用 以下jar包在maven 库中没有,maven最新的是5的,但是那个貌似有问题 dcm4che-tool-dcm2jpg-2.0.23.jar dcm4che-core-2.0.23.jar dcm4che-image-2.0.23.jar dcm4che-imageio-2.0.23.jar dcm4che-imageio-rle-2.0.23.jar slf4j-log4j12-1.5.0.jar slf4j-api-1.5.0.jar log4j-1.2.13.jar commons-cli-1.2.jar
报错解决方案
核心关键是安装jai_imageio库: 安装32位的JDK 将jai_imageio-1_0_01-lib-windows-i586-jdk 安装到32位JDK 切换java运行环境为32 位JDK
备注:按照dcm4chee官方安装说明文档,dcm4chee以及dcm4che2-tools在Linux32bits以及Linux64bits,以及Windows32bits可以顺利完成dcm压缩和解压缩,唯独Windows64bits环境不可,32位环境的问题。
参考
csdn博客大神zssure的文章
How to convert dicom file to jpg conversion
多媒体
2018-04-04 09:32:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
因为题主是从光学角度提问的,下面就从光学角度出发作答,当然实际上无法绕开数学。
球谐函数和振动有关,从某种意义上来说,它和三角函数没什么区别。因为它们只是在“不同坐标系”下描述“不同方向”的振动。
我们知道,麦克斯韦方程导出的波动方程:
(均匀各向同性介质)是描述许多光学现象的出发点。直接假定场在时间上是简谐振动的 (单色光分析),立刻就有 ,其中:
当然这个方程可以描述很多种现象,如果把E理解为温度T,并取:
这就是稳态热扩散方程;如果把 理解为特征值,这就是单自由粒子的定态薛定谔方程。所以这个方程的解,及其表现出的一系列振动特征,在许多领域都是普适的。
我们通常会在3种坐标系下求解这个方程,也就是矩坐标、柱坐标、球坐标。
球坐标系:
球坐标和直角坐的互换:

具体应用,在光学中,比如矩形腔、矩形波导,圆柱腔、圆柱波导,球型腔。热学中,可以有方块、圆柱、球的热扩散问题。量子力学里可以有方势阱、柱状阱、有心力场(氢原子)中的粒子运动问题。
每种坐标系都有3个方向,矩坐标系x、y、z,柱坐标系 ,球坐标系 。上述方程在每种坐标系的每个方向上都会形成特定的振荡形态(有时会出现衰减或放大形态)。球谐函数 描述的就是球坐标系中在 方向的振荡形态。这件事通过分离变量法可以看得很清楚。
方程的具体求解都是通过分离变量进行的,具体是:
矩坐标:
柱坐标 :
球坐标 :
具体求解过程教科书上都有,这里不再赘述,只看结果。注意,分离变量后的每一个子函数都描述了一个特定方向的形态。
矩坐标系的处理在数学上是最容易的,我们知道三个方向都有相似的振荡模式,由三角函数描述,比如 、 ,一般写为 。如果 是实数,就是一个振荡;如果是虚数,就是一个指数衰减(或放大)。
在柱坐标系中, 和矩坐标系没什么区别,也是 的形式。 的解是贝塞尔函数,注意,贝塞尔函数 和 描述的就是径向振荡形态, 和 描述的是径向放大或衰减形态。这和 有相似的意义。 具有 形式的解,也是一个振荡(由于 向通常要求周期性,故没有非振荡解),只是角向振荡。
在球坐标系中, 的解是球贝塞尔函数,意义和柱坐标系下类似。 仍旧具有 形式的解(同样有周期性要求)。 描述了 方向的振荡,只不过具体数学形式比较复杂(涉及勒让德函数)。“球谐函数”就是 。
球坐标系: ,3D图:
2D密度图:
最后,上面出现的各种函数都有各自的正交完备性,类似于三角函数的正交完备性。所以可以用来展开其他函数,正如傅里叶变换。
多媒体
2018-04-02 14:29:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
1.安装截图工具cutycapt
输入命令:sudo apt-get install cutycapt
2.安装看图工具eog
输入命令:sudo apt-get install eog
3.使用截图工具cutycapt
输入命令:cutycapt --url=http://china.nba.com/ --out=localfile.png (这里截取的是NBA的网页)
4.使用查图工具eog
输入命令:eog localfile.png
注:查看工具的使用方法可以输入命令: cutycapt --help 以此来获取使用方法
多媒体
2018-03-30 22:28:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
1首先按照网上面的方法下载源代码,https://www.imagemagick.org/download/windows/releases/
需要下载windows对应的源码。
2. 编译configure.exe 打开目录ImageMagick-7.0.7-28\VisualMagick\configure 编译之, 这里编译没有问题,但为了配合vs2017 需要修改源码中对于工程配置的相关部分。
修改平台工具为
else if (_wizard->visualStudioVersion() == VS2017)
file << " v141" << endl;
添加windows sdk的版本,大约在646行左右的位置
file << " " << _wizard->platform() << "Proj" << endl;
file << " " << "10.0.15063.0" << "" << endl;
然后编译工具即可。
3.第三步,生成vs工程,注意这里不要勾选opencl,如果你没有相应的sdk。
4.打开工程编译,这里会遇到一个地方编译通不过,全局搜索 _InterlockedAdd'修改为 InterlockedAdd。这里网上说是windows sdk版本引起的问题,(https://stackoverflow.com/questions/14603407/why-interlockedadd-is-not-available-in-vs2010)然后编译生成即可。 至此可以编译完成。
多媒体
2018-03-28 13:34:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
永远都在寻觅字体设计的灵感。夏天过后,我买了一套便宜的书法钢笔,说服自己,它会让我的鸡爬字产生脱胎换骨的变化。在浪费了一个星期和几
永远都在寻觅字体设计的灵感。夏天过后,我买了一套便宜的书法钢笔,说服自己,它会让我的鸡爬字产生脱胎换骨的变化。在浪费了一个星期和几打白纸之后,我还是没得到什么有趣的或是有稍微艺术气息的东西。最后,在一天晚上,疲倦而失望的我,在用完了黑色墨水之后,插入了一支红色的笔芯,然后写下了下面这一套字母表——之后它变成了我的 Joules字族。我想,如果我在这个案例研究中把它如何变成字体的过程写出来,大家也许会感兴趣。
下面是那天晚上我写下的好多张草图之一:

特写:

超级特写:我迫不及待想使用的一个大写A。

从草图到字体
从草图创建Joules字体的过程,一如我之前的 两篇文章中所写的,我将草图扫描输入,下面是扫描稿在Photoshop里面转为黑白位图模式之后的样子。

注意原稿位图中包含的粗糙杂点:


通常,我会把这些位图处理干净之后才导入Fontlab,但这次例外。下面是第一个步骤,将位图导入ScanFont:

在ScanFont中的一个特写:

我把这个新字符复制到FontLab中的正确位置。为了给你体验一下在FontLab中是如何进行调整的,下面放大显示了导入进来的粗糙的A。我选择了一个有问题的部分。

然后开始删去那些令人不快的节点:

在FontLab中调节字符的时候,我们必须在两种诱惑中做一个平衡:一方面要使得轮廓尽量的平滑,另一方面,又应该尽可能的留下一些粗糙的节点,以保留字体的趣味。我发觉,对书法字体而言,不应该平滑掉所有的粗糙节点,在某种程度上,那等于剥夺了它们书法的感觉。
救人危难的字符合成
FontLab的一个便捷之处,是它可以自动的合成字符。本例中我先创建了一个A,然后又建了一个“重音符”:

然后我双击这个“A-重音符”的小格…

…FontLab就创建了一个合成字符:

这时候,如果你编辑这个A或者那个重音符,变化会立即的自动更新到那个合成的“A重音符”上。
侧架
我前面几篇关于字体设计的文章中也提到了,设置合适的侧架是一个重要的步骤。(一个原因是,恰当的侧架会让字距调整变得简单!)先为字符做粗调,我粗略设置了一个很小的、正的侧架。在初始编辑时,这个y的侧架显示如下:

这样设置会有什么问题,在初始字距设置中看a和y的配对就能看出来了。

其实我可以保留这个y的侧架不变,然后在字距配对调整中,调节y靠近a,然后依此类推,在每一个字距配对中都调节y靠近其他的字符。但很明显,为这个y的左侧设置一个负的侧架的做法要更简单,也更明智:

下面就是改进后的留白设置在默认字距调整情况下的样子:

Kerning 字距调整
天,为这个字体设置字距花了我几个小时的时间!其中的烦人细节…我还是放过你好了。但这里还有另外一个字距调整的实例。在调整前是这样的:

调节后:

Ligatures 连字
我在Joules中创建了相当数量的连字,使用者在排版时可以手动选择应用它们。

下面举一个实例说说我是如何创建这些连字的。首先,我们先来看看这个z和a原始的连排效果。

其实我可以在字距配对的时候让它们以一种时髦的方式漂亮的重叠在一起,但更负责的做法还是做一个z-a的连字。第一步,创建一个空白字符,然后把z和a复制进来:

第二步,切开它们的轮廓,让它们能在合适的位置结合:

第三步,删除多余的部分:

第四步,移动字符让它们靠近:

第五步,连接节点:


智能连字
Joules的第一个版本中并没有包含智能连字:这种技术是我最近才学会的。其中的细节我就不唠叨了,但可以告诉你的是,需要在Fontlab中打开一个特殊的OpenType面板,并且通常还要写一些脚本,这样才能在一些能够识别连字的软件中激活你所创建的连字。大体上看起来是这个样子:


完成
这就是经过了无数次修整和字距调整之后的最终结果:

我后来又接着给它做了一个意大利体的版本(好吧,其实更象是一个斜体,你们这些吹毛求疵的家伙),然后是一个粗体、粗意大利体,然后是大黑体。如果还有人感兴趣,我可以继续说说其中的细节。
[Alec Julien居住于美国佛蒙特州,是一个网络开发人员和业余字体设计师。他的梦想是有一天能搬到一个温暖的地方,为一部小说做排版。]
本文转载自译言网
原文地址: http://article.yeeyan.org/view/snlchina/3433
多媒体
2018-03-26 22:09:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
1 纹理映射(Texture Mapping)
首先让我们一起来观察这样一张图:
无论是球上的图案,以及地板的木头纹理都呈现出了不同的颜色信息,那么回想在讲解Blinn-Phong反射模型的时候曾提到,一个点的颜色是由其漫反射系数决定的,反射什么颜色的光,人眼就能看见什么颜色。那么针对上面这幅图,难道要去针对每一个点自己去设定一个颜色吗?还是说有什么更方便的方法呢?那便是 纹理映射 了!
我们可以将三维物体上的任意一个点都映射到一个2维平面之上,举一个简单例子,地球仪:
倘若拥有从3维World space到2维Texture space的一个映射关系,那么只需要将每个点的颜色信息即漫反射系数存储在2维的Texture之上,每次利用光照模型进行计算的时候根据映射关系就能查到这个点的漫反射系数是多少,所有点计算完之后,结果就像最左边的screen space之中,整个Texture被贴在了模型之上。
有了Texture,有了映射关系,对渲染结果会有一个非常大提升,因为很多fancy的效果都可以通过texture的设计得到(当然这属于美术的活儿了,咱们用就行了)。可以看这样一个有点丑的例子。
可以看到在利用texture渲染之后,这个独眼怪物丑的更有特点了。
好了,相信到这大家都对纹理映射了有了一个大概的了解,那么有了一张Texture之后,这种映射关系究竟是如何表示的呢?这就要从纹理坐标(UV)说起了。在纹理空间之内任意一个二维坐标都在[0,1]之内。如下图是一个可视化纹理坐标的结果:
横轴和纵轴的最大值都为1,为什么整幅texture图可视化之后是红色和绿色呢?可以将(u,v)坐标的两点想象成red和green就能明白了。一幅Texture上的任意一点都可以用一个(u,v)坐标来表示(0<=u<=1,0<=v<=1),因此只需要在三维world space中每个顶点的信息之中存储下该顶点在texture space的(u,v)坐标信息,自然而然的就直接的得到了这种映射关系。至于一个顶点所对应在纹理空间的坐标是怎么得到的,这就并不是程序员们关心的了,美术大大们会帮我们最好的(当然读者有兴趣也可以自行搜集资料)。
有一种特殊的纹理称为tile,这种纹理的特征是重复拼接之后上下左右都是连续的,因此这种纹理可以复制很多张贴在墙面或地板上。
一个具体的重复利用这种贴图的例子如下:
最后给出一个纹理坐标使用的伪代码供参考:
简而言之就是对每个光栅化的屏幕坐标算出它的uv坐标(利用三角形顶点重心坐标插值),再利用这个uv坐标去查询texture上的颜色,把这个颜色信息当作漫反射系数Kd。
好了在理解了纹理映射的基础之后,考虑如果纹理精度特别小(reslution低)或者纹理精度特别大(reslution大)会分别引起什么问题呢?
2 纹理过小和纹理过大的问题及解决方案
2.1 纹理过小引发的问题
纹理过小的问题相对容易理解,想想我们把一张100x100的纹理贴图应用在一500x500的屏幕之上必然会导致走样失真,因为屏幕空间的几个像素点对应在纹理贴图的坐标上都是集中在一个像素大小之内。那么如果仅仅是使用对应(u,v)坐标在texture贴图下最近的那个像素点,往往会造成严重的走样。
如图中红色点是屏幕空间下一像素所对应在texture空间中的点,会去选择离他最近的那个橙色框起来的点。
这种方法是不可取的,接下来会介绍利用双线性插值的方法缓解这种走样现象。
2.2 双线性插值(Bilinear Interpolation)
我们依然取上图的点作为例子,解释双线性插值。 第一步,取出离红色点最近的4个黑色顶点,分别算出,该红色点在水平及竖直方向偏移的比率s,t,图示如下:
接着先利用s,可以线性插值出如下图所示的u0,u1点的颜色值
那么下一步相信读者也能猜到了,利用比例t,颜色值u0,u1插值出红色点的颜色值
如此这样利用两次线性插值,考虑到了所有4个点的颜色值,能够很好的缓解走样失真现象,并且计算速度较高。
(tips:还有一种插值方法叫做双三次插值(Bicubic),是利用三次方程来进行两次插值,效果可能更好,但是计算速度很低不在这里具体讨论了)
最后以一张闫老师课上的例子看看这3种方法效果的对比
可能会看不太清。理解个意思就好
2.3 纹理过大引发的问题
可能对于我们的第一直觉来说,纹理小确实会引发问题,但是纹理大那不是更好吗,为什么会引发问题呢?但事实是纹理过大所引发的走样甚至会更加严重。 想象一张很大的地板,在上面铺满了重复的方格贴图,我们所期望看到的结果应该是这样的:
嗯,非常符合透视关系,不错,当然这只是一个参考。再来看看利用在第一章所提到的计算纹理颜色的伪代码来计算的结果呢:
近处锯齿!远处摩尔纹!非常严重的走样现象,为什么会导致这样的一个现象呢?这里作者尝试给出自己的两种解释:
1 如开头所说,地板上铺满了重复的方格贴图,根据近大远小,远处的一张完整的贴图可能在屏幕空间中仅仅是几个像素的大小,那么必然屏幕空间的一个像素对应了纹理贴图上的一片范围的点,这其实就是纹理过大所导致的,直观来说想用一个点采样的结果代替纹理空间一片范围的颜色信息,必然会导致严重失真!(从信号的角度来说就是,采样频率过低无法还原信号原貌)
2 换一种想法,考虑离相机很远的一个三角形面,假设该三角形面真正在纹理贴图上对应的一片区域有10个像素点。但是由于透视的关系,距离很远的三角形面投影到近平面时可能只有1个或2个像素点的大小(远远小于10个像素的原来大小),那么这1个或2个像素采样texture的结果就要代表原来这个三角形面10个像素点的颜色信息,自然会导致失真!
(tips:可能有读者一开始会疑惑(包括我也是)为什么1个屏幕空间像素点覆盖多个纹理空间像素就是纹理过大呢,想象一下纹理贴图大小500x500,屏幕空间100x100,将屏幕空间的像素点均匀分布在纹理空间之中,那么1个屏幕空间像素点所占的平均大小就是5x5=25个纹理空间像素,因此这就是纹理过大所导致的结果)
这种现象被形象的成为屏幕像素在texture空间的footprint。如上图所示一个屏幕空间的蓝色像素点离相机越远,对应在texture空间的范围也就越大。其实也就是越来越欠采样,那么一种直观的解决方法就是Supersampling,如果一个像素点不足以代表一个区域的颜色信息,那么便把一个像素细分为更多个小的采样点不就可以解决这个问题了吗?对,确实是这样,可以看看如下图512x超采样的结果
效果虽称不上完美但也极大缓解了走样现象,但问题是什么?计算量太大了,一个像素点被分为了512x512个采样点,计算量几乎多出了25万倍!这显然不是所希望看到的,并且随着屏幕空间的点离相机距离更远,更多的texels(纹理空间的像素)会在屏幕像素的一个footprint里面,会要更高的超采样频率。
那么另外一种想法,如果不去超采样,仅仅是求出每个屏幕像素所对应的footprint里所有texels的颜色均值呢?这也就是接下来所要介绍的著名的Mipmap技术了!
2.3 Mipmap
回顾一下屏幕像素在Texture空间里的footprint的这张图:
正如上文所提,一个采样点的颜色信息不足以代表 “footprint”里一个区域的颜色信息,如果可以求出这样一个区域里面所有颜色的均值,是不是就是一种可行的方法呢?没错我们的目标就是从点查询Point Query迈向区域查询Range Query。但依然存在一个问题,从上图不难看出,不同的屏幕像素所对应的footprint size是不一样大小的,看下图这样一个例子:
远处圆圈里的footprint必然比近处的要大,因此必须要准备不同level的区域查询才可以,而这正是Mipmap。
level 0代表的是原始texture,也是精度最高的纹理,随着level的提升,每提升一级将4个相邻像素点求均值合为一个像素点,因此越高的level也就代表了更大的footprint的区域查询。接下来要做的就是根据屏幕像素的footprint大小选定不同level的texture,再进行点查询即可,而这其实就相当于在原始texture上进行了区域查询!
那么如何去确定使用哪个level的texture呢?利用屏幕像素的相邻像素点估算footprint大小再确定level D!如下图:
在屏幕空间中取当前像素点的右方和上方的两个相邻像素点(4个全取也可以),分别查询得到这3个点对应在Texture space的坐标,计算出当前像素点与右方像素点和上方像素点在Texture space的距离,二者取最大值,计算公式如图中所示,那么level D就是这个距离的log2值 (D = log2L) ! 这不难理解,读者可以具体取几个例子比如L = 1,L = 2,L = 4,看看是否符合这样的计算即可。
但是这里D值算出来是一个连续值,并不是一个整数,有两种对应的方法:
1 四舍五入取得最近的那个level D
2 利用D值在 向下和向上取整的两个不同level进行3线性插值
第一个方法很容易理解,具体讲述一下第二个方法,如图:
所谓3线性插值,就是在向下取整的D level上进行一次双线性插值(前文提过),再在D+1 level之上进行一次双线性插值,这二者数据再根据实际的连续D值在向下和向上取整的两个不同level之间的比例,再来一次线性插值,而这整体就是一个三线性插值了。
好了!根据上述的方法算出屏幕上每一个像素点所对应的Mipmap level,再进行三线性插值得到颜色值,是否就能很好的解决走样问题了呢?很遗憾,在本文的那个地板的例子之中,费了这么大力气依然不能完美解决,如下图结果:
虽然和一开始的point sample有了很大的进步,但是有一个严重的问题是,远处的地板产生一种过曝的现象,完全糊在了一起。该如何解决这个最后的问题呢——各向异性过滤。
2.4 各向异性过滤Mipmap
好,接着上文的远处产生过曝的问题继续来谈,产生这种现象的原因是因为,所采用的不同level的Mipmap默认的都是正方形区域的Range Query,然而真实情况并不是如此,见下图:
可以看出不同screen space的像素点所对应的footprint是不同的,有长方形,甚至是不规则图形,那么针对这种情况,有的所需要的是仅仅是水平方向的高level,有的需要的仅仅是竖直方向上的高level,因此这也就启发了各向异性的过滤:
(个人感觉,应该是要算出水平方向的level D0,再算一个竖直方向的level D1,然后算根据这两个level去各项异性过滤的texture里面找一张最合适的)
利用这样不同的贴图,更加精细的选择后结果就会明显好很多:

可以看出,远处过曝的现象已经大大减少了。
(Note:其实各向异性过滤并不能解决 diagonal的footprint,因为各向异性只能解决水平或竖直的不同大小的矩形footprint,所以针对diagonal的footprint,一般是去sample更多的点,或者提前算好diagonal 过滤的texture
多媒体
2020-08-07 11:35:00
「深度学习福利」大神带你进阶工程师,立即查看>>> 绘制函数
该方法会执行下面的操作:
1.从交换链获取图像;
2.用该图像作为帧缓冲中的附件来执行命令缓冲;
3.将图像返回给交换链以便呈现。
这些事件每一个都是使用单个方法调用来启动的,但是它们 都是异步 的。方法调用会在实际操作结束之前返回,且执行顺序也是不一定的。这就很不幸了,因为每个操作都依赖于之前的操作完成才行。
有两种方式来 同步交换链 事件:栅栏和信号量。它们俩对象都能用于协调操作,方式就是设置一个操作信号,另一个操作等到一个栅栏或者信号量,然后从一个未标记的状态变成标记的状态。
不同之处是栅栏状态可以从你的程序中通过类似vkWaitForFences的调用来访问,而信号量却不行。栅栏主要是设计用于同步你的 应用和渲染 操作的,然而信号量用于同 步命令队列操作 。想要同步绘制命令和呈现的序列操作,选用信号量最合适。
尽管现在我们设置了需要的对象来促进同时使用多帧,我们还是没有防止有多余MAX_FRAMES_IN_FLIGHT的工作提交。当前只有GPU-GPU同步而没有CPU-GPU同步以跟踪任务如何进行。我们可能在0号帧还未准备好的时候用0号帧的对象。
为了进行CPU-GPU同步,Vulkan提供了第二种同步原语叫做 栅栏 。栅栏和信号量类似,它们可以被标记和等待,但是这次我们实际上在自己的代码中等待。
多媒体
2020-08-07 11:34:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
若该文为原创文章,未经允许不得转载 原博主博客地址: https://blog.csdn.net/qq21497936 原博主博客导航: https://blog.csdn.net/qq21497936/article/details/102478062 本文章博客地址: https://blog.csdn.net/qq21497936/article/details/107837715 各位读者,知识无穷而人力有穷,要么改需求,要么找专业人士,要么自己研究 红胖子(红模仿)的博文大全:开发技术集合(包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬结合等等)持续更新中...(点击传送门)
OpenCV开发专栏(点击传送门)
上一篇:《 OpenCV开发笔记(六十八):红胖子8分钟带你使用特征点Flann最邻近差值匹配识别(图文并茂+浅显易懂+程序源码) 》 下一篇:持续补充中…


前言
  红胖子,来也!   特征点、匹配,那么如何使用特征点和匹配来识别已有的物体,也就剩最关键的最后一步:寻找已知的物体了。


Demo
           


寻找已知物体
本篇章使用sift/surf特征点
sift特征点
  尺度不变特征变换(Scale-invariant feature transform,SIFT),是用于图像处理领域的一种描述。这种描述具有尺度不变性,可在图像中检测出关键点,是一种局部特征描述子。
surf特征点
  SURF算法采用了很多方法来对每一步进行优化从而提高速度。分析显示在结果效果相当的情况下SURF的速度是SIFT的3倍。SURF善于处理具有模糊和旋转的图像,但是不善于处理视角变化和光照变化。(SIFT特征是局部特征,其对旋转、尺度缩放、亮度变化保持不变性,对视角变化、仿射变换、噪声也保持一定程度的稳定性)。   针对图像场景的特点,选择不同的特征点,列出之前特征点相关的博文:   《 OpenCV开发笔记(六十三):红胖子8分钟带你深入了解SIFT特征点(图文并茂+浅显易懂+程序源码) 》   《 OpenCV开发笔记(六十四):红胖子8分钟带你深入了解SURF特征点(图文并茂+浅显易懂+程序源码) 》   《 OpenCV开发笔记(六十五):红胖子8分钟带你深入了解ORB特征点(图文并茂+浅显易懂+程序源码) 》
本篇章使用暴力、最邻近差值匹配
暴力匹配
  最佳特征匹配总是尝试所有可能的匹配,从而使得它总能够找到最佳匹配,这也是BruteForce(暴力法)的原始含义,涉及到的类为BFMatcher类。   《 OpenCV开发笔记(六十七):红胖子8分钟带你深入了解特征点暴力匹配(图文并茂+浅显易懂+程序源码) 》
最近邻差值匹配
  一种近似法,算法更快但是找到的是最近邻近似匹配,所以当我们需要找到一个相对好的匹配但是不需要最佳匹配的时候往往使用FlannBasedMatcher。   《 OpenCV开发笔记(六十八):红胖子8分钟带你使用特征点Flann最邻近差值匹配识别(图文并茂+浅显易懂+程序源码) 》
概述
  对已知物体:过滤、去噪后、提取已知物体的特征点;   对场景:过滤、去噪后、提取场景的特征点;   对已知物体特征点集合和场景中的特征点集合去匹配,计算投影矩阵;   若成功计算变换矩阵就表示识别到物体;   通过原始的四个点位置进行变换矩阵计算,即可得到场景中的已知物体的四个顶点,该四个顶点连接起来就是已知物体的位置。
特征点集合计算变换矩阵函数原型 Mat findHomography(InputArray srcPoints, InputArray dstPoints, int method = 0, double ransacReprojThreshold = 3, OutputArray mask=noArray(), const int maxIters = 2000, const double confidence = 0.995); 参数一 :InputArray类型的srcPoints,源平面上的对应点,可以是CV_32FC2的矩阵类型或者vector; 参数二 :InputArray类型的dstPoints;目标平面上的对应点 , 可 以 是 CV 32FC2 的矩阵类型或者 vector; 参数三 :int类型的method,用于计算单应矩阵的方法,如下图:    参数四 :double类型的ransacReprojThreshold,最大允许重投影错误将点对视为内联线(仅用于RANSAC和RHO方法); 参数五 :OutputArray类型的mask,由鲁棒方法(RANSAC或LMEDS)设置的可选输出掩码。注意输入掩码值被忽略。; 参数六 :const int类型的maxIters,RANSAC迭代的最大数量。; 参数七 :const double类型的confidence,置信水平,介于0和1之间;
矩阵变换函数原型 void perspectiveTransform( InputArray src, InputArray dst, InputArray m); 参数一 :InputArray类型的src,输入两通道或三通道浮点数组;每个元素是要转换的二维/三维向量。 参数二 :InputArray类型的dst,与src大小和类型相同的输出数组; 参数三 :InputArray类型的h,3x3或4x4浮点转换矩阵。


本文章博客地址: https://blog.csdn.net/qq21497936/article/details/107837715
Demo源码 void OpenCVManager::testFindKnownObject() { QString fileName1 = "21.jpg"; QString fileName2 = "24.jpg"; int width = 400; int height = 300; cv::Mat srcMat = cv::imread(fileName1.toStdString()); cv::Mat srcMat3 = cv::imread(fileName2.toStdString()); cv::resize(srcMat, srcMat, cv::Size(width, height)); cv::resize(srcMat3, srcMat3, cv::Size(width, height)); cv::String windowName = _windowTitle.toStdString(); cvui::init(windowName); cv::Mat windowMat = cv::Mat(cv::Size(srcMat.cols * 2, srcMat.rows * 3), srcMat.type()); cv::Ptr _pSift = cv::xfeatures2d::SiftFeatureDetector::create(); cv::Ptr _pSurf = cv::xfeatures2d::SurfFeatureDetector::create(450, 10, 10, true, true); cv::Ptr _pFeature2D; cv::Ptr _pDescriptorMatcher; int type = 0; int findType = 0; int k1x = 25; int k1y = 25; int k2x = 75; int k2y = 25; int k3x = 75; int k3y = 75; int k4x = 25; int k4y = 75; // 定义匹配器 cv::Ptr pFlannBasedMatcher = cv::FlannBasedMatcher::create(); cv::Ptr pBFMatcher = cv::BFMatcher::create(); // 定义结果存放 std::vector listDMatch; // 存储特征点检测器检测特征后的描述字 cv::Mat descriptor1; cv::Mat descriptor2; bool moveFlag = true; // 移动的标志,不用每次都匹配 std::vector obj_corners(4); std::vector scene_corners(4); windowMat = cv::Scalar(0, 0, 0); while(true) { cv::Mat mat; { std::vector keyPoints1; std::vector keyPoints2; int typeOld = type; int findTypeOld = findType; int k1xOld = k1x; int k1yOld = k1y; int k2xOld = k2x; int k2yOld = k2y; int k3xOld = k3x; int k3yOld = k3y; int k4xOld = k4x; int k4yOld = k4y; mat = windowMat(cv::Range(srcMat.rows * 0, srcMat.rows * 1), cv::Range(srcMat.cols * 0, srcMat.cols * 1)); mat = cv::Scalar(0); cvui::trackbar(windowMat, 0 + width * 0, 0 + height * 0, 165, &type, 0, 1); cv::String str; switch(type) { case 0: str = "sift"; _pFeature2D = _pSift; break; case 1: str = "surf"; _pFeature2D = _pSurf; break; default: break; } cvui::printf(windowMat, width / 4 + width * 0 - 20, 40 + height * 0, str.c_str()); cvui::trackbar(windowMat, width / 2 + width * 0, 0 + height * 0, 165, &findType, 0, 1); switch(findType) { case 0: str = "BFMatcher"; _pDescriptorMatcher = pBFMatcher; break; case 1: str = "FlannBasedMatcher"; _pDescriptorMatcher = pFlannBasedMatcher; break; default: break; } cvui::printf(windowMat, width / 4 * 3 + width * 0 - 20, 40 + height * 0, str.c_str()); cvui::printf(windowMat, 0 + width * 0, 60 + height * 0, "k1x"); cvui::trackbar(windowMat, 0 + width * 0, 70 + height * 0, 165, &k1x, 0, 100); cvui::printf(windowMat, 0 + width * 0, 120 + height * 0, "k1y"); cvui::trackbar(windowMat, 0 + width * 0, 130 + height * 0, 165, &k1y, 0, 100); cvui::printf(windowMat, width / 2 + width * 0, 60 + height * 0, "k2x"); cvui::trackbar(windowMat, width / 2 + width * 0, 70 + height * 0, 165, &k2x, 0, 100); cvui::printf(windowMat, width / 2 + width * 0, 120 + height * 0, "k2y"); cvui::trackbar(windowMat, width / 2 + width * 0, 130 + height * 0, 165, &k2y, 0, 100); cvui::printf(windowMat, 0 + width * 0, 30 + height * 0 + height / 2, "k3x"); cvui::trackbar(windowMat, 0 + width * 0, 40 + height * 0 + height / 2, 165, &k3x, 0, 100); cvui::printf(windowMat, 0 + width * 0, 90 + height * 0 + height / 2, "k3y"); cvui::trackbar(windowMat, 0 + width * 0, 100 + height * 0 + height / 2, 165, &k3y, 0, 100); cvui::printf(windowMat, width / 2 + width * 0, 30 + height * 0 + height / 2, "k4x"); cvui::trackbar(windowMat, width / 2 + width * 0, 40 + height * 0 + height / 2, 165, &k4x, 0, 100); cvui::printf(windowMat, width / 2 + width * 0, 90 + height * 0 + height / 2, "k4y"); cvui::trackbar(windowMat, width / 2 + width * 0, 100 + height * 0 + height / 2, 165, &k4y, 0, 100); if( k1xOld != k1x || k1yOld != k1y || k2xOld != k2x || k2yOld != k2y || k3xOld != k3x || k3yOld != k3y || k4xOld != k4x || k4yOld != k4y || typeOld != type || findTypeOld != findType) { typeOld = type; findTypeOld = findType; moveFlag = true; } std::vector srcPoints; std::vector dstPoints; srcPoints.push_back(cv::Point2f(0.0f, 0.0f)); srcPoints.push_back(cv::Point2f(srcMat.cols - 1, 0.0f)); srcPoints.push_back(cv::Point2f(srcMat.cols - 1, srcMat.rows - 1)); srcPoints.push_back(cv::Point2f(0.0f, srcMat.rows - 1)); dstPoints.push_back(cv::Point2f(srcMat.cols * k1x / 100.0f, srcMat.rows * k1y / 100.0f)); dstPoints.push_back(cv::Point2f(srcMat.cols * k2x / 100.0f, srcMat.rows * k2y / 100.0f)); dstPoints.push_back(cv::Point2f(srcMat.cols * k3x / 100.0f, srcMat.rows * k3y / 100.0f)); dstPoints.push_back(cv::Point2f(srcMat.cols * k4x / 100.0f, srcMat.rows * k4y / 100.0f)); cv::Mat M = cv::getPerspectiveTransform(srcPoints, dstPoints); cv::Mat srcMat2; cv::warpPerspective(srcMat3, srcMat2, M, cv::Size(srcMat.cols, srcMat.rows), cv::INTER_LINEAR, cv::BORDER_CONSTANT, cv::Scalar::all(0)); mat = windowMat(cv::Range(srcMat.rows * 0, srcMat.rows * 1), cv::Range(srcMat.cols * 1, srcMat.cols * 2)); cv::addWeighted(mat, 0.0f, srcMat2, 1.0f, 0.0f, mat); if(moveFlag) { moveFlag = false; //特征点检测 // _pSift->detect(srcMat, keyPoints1); _pFeature2D->detectAndCompute(srcMat, cv::Mat(), keyPoints1, descriptor1); //绘制特征点(关键点) cv::Mat resultShowMat; cv::drawKeypoints(srcMat, keyPoints1, resultShowMat, cv::Scalar(0, 0, 255), cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS); mat = windowMat(cv::Range(srcMat.rows * 1, srcMat.rows * 2), cv::Range(srcMat.cols * 0, srcMat.cols * 1)); cv::addWeighted(mat, 0.0f, resultShowMat, 1.0f, 0.0f, mat); //特征点检测 // _pSift->detect(srcMat2, keyPoints2); _pFeature2D->detectAndCompute(srcMat2, cv::Mat(), keyPoints2, descriptor2); //绘制特征点(关键点) cv::Mat resultShowMat2; cv::drawKeypoints(srcMat2, keyPoints2, resultShowMat2, cv::Scalar(0, 0, 255), cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS); mat = windowMat(cv::Range(srcMat.rows * 1, srcMat.rows * 2), cv::Range(srcMat.cols * 1, srcMat.cols * 2)); cv::addWeighted(mat, 0.0f, resultShowMat2, 1.0f, 0.0f, mat); // FlannBasedMatcher最近邻匹配 _pDescriptorMatcher->match(descriptor1, descriptor2, listDMatch); // drawMatch绘制出来,并排显示了,高度一样,宽度累加(因为两个宽度相同,所以是两倍了) cv::Mat matchesMat; cv::drawMatches(srcMat, keyPoints1, srcMat2, keyPoints2, listDMatch, matchesMat); mat = windowMat(cv::Range(srcMat.rows * 2, srcMat.rows * 3), cv::Range(srcMat.cols * 0, srcMat.cols * 2)); cv::addWeighted(mat, 0.0f, matchesMat, 1.0f, 0.0f, mat); // 定义两个局部变量 std::vector obj; std::vector scene; // 从匹配成功的匹配对中获取关键点 for(int index = 0; index < listDMatch.size(); index++) { obj.push_back(keyPoints1[listDMatch[index].queryIdx].pt); scene.push_back(keyPoints2[listDMatch[index].trainIdx].pt); } // 计算透视变换 cv::Mat H = cv::findHomography(obj, scene, CV_RANSAC); // 从待测图片中获取角点 obj_corners[0] = cv::Point2f(0,0); obj_corners[1] = cv::Point2f(srcMat.cols,0); obj_corners[2] = cv::Point2f(srcMat.cols, srcMat.rows); obj_corners[3] = cv::Point2f(0, srcMat.rows); // 进行透视变换 cv::perspectiveTransform(obj_corners, scene_corners, H); } // 绘制出角点之间的线 qDebug() << __FILE__ << __LINE__ << scene_corners[0].x << scene_corners[0].y << scene_corners[1].x << scene_corners[1].y; cv::line(windowMat, scene_corners[0] + cv::Point2f(srcMat.cols * 1, srcMat.rows * 0), scene_corners[1] + cv::Point2f(srcMat.cols * 1, srcMat.rows * 0), cv::Scalar(0, 0, 255), 2); cv::line(windowMat, scene_corners[1] + cv::Point2f(srcMat.cols * 1, srcMat.rows * 0), scene_corners[2] + cv::Point2f(srcMat.cols * 1, srcMat.rows * 0), cv::Scalar(0, 0, 255), 2); cv::line(windowMat, scene_corners[2] + cv::Point2f(srcMat.cols * 1, srcMat.rows * 0), scene_corners[3] + cv::Point2f(srcMat.cols * 1, srcMat.rows * 0), cv::Scalar(0, 0, 255), 2); cv::line(windowMat, scene_corners[3] + cv::Point2f(srcMat.cols * 1, srcMat.rows * 0), scene_corners[0] + cv::Point2f(srcMat.cols * 1, srcMat.rows * 0), cv::Scalar(0, 0, 255), 2); } cv::imshow(windowName, windowMat); // 更新 cvui::update(); // 显示 // esc键退出 if(cv::waitKey(25) == 27) { break; } } }


工程模板:对应版本号v1.63.0
  对应版本号v1.63.0


上一篇:《 OpenCV开发笔记(六十八):红胖子8分钟带你使用特征点Flann最邻近差值匹配识别(图文并茂+浅显易懂+程序源码) 》 下一篇:持续补充中…
多媒体
2020-08-06 20:24:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
Visual Paradigm 是包含设计共享、线框图和数据库设计新特性的企业项目设计工具。最新版本16.2已发布,新增智能电子表格编辑器多个功能。新版本,新功能,赶快下载体验吧! (Visual Paradigm现已加入在线订购,现在抢购立享优惠!)
点击下载Visual Paradigm最新试用版
表单构建器的改进

表单构建器中添加了许多新功能,以:
改善表格的外观 通过新字段和收集器支持促进更好的数据收集 支持表单逻辑以显示/隐藏字段
新表格栏位
支持一组丰富的表单字段。您可以轻松找到适合您数据收集需求的正确字段。一些新领域:
签名,数,滑杆,电子邮件,电话,价钱,网站,评分,颜色
有条件显示/隐藏表单字段
可以根据表单中其他字段的值将表单字段设置为可见,隐藏,可编辑和不可编辑。
用法示例:当用户为“您最喜欢的颜色是什么?”问题选择了“其他(请指定)”时,显示一个额外的文本字段供她输入自己喜欢的颜色。
多列复选框和单选按钮
通过多列显示单选按钮和复选框,使表单紧凑而整洁。表单设计者可以指定要显示的列印数量。
像场增强
对图像字段进行了两个较小但有用的增强,使其更加有用: 图像对齐:将图像内容与表单的左,中和右对齐。用法示例:将徽章或公司徽标放置在所需位置。 适合表格:通过扩展图像以适合表格的宽度来避免不必要的页边距。用法示例:展开横幅图像以使其适合表单的宽度。
改善字段对齐
进行了两项更改以改善表单的整体外观: 字段之间的垂直间距增加。 当文本跨越多行时,改进了复选框和单选按钮标签的对齐方式。
主题设定
修改了后台功能。现在,您可以在“主题”设置下进行以下设置: 表单界面的颜色或背景图像。 表单背景(即表单下方的区域)的颜色或背景图像。
改良的收藏家
创建收集器以通过表单提交收集特定人群的反馈。围绕收集器进行了两项增强,以提高其可用性。 收集器现在可以从多个渠道接收反馈-手动输入,URL,嵌入式表格。(以前,一个收集器仅支持一个表单提交通道) 将为每个表单创建一个默认收集器。
其他增强
这是对表单生成器的一些小改进: 使用新的手机预览功能,查看表格在手机上的外观。 通过顶部栏中的数字了解未读提交的数量。
分析画布的改进
Analysis Canvas是捕获和组织想法的便捷工具。通过将画布分成多个命名分区(称为“区域”),然后可以在这些分区中添加便笺以描述您的想法。新版本支持许多新功能,这些功能使分割区域和在区域中重新安排内容变得更加容易。
将画布分成两半
将一个区域垂直或水平完美地分成两个相等的两半。
合并区域
合并两个相邻区域的内容,而无需手动移动内容。
交换区域
一次单击即可立即交换两个相邻区域的内容。
端口增强
围绕“端口”形状进行了一些增强,以使使用端口的图看起来更好,并支持将嵌套端口用于SysML图类型。
端口对齐
使用对齐指南来帮助您精确放置端口。当您拖动一个端口与另一个端口等距排列时,将出现指南。
支持SysML嵌套端口
SysML允许使用嵌套端口-就像块如何嵌套其他块一样。现在,您可以在SysML图中创建嵌套端口。
20多个新的现代主题
提供了一组新主题,其中包括期待已久的深色主题。新主题改变了用户界面中的颜色和字体,使外观焕然一新。请看下面的一些屏幕截图,以了解一些新主题。
拖放图到逻辑视图文件夹
逻辑视图提供了一种用户定义的方式来组织和组织项目中的图。构造逻辑视图既快速又简单。您只需要从诸如Diagram Navigator或Model Explorer的树窗格中选择所需的图,然后将它们拖到逻辑视图文件夹中即可。
可视化连接器的关系
增强了模型可视化功能,以支持查找关系。您现在可以可视化来自给定关系连接器的关系(例如,通用,约束和通用连接器)。
ETL表列宽调整
ETL表是在选定范围内列出特定类型的元素的便捷方法,这些元素及其属性以表的行和列的形式列出。ETL表可以在图表中可视化为“ ETL表形状”。在新版本中,您可以调整ETL表形状中各列的大小以使其看起来更好。
显示实体列的默认值
在ER模型中,可以为实体列明确指定默认值。要在图表上显示默认值,请在图表背景上单击鼠标右键,然后选择“ 演示选项”>“实体列显示选项”>“显示列默认值”。
重用构造型中的全局定义的枚举
现在,可以将项目中定义的枚举用作项目所使用的任何构造型的标记类型。更新枚举的定义时,更改将立即自动应用于构造型。
使用快捷键自动调整形状大小
自动调整大小功能旨在自动将图中的形状调整为最佳大小。在新版本中,您可以为自动调整大小功能分配快捷键,以便您可以更有效地触发该功能。脚步: 从工具栏中选择窗口>应用程序选项。 从左侧列表中选择左侧的键。 在搜索框中输入“ 自动调整形状大小 ”。 更新密钥绑定,然后单击“ 确定”进行确认。
在团队更新中自动更新引用的项目
项目引用是通过项目间引用和项目中模型元素的可重用性来分离模型的便捷方法。为了确保工作的一致性,当您执行团队更新时,我们将帮助您相应地更新引用的项目。此更新将以递归方式工作。这意味着,如果您的项目引用的另一个项目“ X”引用的另一个项目“ Y”,则在更新时,X和Y都将被更新。
新图像输出格式:TIFF
您的模型可以作为图像文件输出并与他人共享。我们支持各种流行的图像格式,例如JPG,PNG,SVG,EMF和PDF。现在,您还可以将其导出为Tiff图像。
=====================================================
想要购买 Visual Paradigm 正版授权的朋友可以 咨询慧都官方客服 。
有关产品资讯的更多精彩内容,敬请关注下方的微信公众号,及时获取产品最新信息~▼▼▼
多媒体
2020-08-06 11:47:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
您是否对以下情况感到熟悉?您是一名软件开发人员,其任务是开发交互式(Gantt)图表,以帮助您的用户更好地理解和使用与时间相关的,面向资源的计划和计划数据。您已经筛选了一些甘特图控件的市场,最后决定测试一两个。您选择了一个下载试用版...现在,您想知道如何开始以及如何尽快实现有意义的甘特图。当然,时间是一个问题,您的老板正在努力争取更快的结果。
这篇博客文章是针对那些面临挑战并决定也测试甘特图控件VARCHART XGantt(.NET, http:// ASP.NET 或ActiveX版本)的软件开发人员的系列文章中的第一篇。当然,我们如何安装组件并开始使用XGantt开始。
要求
案例 http:// 1-.NET 或Gantt图表控件的 http:// ASP.NET 版VARCHART XGantt
要基于.NET开发应用程序,您需要一个开发环境,例如Microsoft Visual Studio 2010以上,至少要支持.NET Framework 2.0并与混合模式.NET组件兼容。XP Service Pack 3或更高版本的Windows仅32位或64位(x64)版本可以用作操作系统。
案例2-甘特图控件VARCHART XGantt的ActiveX版
要使用VARCHART ActiveX控件开发应用程序,您需要一个开发环境来支持ActiveX控件的集成,例如Visual C ++,Visual Basic,Visual Fox Pro,Delphi,Centura,Oracle Forms,Progress和Microsoft Windows操作系统
安装步骤
如果尚未完成,请下载甘特图控件 VARCHART XGantt
情况 http:// 1-.NET 或 http:// ASP.NET 版
默认情况下,控件及其关联文件将存储在文件夹下面
c:\Program Files\NETRONIC (32bit-Windows) or
c:\Program Files (x86)\NETRONIC
安装后,应将控件添加到各自开发环境的工具箱中。
参见下文,如何在Microsoft Visual Studio中继续;其他编程环境需要类似的过程: 在Visual Studio中,创建Windows应用程序或 http:// ASP.NET Web应用程序类型的新项目。选择哪种语言都没有关系,但是请注意该工具箱是可见的。如果不是,请单击“ 查看 工具箱”。 右键单击工具箱,打开上下文菜单,然后选择“选择项目”。 通过单击.NET Framework组件选项卡的浏览,可以从安装目录中选择程序集NETRONIC.XGantt.dll。通过确认后确定 对Visual Studio 2010用户而言重要的是:在将控件拖到窗体之前,必须在应用程序设置(C#)或高级编译器设置(VB)中将目标框架从.NET Framework Client Profile更改为.NET Framework 4。前者缺少System.Design.dll,后者是设计时属性页所必需的。如果不更改框架,则在尝试将控件拖到窗体上时将弹出以下错误消息:


情况2:ActiveX Gantt图表组件
在安装过程中,在Windows注册表中注册了VARCHART ActiveX组件的引用。您可以使用Windows系统文件regsvr32.exe自己运行注册:
c:\ windows \ system32 \ regsvr32“ c:\ program files \ varchart \ xgantt \ -vcgantt.ocx”
指定的路径当然取决于计算机的设置。
安装过程将记录到文件install.log中,以跟踪复制文件的位置。
相同的文件将用于卸载。您可以通过选择开始->设置->控制面板,然后选择添加/删除程序来启动卸载过程。
您可以使用以下命令自己删除注册条目
c:\ windows \ system32 \ regsvr32 -u“ c:\ program files \ varchart \ xgantt \ -vcgantt.ocx”
对于所有XGantt安装:无人参与安装
或者,您可以执行无人值守的VARCHART XGantt安装。为此,请输入:
start/wait (NameOfTheSetupFile).exe /L1033 /s /V"/qn ADDLOCAL=ALL"
此调用将启动安装运行,而无需用户交互且屏幕上不显示状态信息。请注意: 调用过程(例如DOS框)需要以管理员权限运行。否则,可能会出现一条UAC消息,要求用户输入。 语言参数:/ L1033:英语安装;/ L1031:以德语安装。 进度信息:/ qb:将显示进度信息;/ qn:不会显示进度信息;换句话说,您将不会在屏幕上看到任何东西。
如果安装由批处理文件运行,则应使用“开始/等待”,否则该批处理文件将与安装并行运行。
样品收集
与VARCHART XGantt控件一起,将安装我们的“样本收集”。安装.NET和ActiveX Edition时,这会自动发生 。
请注意:必须为 http:// ASP.NET 版单独安装示例集合
该样本集合为开发人员提供了一个很好的起点,因为它由一些甘特图应用程序模板(包括其源代码)组成。这意味着:您可以采用以下模板之一,打开代码,然后从那里开始。您可以通过单击XGantt_Startsymbol 桌面上的相应符号来访问样本集合。



要运行样品,请在列表中标记它,然后单击相应的按钮 Run_Sample_Icon 。要在属性页上查看XGantt的源代码或当前配置,请单击Open_Solution_Icon。但是,始终可以在“ 源代码”选项卡上的文本模式下查看源代码。
样本被分组。第一组应用程序模板包含用于各种用例的一些甘特图模板: 租车:计划和管理车辆租赁订单。 控制中心2:您可以将生产订单分配给分组的机器。订单存储在Microsoft Access数据库中。 生产计划:经典的生产计划模板。 预订计划者2.管理培训室和培训师进行研讨会,并以图形方式可视化冲突。 轮班计划:为员工分配不同的轮班。
“ 功能样本 ”小组展示了XGantt的一些展示功能。
的的例子实时更新样本说明如何鼠标交互的后果正在行动中立即显现,而不是只在结束它。
下一步
一旦确定了合适的甘特图模板开始,您将需要自己开始编码。我们通过提供非常全面的开发人员手册为您提供支持,我们强烈建议使用。
本文章转载自【慧都科技】欢迎任何形式的转载,但请务必注明出处、不得修改原文相关链接,尊重他人劳动成果
多媒体
2020-08-06 11:39:03
「深度学习福利」大神带你进阶工程师,立即查看>>>
在本教程中,我们将学习如何使用Firestore和官方的Highcharts Angular包装器可视化实时数据库更新。整个项目在此GitHub链接中。
我们将在本文中介绍以下几点: 设置Cloud Firestore项目。 使用Firestore设置Angular项目。 将Highcharts与Angular和Firestore集成。
设置Cloud Firestore项目
1.要做的第一件事是创建一个项目,然后执行以下步骤:登录到https://console.firebase.google.com
2.单击添加项目。
3.创建一个数据库。
4.选择“以测试模式启动”。
5.为数据库创建一个集合,使其具有多个文档来存储数据。
6.将文档添加到集合中。
7.将您的Angular应用名称添加到Firestore中。请确保选择“ Web”,因为我们目前正在使用Web 。
8.通过将Angular App Name注册到Firestore,我们将能够立即使用所有关键功能。
9.获取配置详细信息,然后firebaseConfig在angular应用程序中使用该对象。
使用Firestore设置Angular项目
要设置Angular项目,请按照此链接中的说明进行操作。
顺便说一句,创建完Angular项目后,请不要忘记运行以下命令,并说明执行该操作的原因:ng add @angular/fire 列出所有Firebase项目。由此,我们可以选择我们的项目进行进一步的配置和开发。 创建一个列出您的项目配置的Firebase.json配置文件。 创建一个.firebaserc文件来存储您的项目别名。 要更新文件。angular.json
设置完成后,就该将Firebase配置对象添加到环境中了(见下文): export const environment = { production: false, firebaseConfig: { apiKey: "AIzaSyBaGUX7iwBRthIKOE_uhyzs1aPEuKPlEAE", authDomain: "firestore-angular-highcharts.firebaseapp.com", databaseURL: "https://firestore-angular-highcharts.firebaseio.com", projectId: "firestore-angular-highcharts", storageBucket: "firestore-angular-highcharts.appspot.com", messagingSenderId: "669023523724", appId: "1:669023523724:web:43f04e6a2ce0a92c01fca9", measurementId: "G-ZD6C3CEC85" } };
在此HighchartService中与Cloud Firestore API进行交互的最后一步(请参见下文): import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; import { AngularFirestore, AngularFirestoreCollection } from "@angular/fire/firestore"; import { map } from "rxjs/operators"; @Injectable({ providedIn: 'root' }) export class HighchartService { private rateCollection: AngularFirestoreCollection < chartModal > ; rates$: Observable < chartModal[] > ; constructor(private readonly firestoreservice: AngularFirestore) { this.rateCollection = firestoreservice.collection < chartModal > ('ChartData'); // .snapshotChanges() returns a DocumentChangeAction[], which contains // a lot of information about "what happened" with each change. If you want to // get the data and the id use the map operator. this.rates$ = this.rateCollection.snapshotChanges().pipe( map(actions => actions.map(a => { const data = a.payload.doc.data() as chartModal; const id = a.payload.doc.id; return { id, ...data }; })) ); } } export interface chartModal { currency: string, rate: number }
将Highcharts与Angular和Firestore集成
为了使用Highcharts可视化我们项目上的数据更新,我们需要包含Highcharts Angular包装器。它允许我们访问highcharts库。是官方的角度包装器。要安装highcharts-angular和Highcharts库,只需运行以下指令:。要使用这些软件包,首先,必须导入它们。因此,在app.module.ts文件中,我们从highcharts-angular包中导入了模块。另外,我们导入并使用firebaseConfig(见下文):highchats-angular
npm install highcharts-angular highcharts
HighchartsChartModule
AngularFirestoreModuleinitializeApp import { BrowserModule } from "@angular/platform-browser"; import { NgModule } from "@angular/core"; import { HighchartsChartModule } from "highcharts-angular"; import { AppRoutingModule } from "./app-routing.module"; import { AppComponent } from "./app.component"; import { AngularFireModule } from "@angular/fire"; import { environment } from "src/environments/environment"; import { AngularFireAnalyticsModule } from "@angular/fire/analytics"; import { AngularFirestoreModule } from "@angular/fire/firestore"; @NgModule({ declarations: [AppComponent], imports: [ BrowserModule, AppRoutingModule, HighchartsChartModule, AngularFireAnalyticsModule, AngularFirestoreModule, AngularFireModule.initializeApp(environment.firebaseConfig), ], providers: [], bootstrap: [AppComponent], }) export class AppModule {}
让我们来看一下app.component.ts中的操作。
Highcharts从导入。从导入服务和模式。上,我们从文件存储数据库订阅的发光值,并推动新值数组,然后调用和初始化图表数据。import * as Highcharts from “highcharts”;
highchart.service
ngOnInit()chartdatagetChart() import { Component, OnInit } from "@angular/core"; import { HighchartService, chartModal } from "./highchart.service"; import * as Highcharts from "highcharts"; @Component({ selector: "app-root", templateUrl: "./app.component.html", styleUrls: ["./app.component.css"], }) export class AppComponent implements OnInit { title = "Firestore-Angular-Highcharts"; items$: chartModal[]; Highcharts: typeof Highcharts = Highcharts; chardata: any[] = []; chartOptions: any; constructor(private highchartservice: HighchartService) {} ngOnInit() { this.highchartservice.rates$.subscribe((assets) => { this.items$ = assets; if (this.items$) { this.items$.forEach((element) => { this.chardata.push(element.rate); }); this.getChart(); } }); } getChart() { this.chartOptions = { series: [{ data: this.chardata, }, ], chart: { type: "bar", }, title: { text: "barchart", }, }; } }
该项目的最后一步是转到app.component.html添加指令和一些属性绑定以初始化图表数据和选项:highcharts-chart
[Highcharts]="Highcharts"
[options]="chartOptions"
style="width: 100%; height: 400px; display: block;">

这是最终结果:
如下面的GIF所示,条形图从Firebase上托管的数据库中获取数据。当使用Firebase UI将新值添加到数据库时,图表的数据将更新并显示。
同样,在下面的示例中,我们正在将具有新值的折线图绘制到同一折线图上。
好吧,就是这样。现在,您知道如何使用Firestore更新实时数据库,以及如何使用Highcharts可视化更新。

本文章除转载自【慧都科技】。欢迎任何形式的转载,但请务必注明出处、不得修改原文相关链接,尊重他人劳动成果
多媒体
2020-08-04 14:51:00
「深度学习福利」大神带你进阶工程师,立即查看>>> 本文由芒果TV音视频技术专家谭嵩在LiveVideoStackCon2020线上峰会的演讲内容整理而成,结合芒果TV的工程化研发实践经验,对基于主观感兴趣区域的视频编码技术进行了详细解析。
文 / 谭嵩
整理 / LiveVideoStack

大家好,本次分享我将结合芒果TV音视频技术研发团队的实践,对主观感兴趣区域的视频编码技术进行详细解析。内容包括以上四个部分,其中会重点介绍我们在主观感兴趣区域编码工程化中遇到的一些问题与思考。
5G背景下,视频运营平台的新挑战

随着5G时代的到来,用户对于视频画质体验的要求越来越高,如4K、8K、高帧率、HDR等。广电总局对电视播出的4K超高清视频规范的要求是视频码率大于100兆。互联网视频应用对此还没有规范性的标准,但为了达到合格的播放体验要求,视频码率也将呈现出成倍增长的趋势,这就导致视频运营平台短期内在CDN成本、用户播放卡顿两方面会面临更加严峻的挑战。

面对上述挑战,我们最容易想到的方法就是通过压缩效率更高的下一代视频编码标准,如AV1、VVC等,有效降低成本。但就目前情况来看,这些标准其实还不具备大规模商用的生态环境,真正能够支持硬件解码的设备其实非常的少。另一方面,内容感知编码以及拐点参数在国内外各大视频平台已经得到普遍的应用。在这种情况下,我们需要的是挖掘更多的工具或手段,极限压榨编码的码率。
人眼主观感兴趣关注的特点
2.1 人眼的视觉过程

人眼的视觉过程大致为视网膜成像,大脑接收信息并做出初步的分析,产生视觉注意力聚集,并且最后对重点区域做关注。例如,人在开车过程中,前方的道路和车辆就是主观关注和感兴趣区域,周边的区域就是非关注区域。


2.2 主观感兴趣区域数据的提取

主观感兴趣区域数据集一般是通过眼动仪获取。眼动仪是记录眼动行为的一套设备,它包括一幅特殊的眼镜,一个记录模块,一套控制软件,一套数据分析软件。其中控制软件是非常重要的一个组成部分,因为它要对观看的对象和眼部行为数据做精密的同步,数据分析软件就是后期对数据做清洗分析。



使用眼动仪获取ROI大数据要注意这样几点问题:首先使用眼动仪收集数据的成本比较高,且更为重要的是人力的投入,因为需要有一定基数的参与量,数据才具有客观意义;其次是数据的可靠性,一个经验并不丰富的测试人员,很可能在测试过程中会给自己某些心理暗示,导致记录的行为并非自然状态下的真实行为,也就是说最后取得的数据很可能是无效的;最后,第三点就是数据清洗。
主观感兴趣视频编码的工程应用
3.1 设计背景

基于感兴趣区域的视频编码是利用人眼主观存在感兴趣区域和非感兴趣区域的特点,我们对主观感兴趣区域和非感兴趣区域的码率分配做一定的修正,将更多的码率分配到主观感兴趣区域,从而在主观上提升视频质量。这是感兴趣区域视频编码的理论设计,在工程上官方版本的x264、x265编码器并没有这样的设计,即对于一个x264编码器或者x265编码器来说,视频的每个画面中的每个像素都是同等重要。因此我们需要对编码器做一个工程化的开发,来实现不同像素的权重差异化,目标是达到同等码率提升主观质量或者同等体验降低码率,降低运营成本。
3.2 ROI编码器实现架构设计

我们在编码的两个阶段做了ROI的处理,分别是视频的前处理以及视频编码的过程。

前处理阶段主要是做ROI区域的增强,即图像层面的增强,同时对非ROI区域图像层面,进行降噪、磨皮等处理。做过视频编码的朋友应该清楚,高频其实对编码是不太友好的,低频则对编码更加友好。非ROI区域进行降噪、磨皮之后,更多的就变成了相对友好的低频区域。那么在同等的PSNR或者客观指标下,码率会有明显的降低。

视频编码阶段,我们主要是通过AQ调整QP值,实现码率分配的修正。也就是说对不同的主观感兴趣区域和非感兴趣区域做不同的QP调整, QP越小,质量越好,码率也会越高;QP越大,宏块得到的码率越小,画质越差。通过这样一个调整,实现ROI的权重调整。

3.3 ROI区域的定义

ROI编码并不是一个很新的概念,之前就有中心区域ROI、人脸ROI、字幕ROI等。

中心区域ROI是基于经验的判断,在正常视频的拍摄手法上通常会将最重要的内容放在画面最中间。人脸ROI其实也是基于这样的经验判断,一般来讲人物往往是视频内容中的主角。

字幕ROI是非常重要的一个方面,我们做过一个主观的调研:完全同样的视频画面,分别提供不同清晰度的字幕进行主观盲测。最终结果是:认为“字幕清晰”的视频是“画质更好”视频的,占比明显更高。

基于深度学习的ROI区域识别是当前更有效的手段。

3.4 深度学习的ROI的识别技术

基于深度学习的ROI的识别在技术上分为两类,一个是基于感兴趣区域的热度图检测,另外一个是显著对象分割。通过实践理解,在应用场景上我们认为这两者是有一定差异性的。我们认为感兴趣区域的热度图更适合长视频剧集类型的节目,显著对象分割则更适合真人自拍小视频等场景。

3.4.1 显著对象分割

显著对象分割为什么会更适合真人直播或者真人自拍小视频?
真人直播或者真人自拍小视频通常来说有这两个特点:第一是前景在画面中面积的占比一般较大;第二是前景和背景相关性比较弱,且前景的内容重要性具有绝对的地位。以此可以也必须对背景做比较多的图像细节丢失和编码压缩,来提升前景显著区域的画面质量。
3.4.2 感兴趣区域热度检测

剧集类长视频节目的特点是画面的整体相关性更强、画面存在多点位ROI区域,因此不能类似于真人小视频一样过度的衰减非ROI区域来补偿ROI区域,需要用较多的非ROI区域来补偿较小的ROI区域,这样非ROI区域的衰减在主观上也不明显。感兴趣热度图符合这个需求特点。

3.5 第一版ROI编码工程化实现过程

这是我们在ROI编码工程化过程中实现的第一个版本。首先实现的是对一个ROI元素的处理。我们从原图分析得到ROI的热度图,然后对热度图做二值化处理并且膨胀后腐蚀到可控的阈值范围内的ROI面积占比。最后我们做了一个宏块级的数据结构化,因为最终要映射到编码宏块上去,这里有一个阈值参数,是我们要控制好ROI区域和非ROI区域的面积占比。面积太小,ROI增强的效果不太明显,但是面积过大,由于ROI区域画质提升是以牺牲非ROI区域的细节或质量实现的,这会导致非ROI区域的质量下降非常明显、如果进一步下降达到主观产生厌恶感的阈值,会吸引注意力到这些糟糕的、突兀的画面上,ROI编码的目的就没有达到。

在工程化实现上,我们对x264、265编码器做了一个ROI的编码实现。编码器读取当前帧的ROI分析结果元数据,在视频编码器中通过AQ实现对QP的修正,ROI区域的QP会降低,编码质量会提升,同时码率也会消耗的更多。整帧码率控制依然由x264和x265原生的码率控制算法负责。
3.6 二值ROI的失败case

做完第一个版本之后,我们发现,由于这是一个二值ROI,ROI区域和非ROI区域是0和1的区别,导致ROI和非ROI区域的非平滑过渡在衔接处会有一个明显的画质对比跳变(QP值跳变)。第二个问题是我们对非ROI区域的QP调整是交回给x264和x265编码器原生的码率控制,由于我们对ROI区域的QP调整破坏了原生码率控制算法的数学关系,所以部分场景出现非ROI区域的渣感现象。这是两个失败的case。

3.7 灰阶ROI迭代版本

因为上述两个失败的case,我们进一步提出了一个迭代版本灰阶ROI。灰阶ROI实现的是ROI和非ROI区域之间一个ROI强度的灰度过渡,所以QP会有一个平滑的调整,避免两个区域边缘质量差距过大,形成一个突兀的画质对比。

3.7.1 灰阶ROI的计算过程

灰阶ROI码率控制的计算过程:我们的计算目标是得到一个QP的修正值QP’,Sij是我们对应宏块的一个灰阶的ROI值,也就是我们刚才这里ROI的一个灰度的数值,范围是0到255。p%其实是ROI区域的面积占比,b%是ROI区域的码率的占比,也就是说我们在p%面积占比的ROI区域上给予了b%的码率。目前我们采用的数值p是20%,b是30%,会对20%的面积修正给予30%的码率。同时对非ROI区域的QP做了反向的主动修正。

通过这个迭代版本实现了ROI和非ROI区域画质的平滑过渡,同时非ROI区域渣感明显降低。

3.8 效果评估

这段视频是最终送给编码器指导ROI编码的ROI灰阶图数据的真实示例。为了更加清晰和直观,这个示例中非ROI区域我们做了一个灰度视频显示,ROI区域还是原来的彩色显示。

上图是ROI编码和非ROI编码的实际效果对比图。其中ROI编码的版本降低10%码率,在这个条件下,主观质量依然高于非ROI编码版本。
问题和挑战

我们在最后继续向行业抛出这个不算很新的话题 — ROI编码视频质量的机器评价,目前我们主要还是通过人工的方式进行一些主观评价。在批量化自动生产条件下,如何做好ROI编码视频质量的机器评价,是非常具有挑战和价值的一项工作。
多媒体
2020-08-04 14:49:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
WebRTC中文社区 是一个为大家解决在使用WebRTC当中遇到问题所建立的社区,欢迎更多学习和使用WebRTC的人加入进来,一起建设。
视频录制
在之前的 文章 里我们提到过视频录制的两种方式:客户端录制和服务端录制,以及他们的优缺点
WebRTC-Client-Record
服务端录制要根据你所使用的服务端,来决定视频录制的解决方案。 所以不是本文讨论的重点,本文主要讨论使用WebRTC的客户端解决方案
WebRTC-Client-Record 是基于WebRTC所写的客户端视频录制解决方案,他已经完美得集成到Jitsi上,可以手动选择录制的分辨率,默认支持回音消除,他不消耗用户的带宽,只消耗一点用户的CPU和内存。
功能如下 支持手动选择录制的程序,如果有多个屏幕,支持手动选择,可以选择浏览器标签页,和应用程序进行录制 支持手动选择录制的分辨率,建议录制比例4:3,如果选择录制的分辨率不支持,WebRTC-Client-Record会手动选择一个最接近的分辨率 默认开启回音消除 手动选择录制的帧率 支持暂停,继续,下载等功能 兼容各种浏览器,IE没有试过 支持一键部署到Jitsi-Meet 后续增加中
WebRTC-Client-Record支持一键部署到Jitsi-meet,但是不仅仅支持Jitsi-meet,理论上他是支持所有的流媒体服务端的。
github地址 如果你有任何问题,欢迎进群留言
多媒体
2020-08-03 19:19:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
反应样本
你可以在下面的路径中找到反应样本,
/PlatformSamples/React/Samples.
注意:您还可以在以下路径中找到可选的React Visual Studio项目或解决方案,
/PlatformSamples/React/GanttInReact.csproj.
如何运行反应样品
您可以使用NPM(cmd)、Visual Studio 2017版本或Visual Studio代码运行React甘特样例。对于所有这3种方法,您需要首先使用npm来安装相关的'node_modules',如下面的步骤所示。(‘node_modules’包依赖关系已经在‘package’中指定了。json文件。)
另外,确保将您的npm更新为最新版本(使用“npm版本”来确定您的版本)。要更新到最新,使用cmd行:"npm install npm @latest -g"。
使用NPM运行React示例的步骤:
1. 使用“作为管理员运行”选项在命令提示符中浏览到项目目录(..\PlatformSamples\React)。
2. 输入“npm install”。这将在node_modules文件夹中安装依赖的模块。
3.运行“npm start”。
4. 示例自动在默认浏览器中运行。否则,复制端口并粘贴到任何浏览器中。
使用Visual Studio 2017运行React示例的步骤:
1. 使用“npm Install webpack -g”和“npm Install webpack-cli -g”全局安装webpack和webpack-cli。
2. 安装'node_modules'包(上面NPM步骤中的步骤1和步骤2)
3.然后打开'.csproj'与Visual Studio 2017的项目目录,并照常运行它。
使用Visual Studio代码运行React示例的步骤:
1. 打开Visual Studio代码IDE
2. 使用File—>打开文件夹导入React gantt项目
3.使用视图——>集成终端打开命令窗口,并选择Terminal选项卡
4. 然后按照上面NPM步骤中的步骤2到步骤4执行。
反应甘特结构和安装
React甘特包可以在/PlatformSamples/React中找到
创建您的项目目录,并根据需要构造它。我们的是这样的,
React甘特文件可以列出如下。 package.json。 index . html。 webpack.config.js。 Src文件夹。 样本文件夹。 数据源文件夹。
package.json
“package.json"的文件指定了项目名称、描述和反应、Webpack和Babel包,这些包可以在npm安装期间安装。
配置Webpack
在这里,我们通过在webpack.config.js文件中添加所需的配置来为React甘特设置webpack。 const path = require('path'); const webpack = require('webpack'); module.exports = { entry: { // Here, Webpack to start bundling our app at following paths. GanttControlSkeleton: './Samples/GanttControlSkeleton/GanttControlSkeleton', FlexyGanttSkeleton: './Samples/FlexyGanttSkeleton/FlexyGanttSkeleton.jsx' }, output: { // Output our app to the dist/ directory with Sample name. (eg: dist/SampleBrowser.js) path: path.resolve(__dirname, 'dist'), filename: '[name].js', publicPath: 'dist/' }, devServer: { inline: true, port: 3000 }, // devtool enhances the debugging process devtool: 'inline-source-map', resolve: { // add alias for application code directory alias: { RQSrc: path.resolve(__dirname, 'Src'), Samples: path.resolve(__dirname, 'Samples'), }, }, //Webpack uses loaders to translate the file before bundling them. module: { rules: [ { test: /.jsx?$/, loader: 'babel-loader', exclude: /node_modules/, //Configuring babel query: { presets: ['es2015', 'react'] } }, { test: /\.css$/, use: [ "style-loader", "css-loader" ] }, { test: /\.(png|jp(e*)g|svg)$/, use: [{ loader: 'url-loader', options: { limit: 8000, name: 'images/[hash]-[name].[ext]' } }] }, ], }, }; webpack.config.js
如欲进一步了解webpack,请参阅以下连结webpack。
“Src”文件夹
“Src”文件夹包含需要运行甘特的源文件,如ResourceStrings、脚本、样式,它还包括“React_components”文件夹,其中包含“GanttControl”。jsx”和“FlexyGantt。jsx”,这些组件被导出其功能,并且可以通过导入组件在任何地方使用。
“样品”文件夹
它包含示例来说明我们的特性和用法。
多媒体
2020-08-03 14:38:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
dhtmlxGantt是用于跨浏览器和跨平台应用程序的功能齐全的Gantt图表。可满足项目管理应用程序的所有需求,是最完善的甘特图图表库。它允许你创建动态甘特图,并以一个方便的图形化方式可视化项目进度。有了dhtmlxGantt,你可以显示活动之间的依赖关系,显示具有完成百分比阴影的当前任务状态以及组织活动到树结构。
更新
将duration_unit配置设置为“小时” 时,工作时间计算的性能提高
将duration_unit配置设置为“分钟” 时,工作时间计算的性能改进
Gantt.getGanttInstance添加了在的配置对象中指定工作日历的功能
修复
修复gantt类型定义中的语法错误
修复在拖放过程中在触摸设备上触发的脚本错误
修复使用数字值定义链接类型时自动计划扩展的不正确工作
减少了资源直方图的冗余重绘次数
任务分组扩展的性能改进
修复了在触摸设备上滚动资源时间轴的功能
修复使用“隐藏空”按钮时资源控件的错误工作
甘特的返回类型。类型定义中的承诺是固定的
取消了在自动调整大小模式下对甘特图大小的10000px限制,该限制应允许打印较大的图表
当用户在文档正文的任何部分而不是仅在甘特图容器上释放鼠标按钮时,拖放操作现在完成
葡萄牙语语言环境已更新
类型定义中gantt.columnIndexByDate的返回类型是固定的
修复在拖放期间销毁 Gantt实例时触发的脚本错误
修复了将duration_unit设置为“分钟”并且上次工作时间间隔在23:00之后结束时,对end_date / 持续时间的错误计算的问题
修复了导致用户修改任何任务时扩展分组扩展的组的问题
修复了如果将对象传递给第一个参数,则导致dataProcessor.setTransactionMode的第二个参数被忽略的问题
修复了重新绘制甘特图后导致活动的内联编辑器消失的问题
修复了带有static_background扩展名的问题,该问题导致鼠标在空单元格上的单击被解释为对任务元素的单击
甘特图现在可以在拖放过程中动态重绘拆分任务之间的链接
修复这是从抛出脚本错误gantt.addTask在Node.js的包
修复这是从抛出脚本错误gantt.destructor在Node.js的包
修复setWorkTime方法中的回归,该回归在设置特定日期的工作时间时导致脚本错误
修复在SalesForce Lightning Web组件内使用Gantt时键盘导航扩展的不正确工作
修复gantt.config.csp设置为true 时日期格式化程序的不正确工作
修复click_drag和drag_timeline扩展名中的回归,当创建甘特图的多个实例时,这些回归会导致错误的工作
从dataProcessor路由器功能返回错误状态后,修复任务行元素的错误的CSS类
修复Shadow DOM 中内联编辑器的错误工作
大大提高了计算任务在工作时间内的持续时间的性能
修复了Tooltip和Undo扩展中的回归,该回归在创建多个Gantt实例时导致不正确的工作
修复了网格列重新排序的问题,该问题导致在拖放过程中将鼠标指针移到网格边缘时时间轴滚动
将列拖放到网格边框的右侧后,修复列的错误位置
现在,dataProcessor自定义路由器应该可以在被拒绝的承诺下正常工作
修复智能渲染中的回归,该回归使某些链接不可见
现在,拆分任务不仅显示第一级子任务,还显示所有嵌套的子任务
解决了拆分任务和智能渲染时拆分任务具有“任务”类型时发生的问题
解决拆分任务的问题,该问题导致甘特图不计算嵌套在拆分任务中的“项目”任务的持续时间
在RTL模式下打开内联编辑器后,修复占位符的错误位置
本文章转载自慧都网。欢迎任何形式的转载,但请务必注明出处、不得修改原文相关链接,尊重他人劳动成果
多媒体
2020-08-03 14:27:04
「深度学习福利」大神带你进阶工程师,立即查看>>>
如何使用Web的JavaScript HTML5创建网络图网络图是一种特殊的,非常有趣的数据可视化形式。与条形图 或 饼图等更传统的图表类型不同,网络图的作用远不只是可视化数字数据。使用这些图表,可以将每个对象表示为一个点(称为节点),并将对象之间的连接表示为一条线(称为链接或边)。在这里,我们不专注于以通常使用的相同精度来表示对象。取而代之的是,我们试图发现网络或网络中各个部分的关系,而不必担心单个节点。
现在,我将指导您 如何使用JavaScript(HTML5)为Web开发交互式网络图。受 本·沙林斯(Ben Sullins)创建的有趣的可视化效果的启发 ,我决定采用有关过去十年最大的电视连续剧《权力的游戏》的数据。这是一个史诗般的幻想故事,围绕着各种派系式房屋的争吵。因此,在本教程中,我将通过展示谁攻击了谁来形象化《权力的游戏》世界中的关系。跟着我,这将是一次很酷的冒险!
制作网络图
从头开始构建JS网络图

基本上,要构建基于JS的网络图,我们需要遵循与任何JavaScript图表相同的四个步骤: 创建一个HTML页面。 添加必要的脚本。 加载将可视化的数据。 绘制图表。
1.创建一个HTML页面

建立网络图的第一步是设置HTML页面。这涉及为图表创建基本的HTML模板以及添加必要的CSS规则。
在这里,我们还为HTML页面添加标题,并创建一个div来包含图表。 JavaScript Network Graph

我设置了CSS,以使容器div填充整个页面。当然,您可能要根据用例进行更改。

2.添加必要的脚本
第二步是添加所有必需的脚本。我们将使用AnyChart JS Charts ,它是一个非常易于使用且功能强大的JavaScript图表库。我个人喜欢AnyChart,因为它对初学者和专业人士都非常有用。它使您可以快速建立数据可视化的原型,并确实有助于加快整个开发过程。
为了充分利用AnyChart库,我们需要添加其专用模块。在本教程中,我们将使用其中的三个。需要Core和Graph模块来绘制我们的网络图,而Data Adapter模块将帮助我们导入《权力的游戏》 JSON数据(稍后会介绍更多)。

我们将这些脚本添加到代码的开头。

3.加载我们将要可视化的数据
如前所述,我们将使用的数据基于《权力的游戏》。我找到了一个很棒的数据集,《五王之战》。数据最初是从源火维基和冰,致力于权力的游戏维基。然后,数据科学家Wikimedia Foundation的机器学习总监Chris Albon对其进行了刮擦和清理。为了这个JS网络图教程的目的,我自己进一步处理了它。你可以找到的结果 在这里,在JSON。
问题是,AnyChart希望您的网络图形数据采用特定格式。它想要一个具有节点数组和边缘数组的JSON对象。每个节点都需要一个“ id”。边缘需要一个“从”和“到”,分别是每个连接的源和目标。
像这样:
nodes: [{ "id": "Lannister" }] edges: [{ "from": "Lannister", "to": "Tully" }]
然后,我们需要导入数据。为JavaScript数据可视化加载数据有时会很麻烦。但是使用AnyChart, 处理数据 确实非常简单。利用正确的功能,您可以轻松导入CSV,JSON,XML甚至Google电子表格!

在这里,我们的数据为JSON格式,因此我们需要以下功能: anychart.data.loadJsonFile('https://static.anychart.com/git-storage/word-press/data/network-graph-tutorial/data.json', function (data) { // The chart code goes here. })
因为我们希望仅在成功加载数据之后才绘制图表,所以我们将放置代码以在该函数中绘制图表。

4.绘制图表
第四步也是最后一步是绘制图表。在这里,我们命令根据数据创建一个图表,设置图表标题,然后使用这些非常简单的函数将图形可视化: // create a chart from the loaded data var chart = anychart.graph(data); // set the title chart.title("Network Graph showing the battles in Game of Thrones"); // draw the chart chart.container("container").draw();
结果如下:
请参见此网络图示例,以及在AnyChart Playground或CodePen上的完整HTML / CSS / JS代码。

也许不如我们基于D3.js的灵感在美学上给人留下深刻的印象,但我们稍后会谈到。花一些时间将鼠标悬停在节点上,或者将它们拖动一点。多么惊人?通过这四个简单步骤和几行JavaScript代码,我们创建了这个功能强大且交互式的网络图。
完整的代码如下,检查出来:

自定义网络图外观

正如我们目前的可视化效果一样,我们可以做得更好。好了很多。所有不错的JS图表库都允许您自定义使用其API构建的图表。作为数据可视化开发人员,利用这些自定义更好地讲述您的数据故事是一项关键技能。这些定制中的一些是美观的,例如更改元素的大小或颜色,而某些功能是在更改工具提示时。
下面,我将展示一些可用于网络图的自定义技术。
更改迭代步骤
网络图是使用一种算法绘制的,该算法反复尝试改善其布局。对于每次迭代,算法都会根据误差度量进一步优化步骤。这表明迭代次数越多越好。但是,有时算法对优化布局的想法与您(以及您的用户)对优化布局的想法可能会大不相同。通过更改迭代次数,我们可以获得适合我们需求的不同布局。
在下面添加代码,您将看到如果仅使用10次迭代(默认值为500),将会发生什么情况。
// set the iteration step chart.layout().iterationCount(10);
好吧,这只是一个快速演示。让我们跳过此更改,看看如何对我们的JS网络图可视化做一些有趣的事情。

定制节点(基本):
我们可以自定义每个节点的大小,填充和笔触,以及为每个状态设置不同的规则。状态表示默认节点,悬停的节点或选定的节点。可以使用与设置CSS颜色相同的方式来设置JavaScript网络图中的颜色,这里我们将使用十六进制代码。 显示悬停/选定/正常更改: // set the size of nodes nodes.normal().height(30); nodes.hovered().height(45); nodes.selected().height(45); // set the fill of nodes nodes.normal().fill("#ffa000"); nodes.hovered().fill("white"); nodes.selected().fill("#ffa000"); // set the stroke of nodes nodes.normal().stroke(null); nodes.hovered().stroke("#333333", 3);
nodes.selected().stroke("#333333", 3);
请参见此网络图示例,以及在AnyChart Playground或CodePen上的完整HTML / CSS / JS代码。

自定义节点(高级):
就个人而言,对于网络图,我最喜欢的美学更改是将节点图标替换为图像。在这里,我们可以用图像替换韦斯特罗斯(我们的节点)的大房子。通过将图像的文件路径添加到我们JSON中每个节点的对象上,可以轻松完成此操作。例如: {id: "Example", fill: { src: "example_url" } },
我已经进行了更改,可以在这里找到更新的文件。通过将此JSON与包含的图像一起使用,我们得到以下图形:
请参见此网络图示例,以及在AnyChart Playground或CodePen上的完整HTML / CSS / JS代码。

节点标签
并非所有人都知道每所房子的标语,不得不将鼠标悬停在每个节点上以查看它属于哪个节点可能会非常痛苦。为了解决这个问题,我们可以标记每个节点。使用以下代码即可轻松完成此操作: // enable the labels of nodes chart.nodes().labels().enabled(true); // configure the labels of nodes chart.nodes().labels().format("{%id}"); chart.nodes().labels().fontSize(12); chart.nodes().labels().fontWeight(600);
结果是:
请参见此网络图示例,以及在AnyChart Playground或CodePen上的完整HTML / CSS / JS代码。

为了方便起见,这是此交互式Java网络图(此教程的最终版本)的完整代码,该可视化图像将有关权力游戏之战的数据可视化:

结论

就像这样,我们有了很棒的可视化工具,可以解开该网络中的复杂关系。在本教程中,我展示了启动和运行JS网络图表的过程是多么快速和容易,以及通过多一点的努力,我们就能通过一些选择调整真正使图表变得生动起来。
如果本教程激发了您对基于演出的图表的兴趣,请查看此精彩绝伦的32权力游戏数据可视化列表。
多媒体
2020-07-31 17:13:00
「深度学习福利」大神带你进阶工程师,立即查看>>> 本文由百度智能云音视频SDK产品技术负责人李明路在LiveVideoStack线上分享的演讲内容整理而成,内容从音视频数据角度出发,梳理了音视频SDK的发展和技术演进。详细分析数据在常见音视频模块上遇到的问题与挑战,并提出相应的解决思路和技术实践。
文 / 李明路
整理 / LiveVideoStack
视频回放:https://www.livevideostack.cn/video/online-lml/
本次分享的主题是移动音视频SDK开发工程实践,内容主要分为以下五个部分: 音视频SDK的技术演进 数据采集管线的设计与实现 特效模块数据中间件设计和实现 连麦模块数据中间件设计和实现 渲染模块数据中间件设计和实现
音视频SDK的技术演进
1.1 数字化进程

多媒体技术是一项传统技术,但同时也是在不断发展与进步的,我们可以形象的用细胞分裂来表示,即多媒体技术内部也在不断发生分裂,而音视频SDK则是其中的一个分系。
以下大致列出了音视频技术演进的几个重要进展。首先,在互联网化前期多媒体技术就已经开始出现广泛的应用,得益于通讯技术的发展,我们可以借助于广播、有线电视、卫星等等,将数字信号传输给用户。
解码器其实在整个多媒体技术的发展当中也起到非常重要的作用,只不过当时解码器的能力与现在相比还是有些不同,但同样可以实现解码和容器的分离。
此后,随着互联网的出现,我们可以实现基于一些IP网络协议、光纤的广泛使用,通过WiFi、蜂窝网络,传输数据到设备终端(此时终端更多的还是指PC端)。因此产生了一些在线类的音视频服务,例如点播、在线语音等典型的音视频场景。在这些场景中,SDK更多的还是以服务端为主。
2010-2015年期间,随着手机硬件的发展,终端的算力不断提升,编解码芯片也得以快速发展,再加上消费者使用习惯的变化,出现了更多碎片化的场景和产品,像直播、短视频等。此时,随着手机等移动设备的普及,移动端SDK也慢慢进入到消费级领域,逐渐发展成独立技术栈。
近年来, 随着5G、AI人工智能的发展,VR/AR、语音交互等技术也在发生着新的变化,应用场景变得更加广泛,音视频SDK已经不仅仅局限于移动端,未来会出现在各种设备屏幕、产品形态当中。
1.2 移动端音视频框架

移动端的音视频框架与其它移动端框架相比有很大不同。音视频框架首先要立足移动端提供的系统能力,包括系统框架和硬件能力。如图,系统框架层有iOS/Android的一些多媒体框架、硬件的编解码、硬件的处理能力如CPU、GPU、NPU的处理模块,以及一些开源图像库。
系统框架层之上是第三方框架,如FFmpeg、OpenSSL(加解密);在点播场景用的比较多的Ijkplayer/Exoplayer;用于底层通讯的技术:双向的,低延时的WebRTC技术,用于单向点播的RTMP,目前比较火的SRT低延时方案;除此之外,还会有一些图像处理方面的框架如GPUImage等。
在此之上,就是一些跟场景相结合比较多的模块,这里大致列出了部分核心模块。
数据从多媒体采集模块出来,会经过一路或多路的混音混流(与实际场景相结合),然后过渡到多媒体编辑模块:当下短视频的一些能力都是通过多媒体编辑这个处理单元实现,再到后面的多媒体后处理模块:例如AR特效,以及一些比较好玩的互动能力等;
内容生产完成后,我们会根据点播、直播或者短视频等场景的不同,采用不同协议进行分发。再下面就是消费侧,这里可以直接读取数据,也可以间接读取数据作为缓存加快二次读取数据的载入。获取数据后会根据不同的封装格式,如FLV、TS或者是MP4等对数据进行解封装,再进行解码和渲染操作。
相比与其它移动端框架音视频框架最特别的地方就是管线部分,因为音视频SDK的产品与其它产品不太一样,首先需要的是实时处理,数据流是不断在各个模块之间穿梭的,如何保证各个模块间的高效传输,这里提出了一个管线的概念。
1.3 唯快不破

简单介绍了音视频SDK的框架,接下来介绍下目前框架遇到一些问题和挑战。目前很多业务场景,其实都在追求更多的新玩法。这里可能会有一些追求更大的分辨率,还有一些更高的刷新率,甚至是更极致的体验。但是我们知道音视频SDK很大程度上会受制于平台的能力,由于平台具有更多的差异性,所以导致音视频SDK在发展过程当中,其实遇到很多的问题。另外还涉及到模块间的数据交互,因此性能对于移动端来说是一个最大的瓶颈。
其实在有些场景像点播或短视频,720p的分辨率已经不能满足场景的使用,甚至在一些运动场景,30fps的刷新率也已经没办法满足场景的开发,当然这些其实更多的是业务挑战。除此之外,刚才也说过整个多媒体技术的发展,编码器一直起到了举足轻重的作用,所以底层技术也在追求更高效的编码效率。
对于这些问题,其实大家都能达成一个共识,就是说速度是音视频SDK框架要解决的一个问题。但抛开现象看本质,速度其实还是来自于数据的传输,它是一个根本前提条件,因此如何让数据更高效的传递和更高效的处理,才是移动端SDK要解决的最根本问题。
数据采集管线的设计和实现

这里介绍一下目前一些开源产品当中数据链的设计和方案,以两个产品为例,一个是GPUImage。相信做过移动端SDK特效的同学应该都是耳熟能详了,因为它确实是比较好用,可以提供大量的Shader。抛开Shader的不说,我们看一下它在数据链条的设计方式。首先GPUImage提供了一个传输数据的类Output和一个实现接收数据的Input,然后通过生产模式,生产模块像相机采集模块,以及本地相册模块,其实都是实现了Output的类,生产模块生产完毕后会交由Output进行渲染,这里它的强大之处在于Output可以与可编程的Shader进行强关联,很大程度的驱动了OpenGL的运行效率。然后将渲染后RGB的数据转换成FrameBuffer这个实体的对象,整个链条都是通过FrameBuffer进行接收,然后通过链条上的组件去实现了Input后,就能拿到Output传出的FrameBuffer,以此类推将FrameBuffer进一步的传递到下个链条当中,整个链条其实是非常清晰和简单。
我们再来看一下右边的FFmpeg,除了提供很多的数据链条的处理机制、数据的分包,它还可以在不进行解码的情况下进行转包,也可以把数据进行解码,做一些后处理等等,整个链条其实也是比较清晰的。

介绍了两种多媒体框架,接下来看一下我们团队是怎么去考虑这些问题。实际开发中,音视频SDK框架中管线其实是不可控的,我们来看一下这两种方案的一些优势和问题。
首先GPUImage的优势是显而易见的,就是协议相对简单清晰,另外通过可编程Shader驱动了OpenGL,这样它的运行效率变的非常快,通过大量的Shader可以让开发同学去进一步学习这门语言,是一个优秀的图像处理 开源框架。但同样存在一些问题,首先我们看到它提供的这些Shader其实大多数都是图像处理,并且基本上都是同步处理的。所以可能导致在使用GPUImage跟一些框架进行结合的时候,当你的某个模块涉及到耗时处理,这个时候有可能会出现某个对象被其它释放掉或怎么样,会造成整个线程的不安全。
FFmpeg的优势特别多,简单说一下它的函数指针,其实也是FFmpeg的一个最佳实践,它通过指针的方式,把数据进行一层层的拆分跟传递。但是它也同样存在一些问题,比如说FFmpeg是面向过程的,它的链路设计对于不熟悉FFmpeg的同学来说,可能会觉得链路非常复杂,使用起来可能并不是那么的得心应手。
所以我们经过多轮讨论跟思考,就希望得到一个数据管线,它首先像GPUImage,协议比较简单,用起来比较容易上手,对开发者来说是友好的;另外需要它是可控的,因为随着SDK的模块越来越多,我们发现在很多场景下,会出现不可控的问题;除此之外我们还希望它是支持同步或者是异步的,调度是安全的,链路配置是简单可依赖的。

我们也总结了自己的一些方法论跟实践:专注链式,这主要还是考虑到音视频框架,链式是它的一个核心思想,所以我们遵从这种法则自研数据管线。
首先是制定数据协议,主要解决的是最基本的模块间数据的高效、稳定的传递。这里可以看到左图,它的方案跟刚才介绍的GPUImage有些相似,但是也有些不同。我们也提供了类似的AVOutput的模块和AVInput的数据传输接收的协议。但是我们并没有跟GPUImage的OpenGL进行绑定,只是单纯的去记录和管理这条链条上的各个的组件,我们叫target。然后通过Dispatcher的机制,分发从生产端上报的视频帧,通过视频帧不断的传递到各个链路的target当中,而每个target实现AVInput其中的协议方法。例如frame跟type两个函数,frame就是从raiseFrame Output传递下来的数据;type主要是做一些音视频的音频跟视频的区分。
除此之外我们还支持一些二进制场景的分发,主要是为了配合像直播这种对数据有分发要求的场景做了一些协议的升级。在链条的最末端,可以看到我们也实现了AVControl,跟之前不太一样是我们制定了一个控制协议,这个控制协议主要是为了控制数据的流入和流出。为什么要做这个事情呢?我们知道音视频SDK最核心的就是数据在不断的传递,如果说某个模块出现异常,数据没有一种机制能保护它,可能会导致整个SDK的运行出现不稳定的情况。比如在直播场景分发的时候,我们发现网络有抖动,此时我们可以调整发送的速率、速度等。
这里简单的画了一下我们的数据流向,一个是推的方式,就是直接从相机这种模块采集数据,直接着向后面的某个模块去传递。一个是拉的方式,主要是指读取本地文件或者说是间接的获取数据,比如从网络读取数据首先需要将数据读下来,然后再传递到各个模块。这里其实相对于之前说了GPU在异步线程的一些处理,我们也做了一些兼容和保护,就是防止异步处理时对象被释放的情况发生。所以说我们基本上也是沿用了GPUImage协议简单的这种思想,然后在此基础上又增加了一些像控制协议的实现机制,让整个链路变得可控。

除此之外,我们也发现在日常过程当中,只是简单的设计数据管线,其实并不能很好的对业务场景开发提供帮助,原因什么呢?
其实我们只是解决了各个模块数据之间的传递问题,但是并不能对最终产品的场景开发落地解决所有的问题,因为场景跟模块是有很大差异的。我们所了解的像点播、直播,甚至是特效等,它其实都是通用性的能力。但是实际场景就涉及到各个模块之间的组合,所以数据的传递并不能说传递一个数据,就能把一个场景串联起来。
这里也举了几个日常过程中典型的例子,比如对于点播的画质优化,会发现类型转换不是那么通畅和简单。对于连麦场景,如何让我们的SDK或者产品用起来更简单,像人脸特效,比如说涉及到能力的多样化,如何做到兼容性等,这些都不只是靠数据链路或模块搭载就能解决的。因此我们提到了一个中间件的概念,就是把数据进行桥接实现资源的共享,在各个模块或业务输出或使用的时候,就能提高整体数据采集或处理的应用性。所以说应用性也是我们数据采集管线的另一个考量指标。
特效模块数据中间件设计和实现
接下来我们来看一下,在实际过程当中遇到的一些问题跟解决方案。

特效模块通常是典型的PaaS结构,它上面存在多种模型,而且模型之间是可以进行插拔;它还有另外一个特点就是耗资源。那么如何在音视频SDK中将这个模块更好的运用起来,去对外提供能力呢?我们这里以人脸特效的高级美颜接口为例,高级美颜中涉及到的特征点非常多,像大眼、瘦脸、下巴等,而且这些特征点并不是通过一次迭代或是一个模型就能解决的,它可能会涉及到多次的迭代和多种模型的组合叠加。这样就会带来一个问题,在集成特效模块的时候,如果这些能力都是不断变化的,对于模块的使用来说,就存在一个不安全不稳定的因素。那么我们该如何把这个问题解决,或者说屏蔽掉呢?
这里我们提供了一个概念:首先在调用能力的时候,不直接去调用,而是把这些能力做进行抽象、封装,然后这些封装的模型,用来关联后面的一些不同的算法。因为别人在用SDK的时候,不一定按照你所有东西来集成,可能只使用其中部分能力,这样就有可能会导致一些特效的SDK存在版本不一致。如果没有这个代理层,当出现版本不一致的情况,对于上层来说可能就会出现大量接口的调整和修改,会比较耗时耗力。不过我们做代理的话,可能就给屏蔽上层接口的稳定性,然后通过我们的模型跟一些抽象对象,可以去驱动不同的AR模块的版本。
通过数据管线我们可以看到,从录制模块把数据传到effect接口,再把数据再给到AR SDK的时候,每个AR SDK都会有一个处理检测能力,会定时的检测主屏指标等,为什么要这么做?
我们知道现在的特效,场景、玩法是特别复杂的,处理效果没办法完全保证。所以为了保证每帧的处理和整体链路的稳定,我们需要不断做一些性能指标的监测,这些指标监测需要不断的反馈给上层的调用。比如当前数据传输的速度较快,或者传递帧太多处理不完,我们就可以通过数据管线进行回传,进行控制。甚至可以调整录制模块采集的帧率等,这样再把数据返回给录制模块,录制模块再将数据传递给其它模块,像预览进行渲染等。通过数据管线加代理的方案,我们就可以很好的整合不同AR版本、能力,对外保持接口的统一。
连麦模块数据中间件设计和实现

连麦模块也是目前音视频产品中用的比较多的一个能力,如在线教育、直播带货、PK、娱乐等等,并且已经慢慢成为一个标准化能力,但连麦模块在使用过程当中还存在比较多的问题。
标准直播是单向通信,且信令相对简单。融合连麦模块后,则会变成双向通信,信令会变得复杂,媒体流从单路流变成了多路流。如左下图,一个标准的直播SDK加连麦SDK的整合结构,可以看到白色区域是标准的直播流程,从创建直播间到建链、编码、封包,包括通过队列进行分发等等。当融合了连麦能力之后,对整个模块来说会增加更多的链路。首先会引入RTC的服务端,媒体服务端与信令服务端。另外可能还会引入一些类似于业务系统的消息机制,IM服务器等等。
用户如果发起连麦,可以看到左图红色的箭头。首先它会向RTC信令服务器发送连麦请求,同时也会向IM服务器发送一个请求,向IM服务器发送请求的原因,主要是为了做一些业务上的处理,比如说UI界面或者场景的一些流程处理。实际上信令服务器主要是为了传递加入房间的请求,请求到达主播直播间后,主播直播间会响应信令服务器,选择同意或者拒绝。如果同意,则会通过信令服务器将信号返回给小主播/观众,小主播/观众这个时候就会把数据传递到RTMP的媒体服务器,主播也会把媒体流传到RTMP服务器,两路流汇聚到RTMP服务器后,通过旁路转播等方式来进行对外的转播,此时观众就会看到两个主播或主播和观众合流的画面。
这个实现流程其实比较复杂繁琐,对于用户(主播)来说可能理解起来非常吃力。首先他要关心的东西特别多,例如 IM服务器、旁路直播,旁路直播还会涉及一些模板的设置等等,他可能都要去关心,所以使用起来非常的麻烦。而对于消费侧的观众来说,也同样存在一些问题,比如说会出现相同地址,但是会出现不同编码的两路流,这种情况下可能对于一些播放器来说会有一些挑战,可能会出现一些卡顿,因为它在不断的重置一些编码参数,另外这种方案通过旁路转推的方式,可能原始的直播流会断掉,就在频繁切换直播流跟混合流的过程当中可能就会出现延迟,可能对于拉流侧来说它就有很多的问题,也不太好。
针对这些问题其实我们也看到,就是用户在使用过程当中其实有很多的不方便,所以说可以看到说简单的数据传递,其实并不能让这个场景做的特别的简单和应用,后面我们就做了一些大量的端到端的一些数据链的整合,首先是我们考虑到了就是消费侧的编码播放器的一些问题,首先是切换编码参数,这个方案可能并不是一个适用的方案,所以我们采用了本地混流的技术方案,其好处在于我的推流各方面的参数可以保持一致而不发生变化,而其实对于移动端的处理能力来说,本地的2~3路流的合流,其实对一些硬件来说,压力并不是特别大。另外我们把IM服务器跟RTC信令服务器进行了整合,因为我们觉得让用户去关心这么多的信令其实是没有必要的,而且用户也可能会被这些问题所困扰,所以我们内部就通过服务端的方式进行了消息的整合,这样子就让用户使用起来变得更加简单。通过这种端到端的一些媒体和信令的整合,其实也是让数据流变得简单,提高了整体接入的应用性。
渲染模块数据中间件设计和实现

为什么说渲染是音视频SDK关键技术之一?从整体技术链路的角度来说,渲染模块实际上是用户最能感知到的一个模块。在一些复杂的场景下,渲染模块也承载了一些数据的交互和处理。因此渲染模块所包括的处理已经不再只是渲染,可能会涉及到某些场景的特殊需求,例如清屏等等:多人会议的时候,相当于一个关闭画面的效果;如多路混流,前面提到的RTC混流方案等等,渲染模块也可以作为其中的一个技术方案。也包括像小程序,尤其是一些小程序、安卓的同层设计方案,渲染方案等等。
每个模块甚至每个处理的节点,都有可能存在渲染的需求,所以说我们要将渲染模块进一步拆分。首先通过数据管线的一个能力,把各个模块的数据汇聚到渲染模块,然后再进行数据的处理。这里通常会将一些数据转换成各个平台的处理能力,比如CPU的Buffer,还有一些Surface等等。
数据加工,相对之前的生产流程这是新增的一个节点,它主要是为了应对一些复杂场景,如安卓的同层渲染、Surface的创建与绘制相分离,比如业务模块持有了Surface,但是渲染模块会间接引用并绘制。这里可能还会去做多路流混流的一些参数化配置,可以把每路流作为一个间值进行保存,也会做像图层跟视频帧的绑定,然后通过渲染线程同时绘制多路流。
我们都知道渲染必须要独立于当前的线程,否则对CPU和整体的开销影响还是比较大的。因此在创建完GL的环境之后,会按照GL队列将数据进行遍历拆分,来实现单路流,甚至是多路流的绘制。接下来就是一些标准的渲染流程,包括设置渲染的顶点,取渲染纹理,然后新增混合模式,通过绑定纹理来实现OpenGL整体的绘制。
多媒体
2020-07-31 17:01:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
想学更多的WebRTC知识,请关注 WebRTC中文社区
准备工作
安装好Jitsi的服务端,可以参考我之前的文章 5分钟搭建属于你的视频会议系统 , Jitsi手动安装
服务端需要申请证书,配置https证书。
说明
官方提供 Android 和 IOS )的sample示例程序,可以通过这个示例程序上面的代码以及官方文档,很快的把sdk接入到你的项目。
可是官方提供的sdk页面和功能不一定都满足我们的需求,这个时候我们的就需要自己构建SDK了。
clone项目
git clone git@github.com :jitsi/jitsi-meet.git
如果觉得慢,国内镜像
git clone https://gitee.com/xiangyanga/jitsi-meet.git
安装node和npm Node.js >= 12 npm >= 6.
sudo apt-get install npm nodejs
cd jitsi-meet
npm install
此时可能会等到一段时间,很多包在github上
修改config.js把 jitsi-meet.example.com 改为你的装有Jitsi 服务端的域名
hosts: {
        // XMPP domain.
        domain: ' jitsi-meet.example.com ',
// When using authentication, domain for guest users.
        // anonymousdomain: ' guest.example.com ',
// Domain for authenticated users. Defaults to .
        // authdomain: ' jitsi-meet.example.com ',
// Jirecon recording component domain.
        // jirecon: ' jirecon.jitsi-meet.example.com ',
// Call control component (Jigasi).
        // call_control: ' callcontrol.jitsi-meet.example.com ',
// Focus component domain. Defaults to focus..
        // focus: ' focus.jitsi-meet.example.com ',
// XMPP MUC domain. FIXME: use XEP-0030 to discover it.
        muc: ' conference.jitsi-meet.example.com '
    },
// BOSH URL. FIXME: use XEP-0156 to discover it.
    bosh: ' //jitsi-meet.example.com/http-bind ',
// Websocket URL
    // websocket: ' wss://jitsi-meet.example.com/xmpp-websocket ',
// The name of client node advertised in XEP-0115 'c' stanza
    clientNode: ' http://jitsi.org/jitsimeet ', 修改 webpack.config.js https://alpha.jitsi.net 改为你的域名,或者配置环境变量
const devServerProxyTarget
    = process.env.WEBPACK_DEV_SERVER_PROXY_TARGET || ' https://alpha.jitsi.net ';
启动程序
make
make dev
修改lib-jitsi-meet为本地仓库
lib-jitsi-meet是jitsi-meet的webrtc的核心库,Jitsi的webrtc的能力是来自这个库,此库可以直接单独使用,用来构建自己的GUI 为什么要修改它? 因为你可能需要更改一些webrtc的配置,那么要让这个能力生效,你就需要在Jitsi-meet改为自己的仓库
打开 package.json ,可以看到引用
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#9abec6faeea9698db40ed3adc98ce03ec546cedb", 简单改法。github上fork一个这个仓库,然后把引用改为自己的
"lib-jitsi-meet": "github:daxiondi/lib-jitsi-meet#9abec6faeea9698db40ed3adc98ce03ec546cedb",
重新执行
npm install 
make 
make dev 本地clone lib-jitsi-meet
"lib-jitsi-meet": " file:///Users/name/local-lib-jitsi-meet-copy ",
npm install lib-jitsi-meet --force && make 或者使用npm link
cd lib-jitsi-meet
#### create global symlink for lib-jitsi-meet package
npm link
cd ../jitsi-meet
#### create symlink from the local node_modules folder to the global lib-jitsi-meet symlink
npm link lib-jitsi-meet
注意: 这里不要使用yarn,官方说yarn会出问题
安装Android studio和安卓模拟器 网上一大堆,自行百度
安装react native
Jitsi使用的是react native,如果需要修改页面,在Jitsi-meet这个仓库修改对应的代码就行。 我使用的是mac,所以使用的是mac的命令,如果是其他系统参照 文档
brew install watchman
npx react-native run-android
如果一切顺利你会在安卓模拟器上看到jitsi的APK已经安装在了虚拟机上,我们打开他就行
遇到的问题
[JitsiMeetSDK] Unable to resolve module lib-jitsi-meet/lib-jitsi-meet.min from react/features/base/lib-jitsi-meet/_.native.js: lib-jitsi-meet/lib-jitsi-meet.min could not be found within the project.
If you are sure the module exists, try these steps:
Clear watchman watches: watchman watch-del-all
Delete node_modules: rm -rf node_modules and run yarn install
Reset Metro's cache: yarn start --reset-cache
Remove the cache: rm -rf /tmp/metro-*
2020-01-08 11:41:08.642084+0800 jitsi-meet[29478:302701] Received XPC error Connection interrupted for message type 3 kCFNetworkAgentXPCMessageTypePACQuery
2020-01-08 11:41:08.642470+0800 jitsi-meet[29478:302701] Received XPC error Connection invalid for message type 3 kCFNetworkAgentXPCMessageTypePACQuery
解决办法
npm i -g install-local
cd jitsi-meet
install-local ../lib-jitsi-meet
再次运行
npx react-native run-android
构建安卓SDK
/tmp/repo是你的本地maven仓库地址,路径随意
./android/scripts/release-sdk.sh /tmp/repo
执行完毕后你会在/tmp/repo路径下看到Jitsi-sdk需要的依赖包
新建安卓项目
新建安卓项目自行百度 build.gradle中添加
allprojects {
    repositories {
        maven { url "file:/tmp/repo" }
        google()
        jcenter()
    }
}
implementation ('org.jitsi.react:jitsi-meet-sdk:+') { transitive = true } example
package com.example.jitsitest
import android.content.Intent
import android.os.Bundle
import org.jitsi.meet.sdk.JitsiMeetActivity
import org.jitsi.meet.sdk.JitsiMeetActivityDelegate
import org.jitsi.meet.sdk.JitsiMeetConferenceOptions
import org.jitsi.meet.sdk.JitsiMeetView
class MainActivity : JitsiMeetActivity() {
private var view: JitsiMeetView? = null
    override fun onActivityResult(
            requestCode: Int,
            resultCode: Int,
            data: Intent?
    ) {
        JitsiMeetActivityDelegate.onActivityResult(
                this, requestCode, resultCode, data
        )
    }
override fun onBackPressed() {
        JitsiMeetActivityDelegate.onBackPressed()
    }
override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        view = JitsiMeetView(this)
        val options = JitsiMeetConferenceOptions.Builder()
                .setRoom(" https://meet.jit.si/test123 ")
                .build()
        view!!.join(options)
        setContentView(view)
    }
override fun onDestroy() {
        super.onDestroy()
        view!!.dispose()
        view = null
        JitsiMeetActivityDelegate.onHostDestroy(this)
    }
override fun onNewIntent(intent: Intent) {
        JitsiMeetActivityDelegate.onNewIntent(intent)
    }
override fun onRequestPermissionsResult(
            requestCode: Int,
            permissions: Array,
            grantResults: IntArray
    ) {
        JitsiMeetActivityDelegate.onRequestPermissionsResult(requestCode, permissions, grantResults)
    }
override fun onResume() {
        super.onResume()
        JitsiMeetActivityDelegate.onHostResume(this)
    }
override fun onStop() {
        super.onStop()
    }
}
官方文档
学习Webrtc,遇到问题的欢迎群里提问。
原创不易,转载请带出处。并不要删掉QQ群,谢谢
多媒体
2020-07-30 20:37:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
基本介绍
该Postnet码(邮政数字编码技术)条形码符号使用由美国邮政服务(USPS)指导项目的邮件。该星球(邮政字母数字编码技术)条形码也使用美国邮政识别和跟踪邮件。这两种符号通常结合使用,它们是USPS确认服务工作的一部分。Postnet 在2009年被USPS 智能邮件条形码(IMb)取代,Planet在2013年被IMb取代。

快速比较表
智能邮件条形码vs.Postnet vs.Planet 行星代码数字与Postnet代码数字相反,基本上将长条反转为短条,将长条反转为长号。 IMb除了提供增强的跟踪数据外,还有效地将Planet和Postnet的数据编码为单个符号。结果,IMb提供了允许用户使用一个条形码参与多种服务的功能。 与Postnet和Planet相比,IMb具有更大的容量。IMb字段有五个:条形码标识符,服务类型标识符,邮件程序ID,序列号和路由代码(AKA邮政编码)。这些字段可帮助用户确定所需的增值服务。
更重要的是
我们还列出了其他三组比较常见的混淆条形码: UPC与EAN 代码39与代码128 QR码vs.PDF417 vs.DataMatrix
如果您想比较其他条形码类型,请查看我们的在线条形码比较工具。
采取下一步
我们希望这有助于您确定要选择的条形码类型。现在是时候读取条形码了。您可以下载 Dynamsoft条形码阅读器 以在您的应用程序中实现条形码读取功能。
下载Dynamsoft条码阅读器后,您可以开始30天免费试用并尝试我们的在线演示。
本文转载自【慧都科技】。欢迎任何形式的转载,但请务必注明出处、不得修改原文相关链接,尊重他人劳动成果
多媒体
2020-07-30 14:50:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
如何通过浏览器访问网络摄像头?
HTML5 为Web开发人员提供了JavaScript API MediaDevices.getUserMedia()。在本指南中,我们将使用Dynamic Web TWAIN SDK网络摄像头插件构建一个在线网络摄像头演示。
为什么选择Dynamic Web TWAIN网络摄像头附加组件
为什么有免费的选项时考虑付费选项?我们列出了HTML5不提供的网络摄像头附加组件的一些高级功能。 浏览器兼容性 – HTML5与某些较旧的浏览器不兼容,例如Internet Explorer 8。 精确控制网络摄像头 –您可以完全控制摄像机,例如摇摄,倾斜,滚动,变焦,曝光,光圈和自动对焦。 分辨率设置 –使用GetResolution(),SetResolution()API获取和设置分辨率 扫描仪质量的图像 – Dynamsoft提供了多种图像增强技术来帮助用户生成高质量的图像,包括自动边界检测,噪声消除和透视校正。 上载到服务器 – SDK提供了内置方法,供用户将图像上载到服务器端。
如果您要构建一个健壮的,功能齐全的企业级应用程序,那么选择Dynamsoft的SDK这样的商业SDK将会为您带来巨大的投资回报。
关于Dynamic Web TWAIN网络摄像头附加组件
Dynamic Web TWAIN网络摄像头附加组件使Web开发人员可以使用JavaScript代码从网络摄像头捕获图像。网络摄像头SDK支持在Windows,macOS和Linux上的所有主流浏览器中嵌入视频流。Dynamic Web TWAIN还支持从移动相机捕获图像。
设备支持 兼容UVC(USB视频类)的网络摄像头 内置笔记本电脑摄像头 文件相机
浏览器支持(跨平台) Internet Explorer 8+ 边缘 火狐浏览器 铬
服务器端支持 Web服务器:Nginx,IIS,Tomcat,Apache等。 操作系统:Linux,Windows等。 语言:ASP.NET(C#/ VB.NET),PHP,JSP等
如何逐步使用网络摄像头附加组件
在这里,我们将向您展示如何使用SDK轻松地将HTML网页中的网络摄像头捕获和视频预览集成。首先,下载Dynamic Web TWAIN并将Resources文件夹复制到您的项目中。然后创建一个新的网页HelloWorld.html。
步骤1添加参考
步骤#2创建一个容器

步骤#3输入一个下拉列表和两个按钮
步骤#4通过调用Dynamsoft_OnReady()初始化对象
步骤#5控制网络摄像头
您可以使用以下API播放和停止视频流:DWObject.Addon.Webcam.StopVideo()和DWObject.Addon.Webcam.PlayVideo(DWObject,80, function(){})。 function enableButton(element) { element.style.backgroundColor = ""; element.disabled = ""; } function disableButton(element) { element.style.backgroundColor = "#aaa"; element.disabled = "disabled"; } function SetIfWebcamPlayVideo(bShow) { if (bShow) { DWObject.Addon.Webcam.StopVideo(); DWObject.Addon.Webcam.PlayVideo(DWObject, 80, function () { }); isVideoOn = true; enableButton(document.getElementById("btn-grab")); document.getElementById("btn-switch").value = "Hide Video"; } else { DWObject.Addon.Webcam.StopVideo(); isVideoOn = false; disableButton(document.getElementById("btn-grab")); document.getElementById("btn-switch").value = "Show Video"; } } function SwitchViews() { if (isVideoOn == false) { // continue the video SetIfWebcamPlayVideo(true); } else { // stop the video SetIfWebcamPlayVideo(false); } }
步骤#6通过使用CaptureImage()捕获图像 function CaptureImage() { if (DWObject) { var funCaptureImage = function () { SetIfWebcamPlayVideo(false); }; DWObject.Addon.Webcam.CaptureImage(funCaptureImage, funCaptureImage); } }
如何同时从扫描仪和网络摄像头捕获图像
文档/记录管理应用程序通常需要通过Web浏览器从扫描仪和网络摄像机捕获图像。您可以使用Dynamic Web TWAIN核心SDK和网络摄像头附加组件轻松实现此目的。
让我们继续在HelloWorld.html网页上进行工作。
步骤#1重写函数Dynamsoft_OnReady()
要控制TWAIN扫描器,请调用API:DWObject = Dynamsoft.WebTwainEnv.GetWebTwain('dwtcontrolContainer') Dynamsoft.WebTwainEnv.AutoLoad = false; Dynamsoft.WebTwainEnv.RegisterEvent('OnWebTwainReady', Dynamsoft_OnReady); // Register OnWebTwainReady event. This event fires as soon as Dynamic Web TWAIN is initialized and ready to be used var webCamStartingIndex;//This is used to separate scanners and webcams var DWObject; var isVideoOn = true; function Dynamsoft_OnReady() { DWObject = Dynamsoft.WebTwainEnv.GetWebTwain('dwtcontrolContainer'); // Get the Dynamic Web TWAIN object that is embedded in the div with id 'dwtcontrolContainer' if (DWObject) { DWObject.Width = 504; DWObject.Height = 600; document.getElementById('source').options.length = 0; var count = DWObject.SourceCount; for (var i = 0; i < count; i++) { document.getElementById('source').options.add(new Option(DWObject.GetSourceNameItems(i), i)); } webCamStartingIndex = i; var arySource = DWObject.Addon.Webcam.GetSourceList(); for (var i = 0; i < arySource.length; i++) document.getElementById("source").options.add(new Option(arySource[i], arySource[i]), i + webCamStartingIndex); // Get Webcam Source names and put them in a drop-down box } document.getElementById('source').onchange = function () { if (document.getElementById('source').selectedIndex < webCamStartingIndex) { if (arySource.length > 0) DWObject.Addon.Webcam.StopVideo(); isVideoOn = false; document.getElementById("btn-grab").style.backgroundColor = ""; document.getElementById('btn-grab').value = 'Acquire From a Scanner'; document.getElementById("btn-switch").style.display = 'none'; } else { DWObject.Addon.Webcam.SelectSource(document.getElementById("source").options[document.getElementById("source").selectedIndex].value); SetIfWebcamPlayVideo(true); document.getElementById('btn-grab').value = 'Acquire From a Webcam'; document.getElementById("btn-switch").style.display = ''; } document.getElementById("btn-grab").disabled = ""; } document.getElementById('source').onchange(); }
步骤#2捕获图像 function CaptureImage() { if (DWObject) { if (document.getElementById('source').selectedIndex < webCamStartingIndex) { DWObject.IfShowUI = true; DWObject.IfDisableSourceAfterAcquire = true; DWObject.SelectSourceByIndex(document.getElementById('source').selectedIndex); DWObject.CloseSource(); DWObject.OpenSource(); DWObject.AcquireImage(); } else { var funCaptureImage = function () { SetIfWebcamPlayVideo(false); }; DWObject.Addon.Webcam.CaptureImage(funCaptureImage, funCaptureImage); } } }
如何将扫描的图像上传到服务器端
步骤#1添加用于上传的按钮
步骤#2上传图片
准备好图像后,您可以通过调用HTTPUploadThroughPost()将其上传到Web服务器。 function upload() { if (DWObject) { // If no image in buffer, return the function if (DWObject.HowManyImagesInBuffer == 0) return; var strHTTPServer = location.hostname; //The name of the HTTP server. For example: "www.dynamsoft.com"; var CurrentPathName = unescape(location.pathname); var CurrentPath = CurrentPathName.substring(0, CurrentPathName.lastIndexOf("/") + 1); var strActionPage = CurrentPath + "filename"; // Action page DWObject.IfSSL = false; // Set whether SSL is used DWObject.HTTPPort = location.port == "" ? 80 : location.port; var Digital = new Date(); var uploadfilename = Digital.getMilliseconds(); // Uses milliseconds according to local time as the file name //Upload image in JPEG DWObject.HTTPUploadThroughPost(strHTTPServer, DWObject.CurrentImageIndexInBuffer, strActionPage, uploadfilename + ".jpg", OnHttpUploadSuccess, OnHttpUploadFailure); } }
该SDK支持ASP.NET(C#/ VB.NET),PHP,JSP,ASP等,以在服务器端接收图像数据。
PHP代码


JSP代码 <%@page import="java.util.*,java.io.File,java.io.FileOutputStream,org.apache.commons.fileupload.FileUpload,org.apache.commons.fileupload.FileItem,org.apache.commons.fileupload.disk.DiskFileItemFactory,org.apache.commons.fileupload.servlet.ServletFileUpload,sun.misc.BASE64Decoder"%> <%@page contentType="application/json; charset=utf-8" %> <%@page language="java" %> <% String strJson = "{\"success\":false}"; try{ // get more info from: http://commons.apache.org/proper/commons-fileupload/ DiskFileItemFactory factory = new DiskFileItemFactory(); ServletContext servletContext = this.getServletConfig().getServletContext(); File repository = (File) servletContext.getAttribute("javax.servlet.context.tempdir"); factory.setRepository(repository); ServletFileUpload upload = new ServletFileUpload(factory); List items = upload.parseRequest(request); Iterator iter = items.iterator(); String fileName = null; String tempFileName = null; String contentType = null; FileItem fileItem = null; while (iter.hasNext()) { FileItem item = iter.next(); String fieldName = item.getFieldName(); if(fieldName.equals("fileName")){ fileName = item.getString(); }else if(fieldName.equals("RemoteFile")){ tempFileName = item.getName(); contentType = item.getContentType(); fileItem = item; } } if(fileName == null || fileName.isEmpty()){ fileName = tempFileName; } String path = application.getRealPath(request.getServletPath()); String dir = new java.io.File(path).getParent(); String filePath = dir + "/UploadedImages/" + fileName; File file = new File(filePath); if(!file.getParentFile().exists()){ file.getParentFile().mkdir(); } if(!file.exists()){ file.createNewFile(); } if(!contentType.contains("text/plain")){ fileItem.write(file); }else{ String base64Str = fileItem.getString(); byte[] b = null; b = (new BASE64Decoder()).decodeBuffer(base64Str); FileOutputStream fileOutStream = new FileOutputStream(file); fileOutStream.write(b); fileOutStream.flush(); fileOutStream.close(); } strJson = "{\"success\":true, \"fileName\":\"" + fileName + "\"}"; } catch(Exception ex){ strJson = "{\"success\":false, \"error\": \"" + ex.getMessage().replace("\\", "\\\\") + "\"}"; } out.clear(); out.write(strJson); out.close(); %>
ASP.NET(C#)代码 <%@ Page Language="C#" %> <%@ Import Namespace="System.IO" %> <% string strJson = "{\"success\":false}"; try { HttpPostedFile file = Request.Files["RemoteFile"]; string fileName = Request.Form["fileName"]; if (string.IsNullOrEmpty(fileName)) fileName = file.FileName; string filePath = Server.MapPath(".") + "\
多媒体
2020-07-30 14:38:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
FusionCharts产品团队很高兴宣布FusionCharts 3.15.2 的发布!
这是自几个月前FusionCharts成为Idera系列成员以来的第一个FusionCharts版本,其中包括许多要求很高的功能增强。
对于FusionCharts,这包括用于 强大的 2D 图表 ,省时的CSS转换,通过针对选定图表的新事件来扩展开发人员的工作效率,以及对客户最要求的图表的大量质量改进和增强。
FusionCharts 3.15.2
和弦图的事件支持
在上一发行版中,我们引入了3个新图表,其中还包括一个Chord图。在此版本中,我们将为其提供API事件和方法支持。现在,开发人员可以将各种单击和悬停事件绑定到和弦图组件,从而为自定义图表提供了更大的灵活性。
支持CSS转换
在此版本之前,对CSS转换的支持是有限的,并且只能旋转。在此版本中,我们扩展了FusionCharts中CSS转换的范围,包括所有2D转换,例如rotate ,skew ,skewX ,skewY ,scale ,scaleX ,scaleY ,translate ,translateX ,translateY 和matrix 。现在,用户只需指定属性applyCSSTransform 的值为1,FusionCharts将自动处理指定给父容器的CSS转换。

改进了CSV导出
借助FusionCharts 3.15.2,我们改进了迷你图和迷你图图表的CSV导出结构。导出的数据具有预期的格式,现在可以通过任何其他电子表格工具或API直接使用。


沿轴,工具提示,图例等进行了改进……
所有图表都实现了许多改进: 改进了Y轴,可有效处理格式化的数字,小数和数字后缀或前缀。 跨多个图表(例如zoomline,zoomlinedy和其他堆叠的柱状图和条形图)进行了各种工具提示宏修复和改进。 在以前的版本中,我们引入了图例位置自定义。在此版本中,我们改进了其在不同浏览器中的行为。 对getJSONData()和getChartData()API方法的改进。
本文转载自【慧都科技】。欢迎任何形式的转载,但请务必注明出处、不得修改原文相关链接,尊重他人劳动成果
多媒体
2020-07-29 14:43:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
VR360
VR360 is a sample app code build on BasicAppTemplate template for anyone to start building an app with React360. There're also inline comments to guide how each part of the sample code works.
This Project function includes: Landing page Reset surface style Add VrButton Dynamically changing VR Image Image Preview (coming soon...) Transition effects (Coming soon...)
This Project technique includes: Basic UI and interaction Basic Audio Control Basic Environment(photo/video) control. Basic UI Animation.


多媒体
2020-07-28 18:30:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
DICOM 文件介绍
DICOM 全名叫 Digital Imaging and Communications in Medicine(医学数字图像与通信),是医学图像和相关信息的过节标准,也定义了临床所需数据医学图像格式;
DICOM 数据格式包含大量属性,例如病人名字、ID、年龄等基本信息,这些信息从储存在 “头文件” 中,这种储存方式避免了病人与图像误配;
一个病人样本 包含多个 DICOM 子文件,单个 DICOM 对象只包含具有一帧像素数据,帧与帧之间以序列形式存在,可以来回切换形成类似于电影放映的情景,类似于把一个3D 图像扫描成像,沿着垂直方向等厚度分割得到
ITK 对 DICOM 数据进行读取
ITK 根据 DICOM 文件读取方式有两种类型,单张 DICOM 读取或 序列 DICOM 读取
ITK 读取单张 DICOM 文件 itkImageSeriesReader; itkGDCMImageIO;
读取后可以通过 itkGCOMImageIO 中 Get*** 函数获取病人相关信息,并打印输出,这里需要注意一点,DICOM 对应的数据类型为 signed short;核心代码: string DirectoryPath = "D:/Data_Training/Dicom_data/215020265/AT000002.152046493.dcm"; typedef signed short InternalPixelType; const unsigned int Dimension = 2; using InternalImageType = itk::Image; typedef itk::Image ImageType; typedef itk::ImageSeriesReader ReaderType; typedef itk::GDCMImageIO ImageIOType; ImageIOType::Pointer gdcmIO = ImageIOType::New(); ReaderType::Pointer reader = ReaderType::New(); reader->SetImageIO(gdcmIO); reader->SetFileNames(DirectoryPath); // Reading Data; try { reader->Update(); reader->GetMetaDataDictionary(); gdcmIO->GetMetaDataDictionary();// 读取头文件信息; //信息赋值 char* name = new char[50]; char* patientID = new char[50]; char* time = new char[50]; char* manufacture = new char[50]; char* modility = new char[50]; char* hospital = new char[50]; char* sex = new char[50]; char* age = new char[50]; char* description = new char[100]; int pixelType = gdcmIO->GetPixelType(); int componentType = gdcmIO->GetComponentType(); int fileType = gdcmIO->GetFileType(); ImageIOType::ByteOrder byteOrder; byteOrder = gdcmIO->GetByteOrder(); unsigned int dim = 0; gdcmIO->GetDimensions(dim); ImageIOType::SizeType imgsize; imgsize = gdcmIO->GetImageSizeInPixels(); int componetSize = gdcmIO->GetComponentSize(); int dimension = gdcmIO->GetNumberOfDimensions(); int ori = 0; gdcmIO->GetOrigin(ori); int spa = 0; gdcmIO->GetPatientSex(sex); gdcmIO->GetPatientAge(age); gdcmIO->GetStudyDescription(description); gdcmIO->GetSpacing(spa); gdcmIO->GetPatientName(name); gdcmIO->GetModality(modility); gdcmIO->GetPatientID(patientID); gdcmIO->GetManufacturer(manufacture); gdcmIO->GetStudyDate(time); ImageIOType::TCompressionType compressType; compressType = gdcmIO->GetCompressionType(); gdcmIO->GetInstitution(hospital); ImageType::SpacingType spacetype; spacetype = reader->GetOutput()->GetSpacing(); ImageType::PointType origin; origin = reader->GetOutput()->GetOrigin(); cout << "name:" << name << endl; cout << "age:" << age << endl; cout << "sex:" << sex << endl; cout << "description:" << description << endl; cout << "hospital:" << hospital << endl; cout << "modility:" << modility << endl; cout << "manfacture:" << manufacture << endl; cout << "dim:" << dim << endl; cout << "origin:" << origin << endl; cout << "Time:" << time << endl; cout << "patientID:" << patientID << endl; } catch (const itk::ExceptionObject& excp) { cout << " Reading Exceptaion Caught" << endl; cout << excp.what() << endl; return EXIT_FAILURE; }
ITK 读取序列 DICOM 文件 itkImageSeriesReader; itkGDCMImageIO; itkGDCMSeriesFileNames;
与单张读取不同的是,多了一个 itkGDCMSeriesFileNames 类,用于打包 DICOM 序列文件,再传入 GDCMImageIO 进行读取 string DirectoryPath = "D:/Data_Training/Dicom_data/215020265"; typedef signed short InternalPixelType; const unsigned int Dimension = 3; using InternalImageType = itk::Image; typedef itk::Image ImageType; typedef itk::ImageSeriesReader ReaderType; typedef itk::GDCMImageIO ImageIOType; typedef itk::GDCMSeriesFileNames NamesGeneratorType; ImageIOType::Pointer gdcmIO = ImageIOType::New(); NamesGeneratorType::Pointer namesGenerator = NamesGeneratorType::New(); namesGenerator->SetInputDirectory(DirectoryPath); const ReaderType::FileNamesContainer & filenames = namesGenerator->GetInputFileNames(); std::size_t numberOfFileNames = filenames.size(); std::cout << numberOfFileNames << std::endl; ReaderType::Pointer reader = ReaderType::New(); reader->SetImageIO(gdcmIO); reader->SetFileNames(filenames); // Reading Data; try { reader->Update(); reader->GetMetaDataDictionary(); gdcmIO->GetMetaDataDictionary();// 读取头文件信息; //信息赋值 char* name = new char[50]; char* patientID = new char[50]; char* time = new char[50]; char* manufacture = new char[50]; char* modility = new char[50]; char* hospital = new char[50]; char* sex = new char[50]; char* age = new char[50]; char* description = new char[100]; int pixelType = gdcmIO->GetPixelType(); int componentType = gdcmIO->GetComponentType(); int fileType = gdcmIO->GetFileType(); ImageIOType::ByteOrder byteOrder; byteOrder = gdcmIO->GetByteOrder(); unsigned int dim = 0; gdcmIO->GetDimensions(dim); ImageIOType::SizeType imgsize; imgsize = gdcmIO->GetImageSizeInPixels(); int componetSize = gdcmIO->GetComponentSize(); int dimension = gdcmIO->GetNumberOfDimensions(); int ori = 0; gdcmIO->GetOrigin(ori); int spa = 0; gdcmIO->GetPatientSex(sex); gdcmIO->GetPatientAge(age); gdcmIO->GetStudyDescription(description); gdcmIO->GetSpacing(spa); gdcmIO->GetPatientName(name); gdcmIO->GetModality(modility); gdcmIO->GetPatientID(patientID); gdcmIO->GetManufacturer(manufacture); gdcmIO->GetStudyDate(time); ImageIOType::TCompressionType compressType; compressType = gdcmIO->GetCompressionType(); gdcmIO->GetInstitution(hospital); ImageType::SpacingType spacetype; spacetype = reader->GetOutput()->GetSpacing(); ImageType::PointType origin; origin = reader->GetOutput()->GetOrigin(); cout << "name:" << name << endl; cout << "age:" << age << endl; cout << "sex:" << sex << endl; cout << "description:" << description << endl; cout << "hospital:" << hospital << endl; cout << "modility:" << modility << endl; cout << "manfacture:" << manufacture << endl; cout << "dim:" << dim << endl; cout << "origin:" << origin << endl; cout << "Time:" << time << endl; cout << "patientID:" << patientID << endl; } catch (const itk::ExceptionObject& excp) { cout << " Reading Exceptaion Caught" << endl; cout << excp.what() << endl; return EXIT_FAILURE; }
运行结果如下:
除了上面病人年龄、性别、ID 基本信息之外,用 itkGDCMImageIO 获取更多病人相关信息,这里从 itkGDCMImageIO.h 源码中获取到了下面函数,大家可以参考一下:
VTK 读取 DICOM
vtk 读取 DICOM 就比较简单了,因为 VTK 封装的有相关 DICOM 读取类: vtkDICOMImageReader,可直接进行操作;
VTK读取单张DICOM文件并进行预览 vtkDICOMImageReader #include #include #include #include #include #include #include #include #include VTK_MODULE_INIT(vtkRenderingOpenGL2); VTK_MODULE_INIT(vtkInteractionStyle); int main() { using namespace std; string OpenPath = "D:/Data_Training/Dicom_data/215020265/AT000001.151955287.dcm"; vtkSmartPointer reader = vtkSmartPointer::New(); reader->SetFileName(OpenPath.c_str()); reader->Update(); vtkSmartPointer imageViewer = vtkSmartPointer::New(); imageViewer->SetInputConnection(reader->GetOutputPort()); vtkSmartPointer renWin = vtkSmartPointer::New(); imageViewer->SetupInteractor(renWin); imageViewer->Render(); imageViewer->GetRenderer()->ResetCamera(); imageViewer->Render(); renWin->Start(); return EXIT_SUCCESS; }
因为加入了一个交互窗口,因此可利用放大缩小
VTK读取序列DICOM文件并进行预览 vtkDICOMImageReader
用法与单张读取相似,区别是序列图这里读取的文件夹,而单张读取的是单张 DICOM; #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include VTK_MODULE_INIT(vtkRenderingOpenGL2); VTK_MODULE_INIT(vtkInteractionStyle); VTK_MODULE_INIT(vtkRenderingFreeType) using namespace std; class StatusMessage{ public: static std::string Format(int slice, int maxSlice) { std::stringstream tmp; tmp << "Slice Number " << slice + 1 << "/" << maxSlice + 1; return tmp.str(); } }; // Define own interation style; class myVtkInteractorStyleImage : public vtkInteractorStyleImage { public: static myVtkInteractorStyleImage* New(); vtkTypeMacro(myVtkInteractorStyleImage, vtkInteractorStyleImage); protected: vtkImageViewer2* _ImageViewer; vtkTextMapper* _StatusMapper; int _Slice; int _MinSlice; int _MaxSlice; public: void SetImageViewer(vtkImageViewer2* imageViwer) { _ImageViewer = imageViwer; _MinSlice = imageViwer->GetSliceMin(); _MaxSlice = imageViwer->GetSliceMax(); _Slice = _MinSlice; cout << "Slice : Min = " << _MinSlice << ", Max = " << _MaxSlice << endl; } void SetStatusMapper(vtkTextMapper* statusMapper) { _StatusMapper = statusMapper; } protected: void MoveSliceForward() { if (_Slice<_MaxSlice) { _Slice += 1; cout << " MoveSliceForward::Slice = " << _Slice << endl; _ImageViewer->SetSlice(_Slice); std::string msg = StatusMessage::Format(_Slice, _MaxSlice); _StatusMapper->SetInput(msg.c_str()); _ImageViewer->Render(); } } void MoveSliceBackward() { if(_Slice>_MinSlice) { _Slice -= 1; cout << "MoveSliceBackward::Slice = " << _Slice << endl; _ImageViewer->SetSlice(_Slice); std::string msg = StatusMessage::Format(_Slice, _MaxSlice); _StatusMapper->SetInput(msg.c_str()); _ImageViewer->Render(); } } virtual void OnKeyDown() { string key = this->GetInteractor()->GetKeySym(); if (key.compare("Up") == 0) { MoveSliceForward(); } else if (key.compare("Down") == 0) { MoveSliceBackward(); } vtkInteractorStyleImage::OnKeyDown(); } virtual void OnMouseWheelForward() { MoveSliceForward(); } virtual void OnMouseWheelBackward() { if (_Slice > _MinSlice) { MoveSliceBackward(); } } }; vtkStandardNewMacro(myVtkInteractorStyleImage); int main() { using namespace std; string OpenPath = "D:/Data_Training/Dicom_data/215020265/"; vtkSmartPointer reader = vtkSmartPointer::New(); reader->SetDirectoryName(OpenPath.c_str()); reader->Update(); vtkSmartPointer imageViewer = vtkSmartPointer::New(); imageViewer->SetInputConnection(reader->GetOutputPort()); //Slice Status vtkSmartPointer sliceTextProp = vtkSmartPointer::New(); sliceTextProp->SetFontFamilyToCourier(); sliceTextProp->SetFontSize(20); sliceTextProp->SetVerticalJustificationToBottom(); sliceTextProp->SetJustificationToLeft(); vtkSmartPointer sliceTextMapper = vtkSmartPointer::New(); string msg = StatusMessage::Format(imageViewer->GetSliceMin(), imageViewer->GetSliceMax()); sliceTextMapper->SetInput(msg.c_str()); cout << "msg" << msg.c_str() << endl; sliceTextMapper->SetTextProperty(sliceTextProp); vtkSmartPointer sliceTextActor = vtkSmartPointer::New(); sliceTextActor->SetMapper(sliceTextMapper); sliceTextActor->SetPosition(15, 10); //usage hint message; vtkSmartPointer usageTextProp = vtkSmartPointer::New(); usageTextProp->SetFontFamilyToCourier(); usageTextProp->SetFontSize(14); usageTextProp->SetVerticalJustificationToTop(); usageTextProp->SetJustificationToLeft(); vtkSmartPointer usageTextMapper = vtkSmartPointer::New(); usageTextMapper->SetInput("Slice with mouse wheel\n or Up/Down-Key\n =Zoom weh pressed right\n mouse button while dragging"); usageTextMapper->SetTextProperty(usageTextProp); vtkSmartPointer usageTextActor = vtkSmartPointer ::New(); usageTextActor->SetMapper(usageTextMapper); usageTextActor->GetPositionCoordinate()->SetCoordinateSystemToNormalizedDisplay(); usageTextActor->GetPositionCoordinate()->SetValue(0.05, 0.95); vtkSmartPointer myInteractorStyle = vtkSmartPointer::New(); myInteractorStyle->SetImageViewer(imageViewer); myInteractorStyle->SetStatusMapper(sliceTextMapper); vtkSmartPointer renWin = vtkSmartPointer::New(); imageViewer->SetupInteractor(renWin); renWin->SetInteractorStyle(myInteractorStyle); imageViewer->GetRenderer()->AddActor2D(sliceTextActor); imageViewer->GetRenderer()->AddActor2D(usageTextActor); imageViewer->Render(); imageViewer->GetRenderer()->ResetCamera(); imageViewer->Render(); renWin->Start(); return EXIT_SUCCESS; }
代码摘抄于官方,这里定义了一个自定义交互类,可以通过滑动鼠标或者按键来完成切片更换。并且通过类 vtkTextMapper 在界面上添加了两个文本注释,方便引导用户
预览效果如下:
Reference:
1, https://itk.org/Doxygen/html/classitk_1_1ImageSeriesReader.html
2, https://lorensen.github.io/VTKExamples/site/Cxx/IO/ReadDICOMSeries/
多媒体
2020-07-28 14:58:00
「深度学习福利」大神带你进阶工程师,立即查看>>> 本文由上海交通大学教授宋利在LiveVideoStackCon2020线上峰会的演讲内容整理而成,从分析视频传输系统延迟入手,分析视频编码延迟的产生机制,总结优化编码延迟的技术手段和业界典型的低延迟编码方案,讨论不同场景的延迟要求,并对后续技术演进发展方向进行展望。
文 / 宋利
整理 / LiveVideoStack

本次分享的主题是互动场景下的低延迟编码技术,内容分为四个方面:一是互动媒体服务;二是低延迟视频编码技术;三是低延迟编码方案;四是应用场景和发展趋势。
互动媒体服务
1.1 视频媒体形态

如图所示,我们将现有典型的视频相关服务按照高通量、强交互两个维度进行划分,其中横坐标表示高通量,纵坐标表示强交互,一些典型的视频映射到图中分布于不同的位置。
左下角部分可以称为基本视频,它涵盖了当前的一些主流应用,包括TV、视频监控、视频会议以及多人视频游戏等,其特点是以二维视频为主,同时交互形式包括单项、双项和多人交互。
如果从这个区域往外扩展,外面一层是可以称之为增强视频,沿高通量维度由高清向超高清、自由视、点云、光场过渡,交互维度包括仿真训练、电竞,两者都演进的方向是VR、AR,最后演进到全触感,也就是视频媒体形态正在由基本视频向增强视频演进,这两个维度某种程度和现在5G中两个维度很契合,高通量对应大带宽,强交互对应低延迟。

这张图显示了流媒体视频的典型服务场景,流媒体服务经过多年的发展,现在已经形成一个比较完整的技术和生态链,从源端、云端、边端到终端,包括背后的技术体系也相对比较趋同。现在经常使用的是以RTMP代表加H.264进行源端的推流,到CDN边缘上通过265,包括下行的HLS协议转换,形成流媒体服务的基本流,然后用户侧通过播放器从源端进行拉流,获得流媒体直播的体验。这套架构基本上比较成熟和完善,各家公司的竞争点主要体现在用不同的编码器进行替换,不同上下行协议的改造,以及CDN资源的部署,以此获得竞争优势。从整个媒体服务形态变化的角度看,大部分的努力是针对前面提到的通量这个维度。
图中展示了流媒体实时交互演进的一个典型示例,在直播场景下,通过手机小屏发出交互指令,可以在大屏播放时产生交互的反馈,获得一些个性化的体验;比如在下行过程中发起用户指令,叠加符合正在播放内容的、个性化渲染特效。在这种场景下,整个流媒体架构就会发生变化。在此之前是在云端、边端进行处理,与终端并没有太多交互,技术要素变化不大;但是增加互动维度后,在边缘侧就可以引入很多新的要素。
1.2 系统组成要素

构建一套实时的流媒体系统需要对系统中多个方面进行改进,除了视频编码标准外,媒体传送协议和视频渲染技术都需要实时化和低延迟处理。视频编码方面,低延迟编码技术可以和多种编码标准进行结合。
1.3 互动媒体服务系统的权衡

互动媒体服务系统与单点技术不同,需要考虑多方面因素的权衡。首先要满足低延迟,否则影响互动效果。其次是高体验,互动媒体是在现有媒体上叠加的效果,所以体验是也应该是叠加式的,不能因为互动而使原有基础视频的画质下降。最后是用户的大规模,与视频会议系统不同,一场会议很少会出现超过千人级的规模,互动流媒体场景下,由于更接近直播流媒体,它的用户数量会比较多。
低延迟视频编码技术
2.1 视频编解码

第二部分介绍了低延迟视频编码的共性技术,这些技术可能会用在不同的编码方案中。视频编码器有几大阵营,在分发域有:H.264/HEVC/VVC、AVS2/3、VP9/AV1,在分发域中压缩性能是它的主要驱动力。在制作域有:TICO/JPEG-XS、JP2K、LLVC、XAVC、ProRes,这些编码器虽然是应用于视频中,但从技术角度来说更多是图像编码器。

将两大类编码器放在一起,更容易看出彼此之间的差异性,图中展示了五个维度,分别是:高压缩比、低延迟、低复杂度、高质量、平台友好性。将五个维度进行比较,分发域的编码如图所示,它的特点是高压缩比,但延迟和复杂度比较大,质量没有制作域那么高,相对来说压缩比就比较大。
帧内标准如JEPG、JPEG2K、XAVC、WebP,在延迟性和复杂度方面要好些,但代价是压缩比要差些,因为它应用的是专业领域场景,所以质量和码率比较高。
JPEG XS 是JPEG阵营过去两年推出的一个标准,强调复杂度和速度,它的性能在低延迟、低复杂度方面表现比较优秀,但压缩比较差。由图可知,要根据不同应用场景做出均衡性选择。
2.2 编码延迟的构成

编码延迟是指从视频单元(通常是帧)采集到编码完成生成码流所消耗的时间。公式中的max是表示以编码单元中花费时间最大的模块为延迟时间。
编码延迟的来源主要包括三部分:一是视频帧参考关系,二是编码流水线的设计,三是编码模块的复杂度。
2.2.1 低延迟参考结构

左图展示的是HEVC RA模式,典型的编码器一般使用双向B帧,在提高压缩率的同时会带来帧重排序延迟,在HEVC的典型RA模式中,双向参考关系额外引入了3帧延迟。
右图展示的是HEVC的LDP模式,如果只考虑延迟,HEVC的LDP模式只使用P帧,适用于低延迟场景,其单向参考关系不引入额外的延迟,但是带来了9%~42%的编码性能损失。
2.2.2 低延迟编码帧结构

周期性帧内刷新(PIR)编码结构是在LDP模式的基础上进一步降低延迟,被很多商业编码器所支持,被称为超低延迟的编码配置。
如图所示,它的原理是将一帧切成四个纵向的条块,每隔四帧就可刷新一遍。
它的特点主要有:一是常用的超低延迟配置模式,输出码率平缓,缓冲区溢出概率小。二是可以确保在一个刷新周期内完全恢复错误。三是刷新的频率方向,可以根据视频内容进一步优化。
2.2.3 编码流水线优化

编码器的整体架构决定了编码器的延迟和并行度,主要分为三种:帧处理、块处理、条处理。
帧处理是指对每个运动估计进行预测,编码器处理的模块是以帧为单元的,它会将整帧运动估计处理完,再进行运动补偿,在MPEG2这种比较简易的编码器中经常使用这种结构。
块处理相当于单线程参考编码器中的小逻辑,将每个CTU或每个宏块逐步推送到运动估计、预测等部分,X-循环是指每个块里会进行不同模式的选择,是不同模式的循环。Y-循环是指所有的块可以再循环一遍。其中每块中的宏块可以独立输出,不需要等整个帧处理完,所以它的好处是输出粒度小。但如果将块级的编程变成高并发、流水化结构就比较困难,因为粒度小,想做到流水化结构,处理单元要足够多。需要说明,编解码上下文很关键,切断上下文则编码预测性能会受到大的影响。
条处理是基于两者之间的处理,每一个内循环的粒度是以条块为基础,外循环是不同条块之间流水化推进。同时,运动估计和运动补偿耦合比较好适合于整体计算,可以将运动估计和预测放到一个计算单元上,其余的部分组合到另一个上,这样可以增加多级流水的处理。
这三种处理方式属于任务级分解,也是并发、并行化操作。此外,还有数据级分解,就是数据被切割并分配给不同的处理器。右图是在处理4K时可以切成多个高清进行处理,可以用到四种方案:帧级并行、slice级并行、tile级并行、波前并行处理。在实际的编码中,并发、并行化操作中任务级分解和数据级分解是混合使用的。
2.2.4 低延迟并行码率控制

一旦变成并行流水化,除了各个基本模块的调度,还要涉及整体码率控制的调整。码流的平稳程度是影响编码缓冲区延迟的重要因素,缓冲区上溢会造成数据丢失;缓冲区下溢会造成编码器无法得到数据,进而使得视频卡顿。在条/块级并行编码方案中,码率控制模型需要重新优化设计。
2.2.5 编码模式快速预测

第三个方面涉及编码中各个模块的复杂度,当代编码器的编码模式比较多,组合量比较大,即使每种编码模式足够快也不行,核心在于如何快速的在众多候选模式中选出准确的哪个,这就需要根据某种属性快速做出决策。这时深度学习的方法可以发挥作用,近期我们的一个工作中,采用基于深度学习预测CU划分和基于统计学习预测PU模式组合,替换高复杂度的递归编码探索,实现在性能基本保持不变前提下实现复杂度的显著降低。
低延迟编码方案
3.1 SVT构架

这部分介绍一些典型的系统编码方案,首先是英特尔开源的SVT架构,它支持了前面所提到的很多要素,设计比较不错。
SVT构架细节

SVT架构是基于软件的视频编码优化框架,通过联合前处理-编码内部算法,实现性能-延迟-质量的三维优化,并针对Xeon处理器进行优化。
之所以称SVT为三维并行架构,因为它解耦视频分析、模式选择与编码,实现进程级并行;分层GOP内的帧级并行;将一帧图像分为不同条块,实现条块级并行。
SVT也照顾到速度和码率的主观质量优化,对于速度方面的主观质量优化有:首先根据整体复杂度目标,设置搜索的划分模式集合;其次根据块的HVS重要性进行区分;对于码率方面的主观质量优化有:一是根据HVS重要性调整QP偏置;二是降低人眼不敏感区域变换域高频分量。
3.2 H.265低延迟方案

SVT支持很多个编码器,以SVT-HEVC为例,它支持了13个preset(M0~M12),在速度和视觉质量之间实现了较好的权衡。其次,采用客观质量模式(默认)用于权衡速度和客观质量的关系,性能和速度优于x265。而且,最快档次的延迟在百毫秒级别,压缩比在300:1左右,配合其他低延迟技术可以降为小几十毫秒级别。

这部分介绍了H.265低延迟方案的硬件编码器,首先,NETINT基于自研芯片设计了Codensity T408视频转码器,在ASIC中进行复杂的编解码算法处理,从而最小化主机CPU的使用率,编码延迟约为5ms。
其次,NVIDA基于GPU设计了NVENC编码器,可以大幅度释放CPU和内存的负载压力,编码延迟约为3-10ms。
3.3 H.264低延迟方案

前面的两个方案主要面向云端的转码、流媒体服务等,还有一类是面向移动终端的,除了低延迟之外,对功耗、复杂度要求更严格,在这种场景下使用比较多的方案是基于H.264。H.264标准已经被工业界广泛认可和应用,其作为H.265的上一代标准,本身的编码复杂度相对较低,现有低延迟方案大都基于硬件设计。
左图是TPCast方案,它使用CAST公司的H.264-E-BPF IP核编码器,基于H.264 Baseline Profile设计。而且采用CAVLC选项降低熵编码复杂度,并采用帧内刷新技术降低比特率峰。它的编码延迟为10ms级别,压缩率为50:1。
右图是HHI方案,它基于H.264 Baseline Profile设计,采用Intra(16×16和4×4)和VLC编码(不使用CABAC),编码延迟为宏块行级,压缩率为10:1~20:1。两种方案应用的场景不同。
3.4 JPEG-XS低延迟方案

JPEG-XS低延迟方案是更低延迟的方案,它支持Main、Light、Light-subline、High这4种配置编码延迟为毫秒甚至微秒级,视觉无损情况下的压缩率为2:1~6:1,是一个简化的帧内压缩技术。
它的编码过程有:样本拉伸、DC偏移量去除、可逆颜色变换、小波变换、预量化、常规量化、熵编码。JPEG-XS主要是由IntoPIX公司推动的。
3.5 新型的低复杂度/低延迟编码方案

以V-Nova为代表介绍一下新型的低复杂度/低延迟编码方案,V-Nova P+立项的MPEG 5--LCEVC标准,为内容分发域提供高压缩率、低复杂度方案。
左图所展示的编码结构类似于可伸缩编码SVC,分为基本层和增加层,网络带宽的适应性不是其考虑重点,而是考虑终端的兼容性以及复杂度,面向内容分发域。可以应用的场景如当手机上有一块硬解码能力的芯片,支持264 HD,如果传来一个4K的内容,利用这种方案可以进行分层,基本层利用264 HD,增强层用HEVC 4K编码,这样基本层可以使用手机的硬解码264 HD能力,而增强层可以使用复杂度比较低的软件能力,将其进一步增强解码提升到4K。
除此之外,V-Nova公司也正在SMPTE的制作域中推VC-6,主要用于专业的内容制作和影像应用。它的卖点是结合了机器学习技术和优化的码率控制,使用intra-only配置,编码延迟为80ms,编码的HD流为60Mbps。
应用场景和发展趋势
4.1 应用场景

图中展示的是不同延迟量级对应的应用场景的划分,低延迟要与不同场景进行耦合,不同场景对延迟量的要求不同。图中横轴表示编码延迟,根据延迟时间将场景分为四种,纵轴表示压缩比。

秒级延迟场景以赛事直播为例,它对编码延迟要求并不高,之前一般采用H.264实时编码,对4K或8K视频开始使用H.265或AVS2编码标准实时编码。
百毫秒级延迟场景如视频通信、无线投屏,视频通信可接受的端到端延迟为~200ms。以ZOOM为例,它采用了H.264标准编码,编码延迟为11ms(720p),端到端延迟要求低于150ms。无线投屏以Miracast为例,它认证的无线设备端到端延迟不超过250ms,使用H.264和H.265(可选)标准,编码延迟约10~100ms。

十到一百毫秒级别称之为十毫秒级延迟场景,以云VR、云游戏为例,一般端到端延迟低于100ms时才能获得良好的体验。
NVIDA GeForce Now使用NVENC硬件编解码器可实现3-10ms的编码(H.265)和解码延迟,端到端延迟约75ms。
Google Stadia采用H.264和VP9编码标准,端到端延迟约130ms。

毫秒级延迟大多数场景不超过10毫秒,应用领域涵盖远程制作、数字孪生、高级XR等,往往同时需要非常高的视频质量和超低延迟,需要TSN/TTE(时间敏感/触发)类的基础网络架构支持,目前可选择的有JPEG-XS、SMPTE无压缩的解决方案,压缩效果还不太好,所以高压缩比下的超低延迟编解码仍然存在巨大技术挑战。
4.2 发展趋势

近期在多视角、自由视方面,华为、优酷、咪咕都做过一些示范应用,即将原先导播切换的自由度传送到用户侧,由用户进行发送,用户在观看流媒体视频中可以根据自己的喜欢进行视角的切换,以实现媒体服务的个性化。

以游戏类和远程操控类为代表的场景,以往观看流媒体是被动接收,现在大小屏都可以进行实时性交互,因此互动体验增强,这也是互动媒体发展的趋势。

本次分享主要介绍了低延迟互动媒体服务中的低延迟视频编解码环节的相关技术。要做到较好的低延迟互动媒体服务,还需要低延迟传送协议、实时图像渲染以及基础ICT网络技术整体的演进。就编码而言,需要结合平台特性重构编码实现架构,细化编码各工具性能与延迟关系。
比较理想的做法是面向不同延迟的弹性编码方案,如右图所示,将RD曲线按照延迟-压缩比的关系,形成一套根据场景需求进行弹性配置的编码框架,这是近期低延迟编码努力的方向。
多媒体
2020-07-28 11:34:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
mac下捕捉摄像头 ./ffmpeg -f avfoundation -framerate 30 -video_size 1280x720 -i "0:0" -vcodec libx264 -preset ultrafast -acodec libmp3lame -ar 44100 -ac 1 -f flv rtmp://127.0.0.1:1935/live/movie
参考 https://www.cnblogs.com/jackluo/p/7486239.html https://www.jianshu.com/p/d9c66d7d1653 https://www.jianshu.com/p/4f399b9dfb43
多媒体
2020-07-25 15:04:00
「深度学习福利」大神带你进阶工程师,立即查看>>> 队列以及队列族
队列是应用程序和物理设备通信的手段。 应用程序以向队列提交命令缓冲区的形式提供作业任务。 物理设备对它们进行读取并异步处理。
物理设备可能支持四种类型的队列,如下图所示。 物理设备上可能有多个相同类型的队列;这就允许应用程序选择它需要的队列数量和队列类型。 例如,一个简单的应用程序可能需要两个队列:计算队列和图形队列;在这里,前者用于卷积计算,第二个渲染计算后的高斯模糊图像(blur image)。
VK_QUEUE_GRAPHICS_BIT :这是一个图形队列;支持与图形相关的操作, 例如绘制点,线和三角形。
VK_QUEUE_COMPUTE_BIT :这是一个计算队列;提供计算能力。
VK_QUEUE_TRANSFER_BIT :这是一个转移队列;支持传输操作, 例如复制缓冲区和图像内容。
VK_QUEUE_SPARSE_BINDING_BIT :这是一个稀疏队列;能够进行稀疏的内存管理。
物理设备可能由一个或多个队列族组成,在每个队列族内公开了存在什么类型的队列。 此外,每个队列族可以具有一个或多个队列计数。 下图显示了三个队列族及其各自的若干队列:

例如:
一个物理设备包括四种类型的队列族,根据它支持的队列类型(queueFlags)以及每个族中的队列数量(queueCount),其中的每个队列会包含不同的功能。
注意:
Queue 代表 一个GPU线程 ,Vulkan设备执行的就是提交到Queues中的工作。物理设备中Queue可能不止一个, 每一个Queue都被包含在Queue Families中 。
Queue Families 是一个有 相同功能的Queues的集合 ,它们的性能水平和对系统资源的访问是相同的,并且在它们之间数 据传输工作没有任何成本(同步之外 )。
一个物理设备中可以存在多 个Queue Families ,不同的Queue Families有不同的特性。 相同Queue Families中的Queues的功能相同 ,并且可以 并行 运行。 VulKan的多线程

Vulkan为了充分发挥CPU多核多线程的作用,引入了command buffer的概念。 多个线程可以同时协作 ,每个CPU线程都可以往自己的command buffer中 提交渲染命令 ,然后统一提交到对应的Queue中,大大提高了CPU的利用率。 理论上,一个线程可以把Command记录到多个Command Buffer中,多个线程也可以共享同一个Command Buffer,但是一般不鼓励多个线程共享一个Command Buffer。
其中记录命令过程如:
单线程:
多线程:
需要的工作:
同步原语
https://zhuanlan.zhihu.com/p/24817959
https://zhuanlan.zhihu.com/p/80692115
Vulkan中主要有四种同步原语(synchronization primitives): Fences 最大颗粒度的同步原语,目的是给CPU端提供一种方法,可以知道GPU或者其他Vulkan Device什么时候把提交的工作全部做完。 如果你熟悉Android显示机制的话,acquire fence或者retire fence就是类似的作用 Semaphores 颗粒度比Fences更小一点,通常用于不同Queue之间的数据同步操作 Events 颗粒度更小,可以用于Command Buffer之间的同步工作,除了在同一个queue内部使用外,event还可以用在host和一个queue中做同步。
注意,event不能用于不同queue之间同步。 Barriers Vulkan流水线(Pipeline)阶段内用于内存访问管理和资源状态移动的同步机制, Barriers用于显式的控制buffer或者image的访问范围,避免hazards(RaW,WaR,and WaW),保证数据一致性 , Barrier是 同一个queue 中的command,或者 同一个subpass 中的command所明确指定的依赖关系。 Barriers可以分为 Execution Barrier和Memory Barrier。但 Execution Barrier只保证了执行的顺序,对于存储修改的顺序(即无法读取新更新的数据),execution barrier无能为力。这时需要Memory Barrier,Pipeline Barrier API中,可以指定三个数组。这三个数组,分别定义了不同类型的memory barrier: 全局memory barrier buffer上的memory barrier image上的memory barrier
全局memory barrier只有src的访问mask和dst的访问mask,因此作用于当前所有的resource。需要具体操纵某个resource的时候,根据resource的类型,分别使用buffer或者image的memory barrier.
假设我们有个两个渲染管线P1 和 P2,P1会通过Vertex Shader往buffer写入顶点数据,P2需要在Compute Shader中使用这些数据。
如果使用fence去同步,你的流程应该是这样:P1的Command提交后,P2通过fence确保P1的操作已经被全部执行完,再开始工作。
但是这种大颗粒度的同步操作无疑造成了耗时操作:P1的数据在Vertex Shader阶段就已经准备好了,我们为什么要等到它所有操作执行完再开始?P2平白多等待了很长时间,而且在这个期间P2的其他阶段并没有使用到P1的数据,也是可以执行的啊。
Barriers的引入完全解决了这个问题,我们只需要告诉Vulkan,我们在P2的Compute Shader阶段才会等待P1 Vertex Shader里面的数据,其他阶段并不关心,可以同步进行。

Vulkan的执行顺序其实是有一定的潜规则的,在没有同步原语的情况下: Command Buffer中的Command,先记录的先执行 先提交的Command Buffer先执行 同一个Queue中,一起提交的Command Buffer1 和Command Buffer2 按照下标的顺序执行,Command Buffer1 先执行
多媒体
2020-07-25 14:46:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
JPEG文件简介
JPEG的全称是 JointPhotographicExpertsGroup (联合图像专家小组),它是一种常用的图像存储格式, jpg/jpeg是24位的图像文件格式,也是一种高效率的压缩格式,文件格式是JPEG(联合图像专家组)标准的产物,该图像压缩标准是国际电信联盟(International Telecommunication Union,ITU)、国际标准化组织(International Organization for Standardization,ISO)和国际电工委员会(International Electrotechnical Commission,IEC)共同制定。JPEG标准正式地称为ISO/IEC IS(国际标准)10918-1:连续色调静态图像数字压缩和编码(Digital Compression and Coding of Continuous-tone Still Images)和ITU-T建议T.81。
JPEG是第一个国际图像压缩标准,用于连续色调静态图像 (即包括灰度图像和彩色图像),其最初目的是使用64Kbps的通信线路传输720×576 分辨率压缩后的图像。通过损失极少的分辨率,可以将图像所需存储量减少至原大小的10%。由于其高效的压缩效率和标准化要求,目前已广泛用于彩色传真、静止图像、电话会议、印刷及新闻图片的传送上。但那些被删除的资料无法在解压时还原,所以* .jpg/.jpeg文件并不适合放大观看,输出成印刷品时品质也会受到影响。
JPEG文件格式
JPEG的文件格式一般有两种文件扩展名:.jpg和.jpeg,这两种扩展名的实质是相同的,我们可以把.jpg的文件改名为.jpeg,而对文件本身不会有任何影响。严格来讲,JPEG的文件扩展名应该为.jpeg,由于DOS时代的8.3文件名命名原则,就使用了.jpg的扩展名,这种情况类似于.htm和.html的区别。 JPEG标准不指定任何固有的文件格式。它只定义压缩比特流的语法。这就产生了一定数量的文件格式来存储JPEG压缩后的图像,例如JPEG文件交换格式(JPEG File Interchange Format,JFIF),JPEG推广到TIFF6.0、FlashPix等。但它们中的每一个都不能认为是由国际标准委员会支持的正式定义的国际标准。
JPEG格式可以分为 标准JPEG 、 渐进式JPEG 和 JPEG2000 三种格式。
标准JPEG:该类型的图片文件,在网络上应用较多,只有图片完全被加载和读取完毕之后,才能看到图片的全貌;它是一种很灵活的图片压缩方式,用户可以在压缩比和图片品质之间进行权衡。不过,通常来讲,其压缩比在10:1到40:1之间,压缩比越大,品质就越差,压缩比越小,品质就越好。 JPEG格式压缩的主要是高频信息,对色彩的信息保留较好 ,适合应用于互联网,可减少图像的传输时间,可以支持24bit真彩色,也普遍应用于需要连续色调的图像。JPEG由于可以提供 有损压缩 ,因此压缩比可以达到其他传统压缩算法无法比拟的程度。其压缩模式有以下几种: 顺序式编码(SequentialEncoding) 递增式编码(ProgressiveEncoding) 无失真编码(LosslessEncoding) 阶梯式编码(HierarchicalEncoding)
JPEG的压缩步骤 颜色转换 :由于JPEG只支持YUV颜色模式,而不支持RGB颜色模式,所以在将彩色图像进行压缩之前,必须先对颜色模式进据转换。转换完成之后还需要进行数据采样。一般采用的采样比例是2:1:1或4:2:2。由于在执行了此项工作之后,每两行数据只保留一行,因此,采样后图像数据量将压缩为原来的一半。 DCT变换 :DCT(DiscreteConsineTransform)是将图像信号在频率域上进行变换,分离出高频和低频信息的处理过程。然后再对图像的高频部分(即图像细节)进行压缩,以达到压缩图像数据的目的。首先将图像划分为多个8*8的矩阵。然后对每一个矩阵作DCT变换(变换公式此略)。变换后得到一个频率系数矩阵,其中的频率系数都是浮点数。 量化 :由于在后面编码过程中使用的码本都是整数,因此需要对变换后的频率系数进行量化,将之转换为整数。由于进行数据量化后,矩阵中的数据都是近似值,和原始图像数据之间有了差异,这一差异是造成图像压缩后失真的主要原因。 编码 :编码采用两种机制:一是0值的行程长度编码;二是熵编码(EntropyCoding)。在JPEG中,采用曲徊序列,即以矩阵对角线的法线方向作“之”字排列矩阵中的元素。这样做的优点是使得靠近矩阵左上角、值比较大的元素排列在行程的前面,而行程的后面所排列的矩阵元素基本上为0值。行程长度编码是非常简单和常用的编码方式,在此不再赘述。编码实际上是一种基于统计特性的编码方法。在JPEG中允许采用HUFFMAN编码或者算术编码。
更详细可以参看《 色彩空间RGB/CMYK/HSL/HSB/HSV/Lab/YUV基础理论及转换方法:RGB与YUV 》、《 视频采样,量化,编码,压缩,解码相关技术原理学习笔记 》
Baseline JPEG/基本JPEG :这种类型的JPEG文件存储方式是按从上到下的扫描方式,把每一行顺序的保存在JPEG文件中。打开这个文件显示它的内容时,数据将按照存储时的顺序从上到下一行一行的被显示出来,直到所有的数据都被读完,就完成了整张图片的显示。这种图片在web中,如果没有给图片指定宽高,会造成重绘。
progressive jpeg/渐进式JPEG :JPEG文件包含多次扫描,这些扫描顺寻的存储在JPEG文件中。打开文件过程中,会先显示整个图片的模糊轮廓,随着扫描次数的增加,图片变得越来越清晰。该类型的图片是对标准JPEG格式的改进,当在网页上下载渐进式JPEG图片时,首先呈现图片的大概外貌,然后再逐渐呈现具体的细节部分,因而被称之为渐进式JPEG。这种通过HTTP2 多路复用传递渐进式JPEG的扫描图层来提高感知性能和速度指数的方式 已经早在2012年被Google的John Mellor 注意到了。他一直在实验 SPDY 协议,HTTP2的前身。
JPEG2000 :一种全新的图片压缩发,压缩品质更好,并且改善了无线传输时,因信号不稳定而造成的马赛克及位置错乱等问题。另外,作为JPEG的升级版,JPEG2000的压缩率比标准JPEG高约30%,同时支持有损压缩和无损压缩。它还支持渐进式传输,即,先传输图片的粗略轮廓,然后,逐步传输细节数据,使得图片由模糊到清晰逐步显示。此外,JPEG2000还支持感兴趣区域,也就是说,可以指定图片上感兴趣区域的压缩质量,还可以选择指定的部分先进行解压。还有个优势就是,JPEG2000从无损压缩到有损压缩可以兼容



压缩步骤
由于JPEG的有损压缩方式(Lossy mode of operation)并不比其他的压缩方法更优秀,
因此我们着重来看它的有损压缩中最常用的基线JPEG算法(baseline sequential)。以一幅24位彩色图像为例,JPEG的压缩步骤分为:
颜色转换
JPEG支持图像采用任何一个色彩空间,支持1~4个颜色分量。灰度图像颜色分量数为1。RGB、YUV、YCbCr等拥有3种颜色分量。4种颜色分量的例子是青、洋红、黄和黑(Cyan,Magenta,Yellow,and Black,CMYK)。为了减少色度通道包含的大量的冗余信息,本例中采用YCbCr色彩空间。首先需要进行从RGB到YCbCr的色彩空间变换: Y = 0.299000R + 0.587000G + 0.114000B Cb = -0.168736R - 0.331264G + 0.500002B Cr = 0.500000R - 0.418688G - 0.081312B
其中,Y表示亮度分量,Cb和Cr表示蓝红色度分量。
DC电平偏移
最初,在图像中的像素存储在无符号的整数中。对于数学计算,在图像中任何变换或数学计算开始之前,根本上是将这些采样转换成两个补码表示。DC电平偏移的目的是保证输入图像的采样有近似地集中在零附近的动态范围。DC电平偏移执行的图像采样只通过无符号数表示。
方法:假设图片分量的采样精度为n,那么分量中的每个像素值应减去2的(n-1)次幂。
对于图像而言他的采样由无符号的整数表示,例如CT(X光断层成像)图像,动态范围已经集中于零附近,所以不需要DC电平偏移。
子采样
色彩空间转换之后,图像的大多数空间信息包含在亮度分量Y中。色度分量Cb和Cr包含大量冗余的颜色信息,所以我们运用子采样较少色度数据量以在丢失少量信息的情况下压缩图像。基线JPEG常用的子采样格式为4:2:0,同时支持4:2:2和4:4:4颜色格式。
DCT变换
DCT(DiscreteCosineTransform)是将图像信号在频率域上进行变换,分离出高频和低频信息的处理过程。然后再对图像的高频部分(即图像细节)进行压缩,以达到压缩图像数据的目的。首先将图像划分为多个8*8的矩阵。然后对每一个矩阵作DCT变换。变换后得到一个频率系数矩阵,其中的频率系数都是浮点数。
量化
由于在后面编码过程中使用的码本都是整数,因此需要对变换后的频率系数进行量化,将之转换为整数。由于进行数据量化后,矩阵中的数据都是近似值,和原始图像数据之间有了差异,这一差异是造成图像压缩后失真的主要原因。
在这一过程中,质量因子的选取至为重要。值选得过大,可以大幅度提高压缩比,但是图像质量就比较差;反之,质量因子越小(最小为1),图像重建质量越好,但是压缩比越低。对此,ISO已经制定了一组供JPEG代码实现者使用的标准量化值。
右图的两个量化表的设计是根据由Lohscheller做的心理视觉实验来确定二维基函数的可见阈值。
编码
从前面过程我们可以看到,颜色转换完成到编码之前,图像并没有得到进一步的压缩,DCT变换和量化可以说是为编码阶段做准备。
编码采用两种机制:一是0值的行程长度编码;二是熵编码(EntropyCoding)。
之字形排序(Zig-zag ordering)
在JPEG中,采用曲徊序列,即以矩阵对角线的法线方向作“之”字排列矩阵中的元素。这样做的优点是使得靠近矩阵左上角、值比较大的元素排列在行程的前面,而行程的后面所排列的矩阵元素基本上为0值。
使用RLE对交流系数(AC)进行编码 行程长度编码是非常简单和常用的编码方式 ,在此不再赘述。
需要注意的是,AC系数的之字形序列编码中有两个特殊符号——(0,0)和(15,0)。第一个特殊符号指的是块的结束(end-of-block,EOB),用来表明在之字形块中剩余的元素都是零。另一个特殊符号是指零游程长度(zero-run-length,ZRL),用来表明16个零游程。基线JPEG允许的零游程最大长度是16个。如果这里的零超过16个,那么这个游程分成几个长度为16的零游程。 使用DPCM对直流系数(DC)进行编码
DCT系数量化之后,通过差分编码对量化后的DC系数编码。当前块的DC系数减去前个块的DC系数,然后对其差值进行编码,如右图所示。这就利用了邻接块DC值之间的空间相关性。 熵编码: 编码实际上是一种基于统计特性的编码方法。在JPEG中允许采用HUFFMAN编码或者算术编码。而基线JPEG算法(baseline sequential)采用的是前者。
经过RLE编码的AC系数可以映射成两个标志(RUNLENGTH,CATEGORY)和(AMPLITUDE),前者采用的是霍夫曼编码,而后者采用的是VLI编码。同理经过DPCM编码的DC系数同样可以映射成两个标志(CATEGORY)和(AMPLITUDE),前者采用霍夫曼编码,后者采用VLI编码。
基线JPEG允许使用4个霍夫曼表,两个用于AC系数编码,两个用于DC系数编码。


如何识别JEPG文件的

其实很简单,就是判断前面3个字节是什么,如果发现是FF D8 FF开始,那就认为它是JEPG图片。
JPG文件是由一段段的数据构成的组成的(segment),段的多少和长度并不是一定的。只要包含了足够的信息,该JPEG文件就能够被打开。
JPEG格式和标记
JPEG图片格式组成部分:SOI(文件头)+APP0(图像识别信息)+ DQT(定义量化表)+ SOF0(图像基本信息)+ DHT(定义Huffman表) + DRI(定义重新开始间隔)+ SOS(扫描行开始)+ EOI(文件尾)
以16进制模式打开JPG文件,就会发现
JPEG 文件中有一些 形如 0xFF** 这样的数据,它们被称为“标志(Marker)” ,它表示 JPEG 信息数据段。例如 0xFFD8 代表 SOI(Start of image), 0xFFD9 代表 EOI(End of image)。
标志 0xFFE0~0xFFEF 被称为 "Application Marker",它们不是解码 JPEG 文件必须的,可以被用来存储配置信息等。EXIF 也是利用这个标志段来插入信息的,具体来说,是 APP1(0xFFE1) Marker。所有的 EXIF 信息都存储在该数据段。 ------------------ 名称 标记码 说明 ------------------ SOI D8 文件头 EOI D9 文件尾 SOF0 C0 帧开始(标准 JPEG) SOF1 C1 同上 DHT C4 定义 Huffman 表(霍夫曼表) SOS DA 扫描行开始 DQT DB 定义量化表 DRI DD 定义重新开始间隔 APP0 E0 定义交换格式和图像识别信息 DNL DC 标记码 COM FE 注释
段类型有30种,但只有10种是必须被所有程序识别的,其它的类型都可以忽略。
JPEG format and Marker
SOI Marker Marker XX size=SSSS Marker YY size=TTTT SOS Marker size=UUUU Image stream EOI Marker
FFD8
FFXX
SSSS
DDDD......
FFYY
TTTT
DDDD......
FFDA
UUUU
DDDD....
I I I I....
FFD9
0xFFE0~0xFFEF之间的标记被叫做 "应用标记", 它们在JPEG图像解码中不是必须存在的. 它们被使用于用户的应用程序之中. 例如, 老款的olympus/canon/casio/agfa 数字相机使用 JFIF(JPEG文件交换格式/JPEG File Interchange Format)来存储图像. JFIF 使用 APP0(0xFFE0) 标记来插入数字相机的配置信息数据和缩略图.
Exif也使用应用标记来插入数据, 但是Exif 使用 APP1(0xFFE1)标记来避免与JFIF格式的 冲突. 且每一个 Exif 文件格式都开始于它, 如
SOI 标记 标记 XX 的大小=SSSS 标记 YY 的大小=TTTT SOS 标记 的大小=UUUU 图像数据流 EOI 标记
FFD8
FFXXlo0p
SSSS
DDDD......
FFYY
TTTT
DDDD......
FFDA
UUUU
DDDD....
I I I I....
FFD9
Exif也使用应用标记来插入数据, 但是Exif 使用 APP1(0xFFE1)标记来避免与JFIF格式的 冲突. 且每一个 Exif 文件格式都开始于它, 如;
Marker used by Exif
0xFF+Marker Number(1 byte)+Data size(2 bytes)+Data(n bytes)
SOI Marker FFD8
APP1 Marker FFE1
APP1 Data SSSS 457869660000 TTTT......
Other Marker FFXX SSSS DDDD......
该图像文件从SOI(0xFFD8) 标记开始, 因此它是一个 JPEG 文件. 后面马上跟着 APP1 标记. 而它的所有 Exif数据都被存储在 APP1 数据域中. 上面的 "SSSS" 这部分表示 APP1 数据域 (Exif data area)的大小. 请注意这里的大小 "SSSS" 包含描述符本身的大小.
在 "SSSS"后面, 是 APP1 的数据. 其中第一个部分是一个特殊的数据,它用来标识是否是 Exif, 其值是ASCII 字符 "Exif" 和 两个0x00字节 的组合字符串.
在 APP1 标记域的后面是, 跟随着其他的 JPEG 标记
exif数据解析
如果图片图片是16进制数据,如下:
FF D8 FF E0 00 10 4A 46 49 46 00 01 02 01 00 60 00 60 00 00 FF E1 08 32 45 78 69 66 00 00 49 49 10 60 00 60 20 00 …… FFD9
那么FF D8为SOI标志位,FF E0为exif文件起始位,后面四位 为exif marker信息的长度。取这个长度的数据解析为TIFFdata数据,exif直接解析为字符串貌似也没有问题。
FF D8
FF E0 00 10 4A 46 49 46 00 01 02 01 00 60 00 60 00 00 mark0,00 10 =16位
FF E1 08 32 45 78 69 66 00 00 49 49 10 60 00 60 20 00 …… mark1,00 10 =2098位
……
Image stream
FFD9

每个段都是由FFxx开头,其中xx是段的标识,接着就是就是两位的端长度。后面跟着的就是数据。前面的元数据外读取完成后,后面的二进制数据就是图片数据。 数据大小描述符(2个字节) 是 "Motorola" 的字节顺序, 数据的低位被存放在高地址,也就是 BigEndian. 请注意上面中的 "数据内容" 中包含他前面的数据大小描述符, 如果下面的是一个标记的话;
这个长度的表示方法是按照高位在前,低位在后的,与 Intel 的表示方法不同。比方说一个段的长度是0x12AB,那么它会按照0x12,0xAB的顺序存储。但是如果按照Intel的方式:高位在后,低位在前的方式会存储成0xAB,0x12,而这样的存储方法对于JPEG是不对的。这样的话如果一个程序不认识JPEG文件某个段,它就可以读取后两个字节,得到这个段的长度,并跳过忽略它。
关于exif信息解码,请阅读《 JPEG/Exif/TIFF格式解读(2):图片元数据保存及EXIF详解 》
jpeg10中必须的段类型
这里列举10种必备的段类型
APP0图像识别信息 ------------------------------------------------- 名称 字节数 值 说明 ------------------------------------------------- 段标识 1 FF 段类型 1 E0 段长度 2 0010 如果有RGB缩略图就=16+3n   (以下为段内容) 交换格式 5 4A46494600 “JFIF”的ASCII码 主版本号 1 次版本号 1 密度单位 1 0=无单位;1=点数/英寸;2=点数/厘米 X像素密度 2 水平方向的密度 Y像素密度 2 垂直方向的密度 缩略图X像素 1 缩略图水平像素数目 缩略图Y像素 1 缩略图垂直像素数目 (如果“缩略图X像素”和“缩略图Y像素”的值均>0,那么才有下面的数据) RGB缩略图 3×n n=缩略图像素总数=缩略图X像素×缩略图Y像素
说明: JFIF是JPEG File Interchange Forma的缩写,即JPEG文件交换格式,另外还有TIFF等格式,很少用 “如果有RGB缩略图就=16+3n”是什么意思呢?比如说“缩略图X像素”和“缩略图Y像素”的值均为48,就表示有一个48×48像素的缩略图(n=48×48),缩略图是24位真彩位图,用3个字节来表示一个像素,所以共占用3n个字节。但大多数JPG文件都没有这个“鸡肋”缩略图。
DQT定义量化表 -------------------------------------------------------------------------- 名称 字节数 值 说明 -------------------------------------------------------------------------- 段标识 1 FF 段类型 1 DB 段长度 2 43 其值=3+n(当只有一个QT时) (以下为段内容) QT信息 1 0-3位:QT号 4-7位:QT精度(0=8bit,1字节;否则=16bit,2字节) QT n n=64×QT精度的字节数
说明: JPEG文件一般有2个DQT段,为Y值(亮度)定义1个, 为C值(色度)定义1个。 一个DQT段可以包含多个QT, 每个都有自己的信息字节








参考资料:
图片文件Exif信息详细说明 blog.sina.com.cn/s/blog_651251e60102uz3d.html#AboutExif
图像Exif信息 元数据(Metadata) https://www.jianshu.com/p/a6d67df60e7e
关于图片文件旋转JPEG与EXIF信息 https://blog.csdn.net/yulimin/article/details/102827865
https://www.media.mit.edu/pia/Research/deepview/exif.html
https://baike.baidu.com/item/Exif/422825?fr=aladdin
读取JPG图片的Exif属性(一) - Exif信息简介 https://blog.csdn.net/fioletfly/article/details/53605959
读取JPG图片的Exif属性(二) - C代码实现 https://blog.csdn.net/fioletfly/article/details/54094940
读取JPG图片的Exif属性(三) - Exif属性读取GPS信息代码(C/C++实现) https://blog.csdn.net/fioletfly/article/details/54133422
在jpg图片添加Exif信息的C程序实现 https://blog.csdn.net/psy6653/article/details/79658144
JPEG添加EXIF https://blog.csdn.net/weixin_43549602/article/details/84654965
jpeg图片格式详解 https://blog.csdn.net/yun_hen/article/details/78135122
压缩算法——JPEG2000 编解码原理 https://blog.csdn.net/ytang_/article/details/76571635
PNG、JPEG、BMP等几种图片格式详解 https://www.jianshu.com/p/f5557c0e689e
使用HTTP2和渐进式JPEG图片更快的加载图像 https://www.zcfy.cc/article/performance-calendar-raquo-even-faster-images-using-http2-and-progressive-jpegs-2216.html
转载 本站 文章《 JPEG/Exif/TIFF格式解读(1):JEPG图片压缩与存储原理分析 》,
请注明出处: https://www.zhoulujun.cn/html/theory/multimedia/CG-CV-IP/8396.html
多媒体
2020-07-24 19:20:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
1.易查搜索( https://so.acurd.com/ )
推荐指数 *****+ 资源聚合做的比较好的一个网站,也是最常用的一个网站,据up主说有脚本会定时检测每个搜索引擎,不通的或者服务器挂的搜索引擎会自动下架,保证了高可用,这里感谢一下作者的良苦用心,加油!!
2.虫部落( http://magnet.chongbuluo.com/ )
推荐指数 ***** 和top1有一部分重合,可以解决你的大部分搜索需求,但也正因为太多了,所以有点良莠不齐。
3.云盘精灵 ( https://www.yunpanjingling.com/ ) 推荐指数 **** 由于是付费的,所以资源较多,而且都比较新,能够搜到大多数你想要的资源,推荐。
3.小可搜搜 ( https://www.xiaokesoso.com/ )
推荐指数 ***** 资源较多,而且免费,只不过每天限次数,可结合云盘精灵使用,推荐。
4.史莱姆搜索 ( http://www.slimego.cn/ )
推荐指数 ***** -搜索效果挺不错的,不过还是建议先使用云盘精灵和小可搜搜试试,这几个结合使用最佳。
5.其他
由于上面几个网站基本能弄到想要的资源,所以其他的没怎么用。另外,网盘搜索配合google搜索技巧使用效果更佳(比如搜索电子书一类的东西)。google搜索引擎使用方法参考 https://m.acurd.com/home/share/info/catId/23/id/1bc2h1oi77.html 更多内容请访问爱蜜桃
多媒体
2020-07-24 13:41:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
5G时代对于视频行业的发展和业务形态将是一个重要的助推,但5G时代带来的改变不只是带宽提升和延迟降低这两个最直接的因素。本次LiveVideoStackCon 2020线上峰会我们邀请到了阿里巴巴高级算法专家盛骁杰,他将介绍在5G时代,视频信息承载将逐步从目前的平面视频形态过渡到信息表达更为丰富的三维视频,而这种过渡中包含的可能性一方面需要我们充分利用5G技术本身的云、边、端架构带来的技术红利,另一方面,也需要我们对于快速发展的三维视觉,AI,AR等技术进行综合性的创新运用,使得视频体验通过可互动的三维信息表达在5G时代不断给用户创造出更多的价值。

文 / 盛骁杰
整理 / LiveVideoStack

大家好,本次我分享的主题是5G时代的互动视频——探索从平面到三维视频信息表达的新可能。


本次演讲的内容将分为四个部分,分别为:什么是互动6DoF视频、视频体验的发展和5G时代的新机遇、6DoF视频的标准(MPEG/AVS)和对互动6DoF视频的未来展望。
什么是互动6DoF视频


首先通过三个演示视频来认识互动6DoF视频。第一个视频是CBA的运动场景,它是通过相机阵列和三维重建所得到的一个互动6DoF视频,并且通过实际的网络传输以及用户终端的实时互动所展现出来的一个效果。
第二个视频也是相同的场景,但对原先的互动方式进行了一些调整和改变,在这样的视频中,用户触摸屏幕会使整体场景静止,用户可以有一个静态场景下360°的对场景每一个瞬间进行精细化观看的互动体验,这样的体验特别适合于体育等竞技性和专业性较强的场景。

首先,通过对这个视频的体验,可以感受到之前普通的平面体育点播和直播视频当中所没有的新体验。

其次,在一个三维的空间中对传统体育赛事的观看方式进行革新,使得用户能够自由地切换自己的视点,观看其感兴趣的区域。通过这种方式,用户会感觉到视频的观看过程通过可互动的方式,已经和某些游戏的体验有相似性。从来没有体验过这种视频的用户,在第一次体验之后会感觉这种体验很像游戏。之所以有这种感受,是因为它其中包含了3D和互动的体验,而普通的游戏给予用户的正是这样一种体验。这也是互动6DoF视频和平面视频最大的区别, 即互动6DoF视频有着和平面视频不同的三大特点:互动性、三维立体感和场景沉浸感。


互动6DoF视频除了应用在体育的场景当中,在综艺场景中也有着广泛的应用前景。以上展示的是一个综艺场景的效果,视频中的整个综艺场景,通过三维的建模并且通过传输和实时互动,使用户产生很强的现场沉浸感和立体感体验效果。此外,在综艺场景中它可以给予用户一种全方位的信息接受方式。比如这个视频演示是优酷《这就是街舞2》决赛当中的一段视频,我们的交互范围是可以覆盖整体场景正面的180°,基本上可以囊括正面的所有舞蹈视角,通过这种方式的交互,用户可以很方便的把握住舞台的整体,体验到每个舞者在跳舞过程中全方位的信息。今年在优酷的《这就是街舞3》节目中,我们会在优酷的APP端正式上线这样的体验。
这种体验在街舞当中或者其他技巧类、动作类的综艺当中都有很好的应用前景,在整个互动效果当中,它的交互是非常流畅的,用户手指的滑动、画面的重建和渲染都可以达到实时的效果。而且现在手机终端的运算能力越来越强,已经可以使这样的形态让用户在大部分的手机上体验到。

以上的三个例子是通过具体的呈现方式使大家对互动6DoF视频有一个感性的认识。下面我们再来详细介绍下什么是互动6DoF视频。


普通的平面视频从视频的采集、处理、编码、传输、观看的整个过程中都是单向的传输链路,所以平面视频是一种被动式的观看,这是因为用户最终接收到的信息是完全被动式的接收导演想传递给用户的信息,用户不能主动选择想看的部分,这是一种被动式的观影体验。并且,平面视频每一级都是单向链路,就是采集完的信号会给下一级进行视频处理,经过编码模块后进行视频传输到用户端播放,每一级都是一个向后单向传递的处理链路。

互动6DoF视频在平面视频的基础上引入了一种交互式的观看方式,正如前面三个视频的例子就具有很强的交互性。它的交互性来源不只是在终端得到的交互,在视频的采集、内容表达和编码三个方面都和传统的平面视频有很大的区别,在这几个模块中都需要对用户最终的交互形态有一个综合性的考虑和设计。也就是说我们需要事先考虑给用户怎么样的交互形态和交互范围,然后倒推回去设计视频的采集方案、视频的内容表达方案和编码方案。所以它是一种非单向式的视频处理链路,在6DoF视频采集的时候我们就要在方案当中充分考虑到用户的交互行为。

这是我们的技术人员第一次通过这种6DoF交互式的观看获得一种手段,即他能够通过技术去影响和改变上游视频的传统采集和表达方式。而在普通的平面视频的观看当中,技术人员无法影响上游的表达,只是被动的接收上游导播给到的信号,并把它呈现出来。但是互动6DoF视频交互式观看给了技术人员一种有力的工具,就是可以通过提供用户不同的交互形态去影响上游的视频采集、表达、编码的方案。

1.1 采集


这部分我们来介绍一下互动6DoF视频在采集、表达和编码方面与平面视频的一些不同点。
首先在采集方面,上图中四个例子是业界不同的公司对6DoF视频的采集方案在不同场景中的应用,从中可以看出对于不同的场景,6DoF技术可以去设计不同的采集方案,提供给用户不同的体验。从这四张图中也可以看出,虽然它们适用的范围和场景都不一样,但是它们都是通过相机阵列进行采集,而不是局限于单个相机进行采集,这也是三维视频的一个特点。第一张图中是在体育馆中有一个相机阵列,第二张图是在摄影棚中部署了更多的相机集群,第三张图是围绕体育场的一圈360度进行拍摄,第四张图是专业的物体建模采集工作室。这样做的特点就是通过多相机进行采集以后,能够对场景进行三维重建和表达,再通过压缩,最终传递给用户一种6DoF互动的视频体验。


在之前演示的CBA场景当中,我们的采集方案就是相机阵列的采集,以上的两个图片是在CBA广东队的主场拍到的效果,整个采集方式是通过相机阵列进行的采集,然后进行空间的三维重建并进行视频的传输,最后给用户一个互动的体验。

我们可以看到,6DoF视频的采集具有几个特点:一是需要多相机的采集阵列;二是根据不同场景的应用有一定的灵活性,针对不同的场景我们需要有不同的采集方案和表达方案,在用户端也就会有不同的交互体验。

1.2 表达


第二个重要部分就是6DoF视频的表达。6DoF视频和平面视频是完全不一样的,现在常见的三种视频格式分别是:立体视频、平面视频、VR 360视频。立体视频是基于双目相机拍摄以供用户立体观看的表达。平面视频就是一个2D视频,它是一种不依赖于任何特殊观看设备的表达。VR 360视频给用户提供的是一种现场沉浸式的体验,但它往往依赖于特殊的设备,所以这三种已知的视频表达方式,它们有各自的优缺点。

6DoF视频表达是融合了以上三个视频表达方式的优点而得到的一种新的视频表达方式,它的“新”主要表现在以下几点:

首先,6DoF视频和平面视频一样,不依赖于特殊的观看设备。从之前的例子看出,在手机端上用户就可以做便捷的交互。同时,它也具有VR 360视频所提供的现场沉浸感体验。其次,它还可以基于三维重建技术给用户提供立体感的体验。所以6DoF视频是集中了这三个视频表达方式的特点最终形成它自己的特殊表达,它所展现的形式和游戏体验很像,这是因为游戏体验也在平面显示器上,能够给人带来沉浸感、交互性和立体感。

从技术层面来看,6DoF视频有如下几种不同的表达方式:

第一种表达方式是基于深度的表达,就是对每一个拍摄的像素进行深度的重建,我们有它的深度图,通过纹理图和深度图两两成对的表达方式,再加上Multiview的视点,形成一种基于深度的表达。这种表达方式的优点是完全基于图像重建的表达方式,无论是纹理图和深度图都可以编码成图像,具有成熟的视频压缩标准来配合,在工业化和产品化下的支撑具有明显优势,而且在基于图像重建的效果自然性上更能让用户接受。

第二种表达方式是基于点云的表达,其实它是基于图形的重建,Point Cloud会把空间所有像素点的三维位置表达出来,并且通过纹理的贴图形成一个真实的三维模型。但经过点云的渲染也有它特别的地方,因为是基于Graphic,所以它的渲染不会非常自然。其次,虽然MPEG和AVS也在探索一些基于点云的压缩方案,但点云目前还没有一个成熟的压缩标准去支持,包括解码也没有成熟的终端硬件支持。所以总结来说,目前点云表达方式的优点在于,由于它是一种空间完全的三维重建,所以具有最大的交互自由度,可以把空间中一个全视角的三维模型重建出来,但缺点是它在压缩传输和渲染自然性上不如深度重建。

最后一种表达方式是光场,也是数据量最大的表达方式。它是通过密集的相机采集阵列或者光场相机所采集到的空间非常密集的视点表达,可以提供完全的6DoF体验。因为光场是一种非常稠密的视点表达,所以它的数据量过大,目前还缺乏成熟的压缩标准支持,还不具有工业成熟性。

1.3 编码


图中展示的内容是基于深度图表达的6DoF视频编码框架。首先是基于多相机采集的纹理图,同时配备了和多相机采集纹理图所对应的深度图,还有基于多相机采集的纹理图和深度图的元数据。元数据中包含了一些多相机的摄像机参数信息以及相机纹理图和深度图的拼接和表达的配置信息。有了多相机采集的纹理图、深度图和元数据以后,我们就得到了一个基于深度的完整6DoF视频表达。

在此基础上就可以通过视频的编码方式(比如视频的编解码标准:AVS3、H.264、HEVC),对于前面所述的6DoF视频表达进行编码之后传到终端进行解码,然后基于多相机纹理图、深度图和元数据进行6DoF视频重建,也就是在终端对用户的虚拟视点进行重建,最后把重建完的图像进行终端渲染输出。上述就是整个6DoF视频编码框架和解码后呈现给用户的方案。
视频体验的发展和5G的新机遇
2.1 平面视频


这部分回顾了视频体验的发展过程以及解释一下为什么说6DoF视频是5G时代新机遇的原因。大家对于视频体验的发展,最耳熟能详的就是平面视频,视频介质中数量最多的也是平面视频,平面视频的技术发展目前还在一直向前推进。总体来看,平面视频的视频体验的发展主要分为三个方面:

第一方面是平面视频分辨率的发展。平面视频从原先的540p逐步发展到1080p甚至8k,显示屏幕也越来越大。

第二方面是亮度和色域的发展。由中间这张图可知,原先的色彩表达空间比较小,因为人眼的整个色彩感受空间是非常广泛的,但对于SDR是怕来说,它只能表达其中的一小块,但HDR可以表达更大的色彩空间,所以可以让用户感受到除了分辨率的提升以外,人眼所感受到的色彩丰富度也越来越多。

第三方面是帧率的变化。原先大部分的视频都是25FPS或者30FPS,现在由于整体视频显示设备刷新率的增强,比如现在很多手机都已经支持120FPS的刷新率,那么视频的帧率也需要提高,才会给用户丝滑流畅的感受。当前帧率的发展已经逐步从30FPS过渡到60FPS甚至到120FPS。

配合分辨率、亮度和色域、帧率这三个方面平面视频的技术发展,平面视频的压缩方法也在不断地迭代。右图当中可以看到,平面视频的压缩标准基本上是以十年为一代往前推进。从2003年的H.264是为1080p视频做准备的压缩标准,到2013年的H.265是为4K视频做准备的压缩标准,再到2020年的H.266也就是VVC是为8K视频做准备的。

国内的视频编解码标准H.264、H.265和H.266相对的是AVS+、AVS2和AVS3,它们分别是为1080P、4K和8K分辨率准备的视频编解码技术。

2.2 立体视频


除了平面视频以外,另外一种视频的表达方式是立体视频。立体视频是通过双目相机进行拍摄,使用户戴上眼镜后对视频产生有深度的立体感体验。

早在2009年,电影“阿凡达”就引入了全新的立体视频观影体验,把这种视频的方式带到影院当中。

立体视频技术推动了视频采集、视频制作、3D视频压缩标准、视频观看设备的全面革新,比如在视频采集方面使用的不再是普通的相机而是双目立体相机。

在2010年电视机厂商提出了一种新的视频观影体验——FTV(Freeview TV)也叫自由视点的电视。它是在立体视频的基础上进一步的发展,用户可以在屏幕前面,通过一定范围的移动看到视频当中的立体视频。它不但是3D的,而且还能进行一定自由度的移动观看。根据用户的左右移动,电视机里面的内容也会发生变化,它是根据人在电视机前的移动,通过不同的光栅信号折射使人眼看到不同的视角。

但FTV出来之后并没有非常火,首先是基于光栅的电视成本非常的高,其次,它的体验方式并不是非常自然。同时,它提供的自由视角也比较小,因为FTV内容源并不是通过相机阵列进行采集的,而是基于普通的双目相机采集,并对双目相机的左右两个视点进行一定的外插值,相当于做一些假的虚拟视点。但这个范围不会特别大,因为它原先采集的素材就只有左右两个视点,所以没有办法得到更大范围的自由度。基于这几点原因,FTV虽然提出一个非常好的概念,能够让用户有自由视点的电视体验,但是最终并未在市场上火起来,而且在后期逐步的销声匿迹了。

2.3 VR 360视频


VR 360视频是在2016年火起来的,它进一步带来了360度沉浸式的视频体验,并引入了3DoF观看体验。VR 360视频的采集端通过多个鱼眼相机的采集以及拼接得到一个360度的视频,如果用户戴上头盔可以通过转动到任意角度进行观看。我们来解释一下为什么说VR 360视频是3DoF的?右图显示了人在真实空间中的六个自由度的表现,六个自由度是指在空间上有X、Y、Z这三个移动的自由度,以及在这三个移动自由度基础上的三个旋转的自由度,加起来一共是六个自由度,这是人在真实空间中所具有的自由度空间。3DoF视频只有三个旋转的自由度,没有三个移动的自由度。当戴上眼罩观看VR 360视频时,只能进行旋转观看但不能移动,因为前后走动并不影响看到的内容,所以说VR 360视频是3DoF的视频。

2.4 6DoF视频


人对真实世界的视觉感知同时具备了沉浸感、立体感和空间感,这三者其实是统一的。
左下角的视频是Facebook展示的一个立体视频,这是通过一种基于深度的渲染技术得到的视频,虽然用户不需要带任何的3D眼镜就可以进行观看,但用户同样可以感知到非常强烈的3D感和沉浸感。这是因为人对于空间的沉浸感、立体感和空间感的感知是一起的,所以对于视频中的内容稍微发生变化,就感觉是3D的,而且沉浸感也会加强。

基于这种原理,6DoF视频也有类似的特点。6DoF视频就是普通平面观影设备上的一个交互式立体视频,可以带给用户沉浸式的体验,而且交互更自然。6DoF视频就是在左下角视频的基础上发展了一大步,能够在一个非常大的范围之内让用户体验交互、沉浸和3D的感觉。

6DoF视频解决了前面提到的立体视频、VR 360视频遇到的两个核心问题:一是它的内容。立体视频、VR 360视频虽然是两种新的内容表达方式,但在它的制作侧、内容的丰富程度是不能和平面视频相比的,立体视频、VR 360视频的数量是非常少的。

二是它的交互不自然。所谓交互不自然是指在用户侧,立体视频需要用户戴眼镜,VR视频需要用户戴头盔,这和交互的自然性是相违背的。由于这两个原因,立体视频和VR 360视频始终没有火起来。6DoF视频在这两点上具有天然的优势,它在内容丰富性上,无论是体育还是综艺都有很强的需求。其次,在交互自然性上,用户不需要任何的特殊设备,在手机屏幕上就能进行交互,这极大的改善了立体视频和VR 360视频遇到的两个核心难点。

从右边一组图中可以看出关于6DoF视频在 MPEG所定义的发展趋势。3DoF是模拟一个人坐在椅子上,只能有旋转的自由度,不能有空间移动的自由度。3DoF+是在三个旋转的自由度的基础上,向用户提供了前倾、左倾、右倾、后倾等受约束的空间自由度。发展到我们目前处于的位置是Windowed 6DoF,它给了用户一定的旋转自由度,模拟用户在一个窗前进行观看,往前往后自由度是受限的,而且旋转自由度在某种程度上也是受限的,给用户的感觉是在一个窗前面自由移动看窗外变化的景色。最后是完全的6DoF,用户在任意的六个自由度上都不会受到约束,可以任意进行前后移动和旋转,这是视频体验的终极发展方向。


现在的6DoF视频可以在移动端打破移动分辨率显示的局限,原先移动端的分辨率达到1080p以上时,在移动端人眼视场角的像素数量已经接近上限,分辨率再增加人眼已经感受不到清晰度的增强了。但如果分辨率可以进一步上升,比如5G可以传4K、8K甚至16K的视频,那么在这个基础上,6DoF视频提供了一种新的可能性,就是把空间信息量的提升转化为交互性和自由度的提升,它为移动端的视频体验装上一个新的引擎,能够在5G时代沿着全新的引擎进一步提升用户的体验。

在5G时代,带宽、解码能力和三维重建技术这三个条件都会取得长足的发展。比如5G时代的传输带宽可以非常轻易的达到下行100M左右,这在4G时代是无法想象的。另外5G手机越来越多的支持8K的解码,8K解码让我们有了传输6DoF视频的基础。最后基于AI技术的三维重建也使得基于6DoF视频的三维重建的质量和速度得到大幅度的提升,所以基于5G的技术,6DoF视频可以有非常好的基础设施,在此基础上能够为用户提供更好的观看体验。
6DoF视频的标准
3.1 MPEG


这部分我们来介绍一下MPEG标准组是怎么对6DoF视频的标准来进行定义的。

这是一张最新的2020年MPEG路线图,根据前文所提到的3DoF、3DoF+以及6DoF的发展,目前MPEG已经完成了普通VR 360视频标准的建设,接下来它会沿着3DoF+发展,3DoF+还在建设当中并且预计会在2021完成标准建设,之后会进入6DoF的发展阶段,6DoF的发展会分为两个阶段:Windowed 6DoF阶段和完全6DoF阶段,整个6DoF标准的发展会一直延续到2023年甚至更远。所以MPEG在6DoF视频上规划了非常长的路线图,伴随5G网络的普及而发展,一直到2023年左右才会过渡到真正的6DoF视频标准。

3.2 AVS


现在国内像阿里正在参与的AVS标准已经在整体的6DoF标准建设上走在了MPEG的前面,我们已经过渡到了Windowed 6DoF标准,而MPEG现在还在建设3DoF+的标准,所以说从标准的路线图上,我们现在是跑在MPEG前面。

我们来看一下AVS标准目前的进展:首先它的标准文档已经形成了草案的5.0版本,并且标准组计划在2020年底将AVS 6DoF标准推进到FCD阶段。

在标准测试用例方面,我们已经有了六段标准的测试用例,每段包括30个以上的相机纹理图和深度图,所以我们的标准测试用例是比较丰富的,并且相机阵列的数目也是比较多的。其次,在参考软件方面,我们提供了视点重建参考软件,这个参考软件目前已经更新到VSS 3.0版本,所以基于标准测试用例和参考软件,参与标准的一些公司和学校就能很快参与到6DoF视频标准的实验、算法开发和提案工作当中。
互动6DoF视频的未来展望


6DoF视频未来可提升的空间也是非常大的,它的提升空间可以分为两个部分:

第一是基础体验提升。基础体验提升意味着三个方面:首先是6DoF视频时效性的提升,比如我们现在做6DoF视频的点播,下一步可能要做6DoF视频的直播。其次是自由度的提升,也就是从Windowed 6DoF发展到完全的6DoF。最后是清晰度的提升,用户看到的任意视点的清晰度是否可以更高,比如达到1080p甚至以上。

第二是互动体验的提升。这意味着6DoF视频是有和其他周边的技术进行融合的可能,因为6DoF视频本身是一个完全三维立体化的视频,基于这个三维立体化的视频,我们可以把原先很多的算法在三维的场景上进行组合。比如AR原先是在平面的视频上,如果它基于6DoF视频可以有更好的AR展现效果,包括AI原先也是主要用在平面视频中,在立体的空间中,AI算法能够有更好的发挥的余地和想象空间。

最后我们通过一段demo视频来演示一下上述的概念,首先6DoF视频是在一个完全三维的场景下,在此基础上,我们基于AR技术进行数据的呈现,整体三维场景中AR的贴图,包括平面检测都是在三维场景下发生的,这是普通2D视频中的AR所不能展现的效果。

图中也显示了AI技术和6DoF的结合,在终端交互过程中可以对6DoF视频里的人物进行点击,通过AI技术在多相机的阵列的图像中对人进行识别和跟踪,相比通过单个相机进行识别跟踪,它的检测率会更高。

这样的体验可以使6DoF视频在未来成为接近完全游戏化的体验方式,用户在观看一个篮球比赛时可以有很强的交互性、沉浸感以及和AR, AI技术的有机结合,这是对6DoF视频未来能够做到的体验上限的展望。迈入5G时代,我们对于6DoF的基础体验和互动体验的提升还有很长的路要走。
多媒体
2020-07-23 10:50:00
「深度学习福利」大神带你进阶工程师,立即查看>>> “视频会议是一个不断创新发展的产品,随着新的技术出现在不断演进和迭代,不断给用户带来惊喜。” 在LiveVideoStackCon 2020 线上峰会的主题演讲中,拍乐云创始人&CEO赵加雨分享了视频会议技术多年来的发展与演进,并畅想未来视频会议产品可能存在的进一步应用与创新。

Hi,大家好,我是赵加雨,2003年加入Cisco WebEx,是WebEx美国架构师,负责WebEx技术架构的演进。2017年回国加入网易,去年出来创业,创立了拍乐云Pano(Pano是全景的意思,音译成中文是“拍乐云”)。作为一名WebEx的老兵,也非常了解Zoom的成长经历,多年来也一直在参与视频会议的产品研发工作。

Zoom股市走势图
疫情让视频会议站到了C位,Zoom的用量和股价双双暴涨,让很多人看到了视频会议的价值,实际上 视频会议并不是一个很新的产品形态,WebEx是最早的基于互联网的视频会议产品,20年前就已经出现了,但是视频会议又是一个不断创新发展的产品,随着新技术的出现也在不断演进和迭代,不断给用户带来惊喜, 这里我套用一个春晚小品的名字,也聊一聊视频会议的“昨天,今天和明天”。作为一名程序员,我分别把他们称为Meeting 1.0、Meeting 2.0和Meeting 3.0。
在聊视频会议之前,我们先简单理解一下视频会议,视频会议技术涵盖了非常多的音视频技术方向,包括音视频编解码、网络传输、弱网对抗与QoE、音频3A、高并发的服务端分发等等,所有这些都需要在极低的时延下解决好,实时这一限定条件极大地提升了视频会议的技术门槛,因此能够做好视频会议的团队和公司并不是很多。
01 视频会议1.0时代

视频会议1.0的代表公司毫无疑问是WebEx,WebEx在2000年上市,和Salesforce是最早的两家SaaS上市公司,在07年被Cisco收购。同时期的产品还有Citrix的GotoMeeting、微软的Live Meeting等,但是WebEx是绝对的领导者,市场占有率一直在50%以上。
WebEx所处的年代属于PC互联网时代,那时候的上网方式基本上还是拨号上网和有线宽带,对应的网速从今天的这个标准来看还是非常的慢,当时的网速差不多是128K~4M。上网的方式还是主要以电脑为主,是一个Wintel的时代,也就是Windows+PC的一个时代,主流的浏览器是IE,当时的时代背景就决定了视频会议的这样一个产品想要达到较好的效果,就需要付出非常多的工作,这是一个相对受限的时代背景,但即使在这样一个时代背景之下,WebEx还是做了非常多的创新。

WebEx创新点
WebEx定义了云视频会议的产品形态, 在WebEx之前可能用的更多的是电话会议,没有文档共享,沟通的效率就会差很多,要么就是直接出差。WebEx里边的很多功能都变成了云视频会议的标配,比如共享、音视频、聊天,跟传统视频会议、电话会议的打通。这些功能点也都已经变成了现在各种视频会议产品的标配。有意思的,WebEx当时一般是被称为Web Conference。
02 视频会议2.0时代

视频会议2.0的代表公司就是Zoom,这是由一群WebEx的同学创立的一个公司,诞生在移动互联网时代,利用新时代的技术环境,包括用户习惯的一些改变,再次推动了视频会议的发展。WebEx里边有的功能在Zoom里边都是有的,Zoom用自己的方式进一步进行了打磨和提升,在很多细节的地方和易用性方面做了很多的提升,除了已有的功能点的打磨,Zoom在这几个方面也做了很多的创新。

Zoom创新点
相比WebEx被称为Web Conference,Zoom被称为Video Conference,从这两个名字的不同也能看出这两个产品的不同。
03 视频会议3.0时代

我们现在还处在Meeting 2.0的时代,但是我们不妨来畅想一下未来,在未来,视频会议是什么样子的。
未来是一个物联网时代,万物互联的时代,接入网络方式可能是5G、WIFI 6,这两个技术也在逐渐普及,5G也已经在进行商用及推广,也许再经过一两年就能变得非常的普及,这带来的一个效果就是网速进一步提升,未来的网速可能是在百兆以上,随着5G推进,我个人也非常看好一些新的产品、技术,其中比较重要的就是AR/VR,可能在未来5G的时代AR/VR会变得更加成熟,会走入更多的场景。
在这个历史背景下,我们从两个维度畅想一下:
RTC: 一个就是RTC本身,在畅想未来的新的视频会议形态的时候我们也不想局限于视频会议这么一个SaaS产品形态本身,视频会议本质上是一个RTC。
RTC+ : 除了RTC本身的的发展和创新,RTC也可能跟其他的技术做一些结合,带来更多的应用体验。
RTC
Codec
RTC的视频的编码,一定会有一个发展,我们知道H264第一版标准的最终草案完成于2003年5月,到现在也已经有十七年了;H265因为版权的原因并没有得到一个广泛的应用,我们预期未来的新的编码标准可能会比较快的走入RTC的应用场景,像H266,包括AV1等等。
超高清视频
随着更大的屏幕的出现,超高清视频也可能会变得更加普及,伴随着网络不是问题超高清视频也会变得更加常见。
拥塞控制
作为RTC很重要的一块,拥塞控制也在一直持续发展,应该还是会有更多的创新。
网络分发
像边缘计算、SD-WAN包括CDN,这些技术可能都会影响到RTC的网络分发。
WebRTC
目前来说主流的浏览器都支持WebRTC了,在meeting 1.0和meeting 2.0的时候无论是WebEx还是Zoom,主流的会议的端还都是native的端,随着WebRTC在各个主流浏览器上的成熟和普及,也许Web端会变成一个很重要的视频会议的端,会变成一个很重要的RTC的一个端。
RTC+
RTC+AI
AI在音视频领域已经被广泛应用了,比如说像语音识别、语音合成、图像识别、感知编码、超分、虚拟背景、降噪等等,这几年AI的发展相对来说比较成熟的应用场景也还是在和图像、语音相关的领域。在未来,我们预期RTC+AI还会有更多的应用场景的出现,会有更多的创新的出现,包括在3A算法、网络的拥塞控制、图像的编解码等各种场景下还会有进一步的创新。
RTC+Thing
Thing指物联网时代的Thing,我们预期在未来的5G时代,在万物互联的时代,RTC也会跟这些Thing会有一些结合,就是说接入物联网的这些设备可能也会拥有RTC的能力,当然也不一定是视频通话,但是可能有一些实时通信的一些技术的应用。
RTC+Screen
在生活当中其实有各种各样的屏,这些屏也会越来越多,像我们的智能手表、智能眼镜、智能手机、智能电视、智慧屏、汽车上的一些大屏,已经非常常见,在生活当中各种产品都有这样的一个智能屏,所有这些智能屏的地方,我个人的畅想,也许未来也会有一些结合到RTC的能力,这样子的话只要有屏的地方,也许都可以有RTC,甚至像小区里边的监控、家里的智能门铃,这些场景都可能和RTC结合。
RTC+Scenarios
RTC和各种场景的融合,从这次疫情我们也能看到这样趋势,随着这些基础设施的一些改善,包括5G的建设,更多的场景都可能和RTC有一个融合,包括像云游戏,随着5G和低时延的到来,云游戏可能会变得更加可行,包括像云办公、云教育、云学习,各种场景都可能和RTC会有一个融合,就是说RTC会走入我们日常生活中的各种场景,而不仅是局限于视频会议。

以上是我自己畅想的视频通讯的技术在未来可能会有的一些创新点,以我自己在美国生活几年的经验,包括在中国的生活经验和观察,我有一个个人的预判,或者说是美好的祝福,Meeting3.0产品和技术的创新更多地会出现在中国。这是因为中国的消费端更加拥抱创新;中国的生产端,也就是企业端也能够有很好的创新机制和文化。
作为RTC一直以来的从业者,我期望能够参与、促进这样一个创新的出现,和其他RTC从业者一起推动RTC在中国更多产品形态的创新。
多媒体
2020-07-21 14:40:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
  每个人都有自己的审美标准,社会上的各行各业也都有一套自己的评判标准。很多UI新手在刚入行的时候,都会担心自己审美能力不够,界面设计能力不行,有些人甚至连基础元素的运用都有问题。那么,我们该设计师如何提高自己的界面设计能力,从而使自己的设计水平有所提升呢?
  一、海量看图,提高鉴赏能力
  平时有事没事,一定要多看图,提高自己的鉴赏能力。一位优秀的设计师一定看过上万张以上的作品。海量看图,不是走马观花,而是要带着思考去看图。看图的时候,要学会分析,整张图片运用了什么元素,设计师的创作思路和手法是什么,如果换成我自己,我能不能做成这样或者有新的思路去开拓。另外,我们也要时时去回顾自己以往的作品,与现在的作品进行对比,看看自己的设计水平有无提高。
  二、模仿与创新
  这里所说的“模仿”不是指全盘照搬,而是模仿其界面的精髓,分析它的画面结构、色彩搭配,思考一下为什么要这样组合。如果想提高自己的界面设计能力,则还需要对主题进行重新设计。抛开固有风格、元素,在保持主题不变的情况下,自己重新创作设计,优秀的点我们可以转化在自己的作品中,并加上自己的创意元素。
  三、专业能力与知识拓展
  如果说一名优秀的UI设计师,审美能力是软实力的话,那么专业能力就是硬实力了。即使一张小小的名片设计,也包含着你的审美、排版、创意等意识,也非常考验专业功底。当你的设计越做越多、越来越得心应手的时候,你需要把以前很零散的东西融会贯通,组合成一个整体,既要有全局思维,也要有局部细节的处理能力,你需要通过不断的学习来提高自己的专业技能,为自己创造价值,提升自身的市场竞争力。
  同时也要做好设计以外知识的积累和拓展,如社交沟通、心理分析、谈判技巧等。因为在实际工作中,UI设计师不仅要了解客户需求,还要分析项目整体,不仅要让客户认可你的专业度,还要让客户认可你的为人。
  天道酬勤,要想提高自己的界面设计能力,还是需要你脚踏实地,不断练习。多看些优秀的设计作品,多临摹多钻研。提高自己的专业能力,使自己在做界面设计的时候更得心应手。 郑州同济不孕不育医院:http://jbk.39.net/yiyuanzaixian/zztjyy/郑州男妇科医院在线咨询:http://news.39.net/ylzx/zztjyy/郑州妇科医院那里好:http://jbk.39.net/yiyuanzaixian/sysdfkyy/
多媒体
2020-07-17 11:04:00