利用OpenCV实现抖音最强变脸术 | CSDN原力计划
作者 | 亓斌
来源 | CSDN原力计划获奖作品
(*点击阅读原文,查看作者更多文章)
最近一个“最强变脸术”又火爆抖音啦,还不知道的朋友建议先打开抖音,搜索“最强变脸术”看个十来个视频再回来看这篇文章。视频看起来炫酷,其实本质就是图像的各种变换组合到一块的结果。那我们能不能也搞出一个来玩玩?我利用周末刷了两天抖音,不停的暂停、继续… 最终在尝试了仿射变换和透视变换两种方案后,搞出了一个“低配版最强变脸术”。首先先来看看最终实现的效果(忽略gif颜色问题),也可以到http://www.iqiyi.com/w_19saz1z92h.html查看完整视频,然后从数学原理、opencv代码实现入手一步步的搞一个“最强变脸术”。
人脸关键点识别
看过“最强变脸术”的都知道,这个效果最基础的技术就是人脸识别。都2020年了,人脸识别当然不是多难的事了,可以选择的技术也很多,比如可以利用深度学习自己训练一个,也可以和我一样使用dlib这个三方库。
dlib用起来很简单,下面直接上代码了。
1img = cv2.imread("./imgs/2.jpg")2dets = detector(img, 1)34shape = predictor(img, dets[0])5landmarks = []6for p in shape.parts():7 landmarks.append(np.array([p.x, p.y]))89for idx, point in enumerate(landmarks):
10 cv2.putText(img, str(idx), (point[0], point[1]), fontFace=cv2.FONT_HERSHEY_SCRIPT_SIMPLEX,
11 fontScale=0.3, color=(0, 255, 0))
12cv2.imshow("--", img)
13cv2.waitKey()
运行上面的代码可以看到这样的结果:
请注意上面图中36、45、29三个数字的位置,因为在下面仿射变换的版本中我们要用到。
版本一:仿射变换实现
人脸关键点搞定后的第一次尝试,我是用的图像仿射变换来实现的。通过不断观察,我拆解出了一下三种变换方式:
平移
缩放
旋转
平移
需要平移,是因为我们需要把两张图片上的人脸叠放到一块。平移的变换操作矩阵是:
例如我们要向右平移100个像素,向下平移50个像素,那么变换矩阵就应该是:
对应的运算是:
即
所以平移操作的本质就是对每个像素加上一个偏移量。下面是使用opencv对图像进行平移操作的代码:
1img = cv2.imread("./imgs/2.jpg")2M = np.float32(3 [4 [1, 0, 100],5 [0, 1, 50]6 ]7)89dst = cv2.warpAffine(img, M, (img.shape[1], img.shape[0]))
10cv2.imshow("", dst)
11cv2.waitKey()
运行上面的代码可以看到这样的结果:
缩放
需要缩放,是因为我们在人脸对齐的时候需要尽可能的保证两张人脸大小一致。缩放的变换操作矩阵是:
fx代表x方向的缩放因子,fy代表y方向的缩放因子。所以如果我们想x轴放大1.5倍,y轴放大2倍的代码如下:
1img = cv2.imread("./imgs/2.jpg")2M = np.float32(3 [4 [1.5, 0, 0],5 [0, 2, 0]6 ]7)89dst = cv2.warpAffine(img, M, (img.shape[1], img.shape[0]))
10cv2.imshow("", dst)
11cv2.waitKey()
运行上面的代码可以看到这样的结果:
旋转
需要旋转,是因为我们需要把两张图片上的人脸进行对齐操作。旋转的变换操作矩阵是:
如果我们想要旋转30度,可以使用一下代码:
1img = cv2.imread("./imgs/2.jpg")23theta = math.radians(-30)4M = np.float32(5 [6 [np.cos(theta), -np.sin(theta), 0],7 [np.sin(theta), np.cos(theta), 0]8 ]9)
10
11dst = cv2.warpAffine(img, M, (img.shape[1], img.shape[0]))
12cv2.imshow("", dst)
13cv2.waitKey()
运行效果如下:
观察结果可能发现了,这次旋转的中心是在原点,如果我们想以任意点为旋转中心怎么办? opencv提供了一个函数:
1getRotationMatrix2D(center, angle, scale)
center: 指定旋转的中心
angle: 旋转角度
scale: 缩放因子
这个函数还顺手解决了我们上面需要的缩放操作。可以比较下面代码和上面的效果:
1img = cv2.imread("./imgs/2.jpg")
2
3M = cv2.getRotationMatrix2D((img.shape[1], img.shape[0]), 30, 1)
4
5dst = cv2.warpAffine(img, M, (img.shape[1], img.shape[0]))
6cv2.imshow("", dst)
7cv2.waitKey()
最强变脸术第一次实现
仿射变换版本其实就是利用了以上三种变换方式的组合,首先先定义一个函数入口。
1def compose_img(name, frames_per_transformer, wait_frames, *imgs):
2 pass
参数1:生成视频的文件名
参数2:每两张图像之前的变换(称之为1次迭代)需要多少帧
参数3:每个迭代后写入多少帧静态图,也就是每次迭代完成后图片保持多少帧不变
参数4:参与生成视频的图片集合
除了这个函数外,我们还需要几个辅助函数。
1def to_video(name, width, height):2 fps = 103 video_writer = cv2.VideoWriter(name, cv2.VideoWriter_fourcc('I', '4', '2', '0'), fps, (width, height))45 return video_writer67def get_equation(x0, y0, x1, y1, pow_arg=1):8 k = (y1 - y0) / (pow(x1, pow_arg) - pow(x0, pow_arg))9 b = y0 - k * pow(x0, pow_arg)
10
11 def f(x):
12 return k * pow(x, pow_arg) + b
13
14 return f
15
16def get_rotate_theta(from_landmarks, to_landmarks):
17 from_left_eye = from_landmarks[36]
18 from_right_eye = from_landmarks[45]
19
20 to_left_eye = to_landmarks[36]
21 to_right_eye = to_landmarks[45]
22
23 from_angle = math.atan2(from_right_eye[1] - from_left_eye[1], from_right_eye[0] - from_left_eye[0])
24 to_angle = math.atan2(to_right_eye[1] - to_left_eye[1], to_right_eye[0] - to_left_eye[0])
25
26 from_theta = -from_angle * (180 / math.pi)
27 to_theta = -to_angle * (180 / math.pi)
28
29 return to_theta - from_theta
to_video函数主要是用来创建一个视频生成器的。get_equation函数是用来生成一个根据时间变化的方程,主要用到了一次方程和二次方程。get_rotate_theta这个函数是通过计算左右眼的夹角来估计人脸倾斜角度差值,下标的值可以参考第一张图片。
最后我们就要进入主函数的实现了,主要思路是遍历所有图片,每个迭代拿出当前图和下一张图,然后识别出两张人脸中的关键点,通过这些关键点我们可以计算出两张图在某一时刻需要的旋转角度、旋转中心、缩放比例、位移像素数等关键参数。最终我们再次迭代frames_per_transformer次通过对两张图片分别做旋转、平移变换来达到效果。
1def compose_img(name, frames_per_transformer, wait_frames, *imgs):2 video_writer = to_video("{}.avi".format(name), imgs[0].shape[1], imgs[0].shape[0])34 img_count = len(imgs)5 for idx in range(img_count - 1):6 from_img = imgs[idx]7 to_img = imgs[idx + 1]89 from_width = from_img.shape[1]10 from_height = from_img.shape[0]1112 to_width = to_img.shape[1]13 to_height = to_img.shape[0]1415 from_face_region, from_landmarks = face_detector(from_img)16 to_face_region, to_landmarks = face_detector(to_img)1718 # 第一张图最终的旋转角度19 from_theta = get_rotate_theta(from_landmarks, to_landmarks)20 # 第二张图初始的旋转角度21 to_theta = get_rotate_theta(to_landmarks, from_landmarks)2223 # 两张图的旋转中心24 from_rotate_center = (from_face_region.left() + (from_face_region.right() - from_face_region.left()) / 2, from_face_region.top() + (from_face_region.bottom() - from_face_region.top()) / 2)25 to_rotate_center = (to_face_region.left() + (to_face_region.right() - to_face_region.left()) / 2, to_face_region.top() + (to_face_region.bottom() - to_face_region.top())/2)2627 from_face_area = from_face_region.area()28 to_face_area = to_face_region.area()2930 # 第一张图的最终缩放因子31 to_scaled = from_face_area / to_face_area32 # 第二张图的初始缩放因子33 from_scaled = to_face_area / from_face_area3435 # 平移多少的基准36 to_translation_base = to_rotate_center37 from_translation_base = from_rotate_center3839 equation_pow = 1 if idx % 2 == 0 else 24041 # 建立变换角度的方程42 to_theta_f = get_equation(0, to_theta, frames_per_transformer - 1, 0, equation_pow)43 from_theta_f = get_equation(0, 0, frames_per_transformer - 1, from_theta, equation_pow)4445 # 建立缩放系数的角度46 to_scaled_f = get_equation(0, to_scaled, frames_per_transformer - 1, 1, equation_pow)47 from_scaled_f = get_equation(0, 1, frames_per_transformer - 1, from_scaled, equation_pow)4849 for i in range(frames_per_transformer):50 # 当前时间点的旋转角度51 cur_to_theta = to_theta_f(i)52 cur_from_theta = from_theta_f(i)5354 # 当前时间点的缩放因子55 cur_to_scaled = to_scaled_f(i)56 cur_from_scaled = from_scaled_f(i)5758 # 生成第二张图片变换矩阵59 to_rotate_M = cv2.getRotationMatrix2D(to_rotate_center, cur_to_theta, cur_to_scaled)60 # 对第二张图片执行仿射变换61 to_dst = cv2.warpAffine(to_img, to_rotate_M, (to_width, to_height), borderMode=cv2.BORDER_REPLICATE)6263 # 生成第一张图片的变换矩阵64 from_rotate_M = cv2.getRotationMatrix2D(from_rotate_center, cur_from_theta, cur_from_scaled)65 # 对第一张图片执行仿射变换66 from_dst = cv2.warpAffine(from_img, from_rotate_M, (from_width, from_height), borderMode=cv2.BORDER_REPLICATE)6768 # 重新计算变换后的平移基准69 to_left_rotated = to_rotate_M[0][0] * to_translation_base[0] + to_rotate_M[0][1] * to_translation_base[1] + to_rotate_M[0][2]70 to_top_rotated = to_rotate_M[1][0] * to_translation_base[0] + to_rotate_M[1][1] * to_translation_base[1] + to_rotate_M[1][2]7172 from_left_rotated = from_rotate_M[0][0] * from_translation_base[0] + from_rotate_M[0][1] * from_translation_base[1] + from_rotate_M[0][2]73 from_top_rotated = from_rotate_M[1][0] * from_translation_base[0] + from_rotate_M[1][1] * from_translation_base[1] + from_rotate_M[1][2]7475 # 当前时间点的平移数76 to_left_f = get_equation(0, from_left_rotated - to_left_rotated, frames_per_transformer - 1, 0, equation_pow)77 to_top_f = get_equation(0, from_top_rotated - to_top_rotated, frames_per_transformer - 1, 0, equation_pow)7879 from_left_f = get_equation(0, 0, frames_per_transformer - 1, to_left_rotated - from_left_rotated, equation_pow)80 from_top_f = get_equation(0, 0, frames_per_transformer - 1, to_top_rotated - from_top_rotated, equation_pow)8182 # 生成第二张图片平移的变换矩阵83 to_translation_M = np.float32(84 [85 [1, 0, to_left_f(i)],86 [0, 1, to_top_f(i)]87 ]88 )8990 # 对第二张图片执行平移变换91 to_dst = cv2.warpAffine(to_dst, to_translation_M, (to_width, to_height), borderMode=cv2.BORDER_REPLICATE)9293 # 生成第一张图片平移的变换矩阵94 from_translation_M = np.float32(95 [96 [1, 0, from_left_f(i)],97 [0, 1, from_top_f(i)]98 ]99 )
100
101 # 对第一张图片执行平移变换
102 from_dst = cv2.warpAffine(from_dst, from_translation_M, (from_width, from_height), borderMode=cv2.BORDER_REPLICATE)
103
104 # 将两张图片合成到一张,并写入视频帧
105 new_img = cv2.addWeighted(from_dst, 1 - ((i + 1) / frames_per_transformer), to_dst, (i + 1) / frames_per_transformer, 0)
106 video_writer.write(new_img)
107
108 # 一个迭代完成,迭代n次写入第二张图片
109 for _ in range(wait_frames):
110 video_writer.write(to_img)
111
112 video_writer.release()
以上就是利用仿射变换实现的代码。效果可以看下面的gif(忽略gif的颜色问题,视频正常!完整视频可以到http://www.iqiyi.com/w_19saz225ol.html查看)
通过观察效果和代码,我们来总结一下这个版本的不足之处:
1. 两张人脸并未真正实现大小一致。
2. 人脸对齐也做的不够好。
3. 仅在2D空间做了变换,对于脸朝向的变换不敏感。
4. 代码复杂。
5. 仅利用了68个人脸关键点中的一小部分,并未充分利用人脸的特征。
以上几个问题其实就决定了仿射变换版本的使用局限性很大,跟抖音实现的效果差距很大。这也迫使我寻找另一种解决方案,结果就是透视变换版本,这个版本代码简单而且效果更接近抖音。
透视变换
仿射变换仅在二维空间做线性变换和平移,所以两条平行线变换后还是平行的,因而我们感受不到立体变换的效果。而透视变换则不同,它是在3D空间做变换,最后在映射到2D平面。以下是透视变换的数学原理。
从公式中可以看到变换后做了第3个维度z。展开为方程组形式:
最后映射回2维空间:
从公式中可以看到,假设将a33设为1,那么会有8个未知数,也就是我们至少需要4个点才能求得方程的接。在python中可以轻松的实现:
1img = cv2.imread("./1.jpg")2src_pts = np.float32(3[4 [5 [0, 0],6 [0, 626],7 [500, 626],8 [500, 0]9 ]
10])
11
12dst_pts = np.float32(
13 [
14 [100, 50],
15 [150, 200],
16 [500, 626],
17 [500, 0]
18 ]
19)
20
21M = cv2.getPerspectiveTransform(src_pts, dst_pts)
22dst = cv2.warpPerspective(img, M, (img.shape[0], img.shape[1]))
23cv2.imshow("", dst)
24cv2.waitKey()
上面代码效果如下:
上面的代码是通过getPerspectiveTransform函数找到src的4个点和dst的4个点的变换矩阵,还有一个函数findHomography可以在一堆点中找到最佳的变换矩阵,很明显,第二个函数更符合这个需求的实现,可以直接将人脸识别后的关键点扔给这个函数,然后找到最佳变换矩阵。所以透视变换版本的代码如下:
1def compose_img(name, frames_per_transformer, wait_frames, *imgs):2 video_writer = to_video("{}.avi".format(name), imgs[0].shape[1], imgs[0].shape[0])34 img_count = len(imgs)5 for idx in range(img_count - 1):6 from_img = imgs[idx]7 to_img = imgs[idx + 1]89 from_width = from_img.shape[1]
10 from_height = from_img.shape[0]
11
12 to_width = to_img.shape[1]
13 to_height = to_img.shape[0]
14
15 equation_pow = 1 if idx % 2 == 0 else 2
16
17 from_face_region, from_landmarks = face_detector(from_img)
18 to_face_region, to_landmarks = face_detector(to_img)
19
20 homography_equation = get_equation(0, from_landmarks, frames_per_transformer - 1, to_landmarks, equation_pow)
21
22 for i in range(frames_per_transformer):
23 from_H, _ = cv2.findHomography(from_landmarks, homography_equation(i))
24 to_H, _ = cv2.findHomography(to_landmarks, homography_equation(i))
25
26 from_dst = cv2.warpPerspective(from_img, from_H, (from_width, from_height), borderMode=cv2.BORDER_REPLICATE)
27 to_dst = cv2.warpPerspective(to_img, to_H, (to_width, to_height), borderMode=cv2.BORDER_REPLICATE)
28
29 new_img = cv2.addWeighted(from_dst, 1 - ((i + 1) / frames_per_transformer), to_dst, (i + 1) / frames_per_transformer, 0)
30 video_writer.write(new_img)
31
32 for _ in range(wait_frames):
33 video_writer.write(to_img)
34
35 video_writer.release()
可以看到代码简化了不少,也仅用了一次变换就完成了。如上面所说,我们使用findHomography函数,在68个关键点中寻找最佳变换矩阵,然后利用warpPerspective函数进行变换,效果可以看下面的gif(忽略gif的颜色问题,视频正常!完整视频可以到http://www.iqiyi.com/w_19saz1z92h.html查看)
可以看到这次的效果完全有了立体感,而且人脸的对齐也比第一个版本好的多,跟抖音的差距也缩小了不少。
最终所有代码都可以再我的github下载:
https://github.com/qibin0506/change_face
技术的道路一个人走着极为艰难?
一身的本领得不施展?
优质的文章得不到曝光?
别担心,
即刻起,CSDN 将为你带来创新创造创变展现的大舞台,
扫描下方二维码,欢迎加入 CSDN 「原力计划」!
(*本文为AI科技大本营转载文章,转载请联系作者)
◆
精彩推荐
◆
推荐阅读
钢铁侠“变身”AI布道师?小罗伯特·唐尼这次推出一部AI科普纪录片
提高建模效率,改变手工作坊式生产,AutoML的技术研究与应用进展如何了?
2019年上万篇论文发表,这14篇备受瞩目的论文,你都了解吗?
迁移学习前沿研究亟需新鲜血液,深度学习理论不能掉链子
俄罗斯“扎克伯格”:创建区块
链版“微信”,27 岁身价达 2.5 亿美元
什么是数字孪生?
6 个重要模块,带你编写一个基于Golang的区块链公链demo!| 博文精选
抗住 60 亿次攻击,起底阿里云安全的演进之路 | 问底中国 IT 技术演进
你点的每个“在看”,我都认真当成了AI
相关文章:

思科PIX防火墙的实际应用配置
PIX:一个合法IP完成inside、outside和dmz之间的访问 现有条件:100M宽带接入,分配一个合法的IP(222.134.135.98)(只有1个静态IP是否够用?);PiX515e-r-DMZ-BUN1台ÿ…

CommonJS 的 AMD 规范
异步模块定义(Asynchronous Module Definition,简称 AMD)API 描述了一种定义模块的机制,模块及其依赖模块可以通过这种机制进行加载。该机制特别适用于浏览器。 本规范曾被称为 Modules Transport/C,但本规范主要不是用…
易观的大数据中台之路
作者 | 易观CTO郭炜出品 | AI科技大本营(ID:rgznai100)本文为CSDN即将推出的《新战场:决胜中台》专刊的第 2 篇文章。什么是数据中台?中台的定义来自于投资银行,简单说来,投行当中前台是赚钱的、后台是做支…

WMI技术介绍和应用——查询系统信息和补丁包信息
本文使用了《 WMI技术介绍和应用——使用VC编写一个半同步查询WMI服务的类》中代码做为基础。本节只是列出了WQL语句,具体使用参看前面的例子。( 转载请指明出于breaksoftware的csdn博客)本文主要知识点是Win32_OperatingSystem和Win32_Quick…

Winform与Webform中的对话框
参考MSDNDialogResult result MessageBox.Show("见过打劫的没?", "打、打、打劫", MessageBoxButtons.OKCancel, MessageBoxIcon.Information, MessageBoxDefaultButton.Button1);switch (result){case DialogResult.OK:MessageBox.Show("你按了确定…
启动Genymotion时报错Failed to initialize backend EGL display
在启动Genymotion的时候报错: video card说的是显卡,你的显卡可能不支持 OpenGL2.0,或者你装的驱动有问题。解决办法:将驱动重新安装一下。可直接下载一个如“驱动人生“一样的驱动软件,把你的显卡驱动更新一下。转载…

新年新方向-斩获新职位
^_^上个星期斩获公司内部竞聘职位:项目管理部总经理,高兴,发展方向变了,以后我会多写一些项目管理的文章和大家分享。WPF的学习和分享也不会停下来,我要全面发展,做社会主义的接班人!YE!项目管理部总经理职…
清华官宣:前百度总裁张亚勤正式加盟清华大学
整理 | Jane出品 | AI科技大本营(ID:rgznai100)12月31日,2019 年的最后一天,在大家沉浸在「总结过去」、「展望未来」之时,清华大学刚刚官宣了一则消息:前百度总裁张亚勤正式加盟清华大学&#…

WMI技术介绍和应用——查询时间信息
本文使用了《WMI技术介绍和应用——使用VC编写一个半同步查询WMI服务的类》中代码做为基础。本节只是列出了WQL语句,具体使用参看前面的例子。(转载请指明出于breaksoftware的csdn博客) 本文主要介绍Win32_LocalTime和Win32_UTCTime。 如何使…

问题-[VMware Workstation]断电后,重启电脑,之后就提示“内部错误”
问题现象:突然断电后,重启电脑,再打开VMware Workstation,启动不了。之后就提示“内部错误”。问题原因:希望高人指点。问题处理:关闭VMware Workstation,在快捷方式上,右击…

FC SAN与IP SAN的比较
SAN(Storage Area Network,存储区域网络)是一个由存储设备和系统部件构成的网络。所有的通信都在一个与应用网络隔离的单独的网络上完成,可以被用来集中和共享存储资源。SAN不但提供了对数据设备的高性能连接,提高了数…
“数学不行,干啥也不行!”骨灰级程序员:其实你们都是瞎努力
编程圈一直都流传着一个段子:一流程序员靠数学,二流程序员靠算法,末端程序员靠百度,低端看高端就是黑魔法。懂的人其实都知道,这不是段子,其实就是程序员的真实写照。想一想,我们日常学习、求职…

WMI技术介绍和应用——查询系统服务
本文使用了《 WMI技术介绍和应用——使用VC编写一个半同步查询WMI服务的类》中代码做为基础。本节只是列出了WQL语句,具体使用参看前面的例子。( 转载请指明出于breaksoftware的csdn博客)本文主要介绍Win32_Service类。 如何使用WMI枚举系统中…
送书 | 2020年新一天,用这本书开启你的NLP学习之路!
本文选自电子工业出版社博文视点新书《大数据智能:数据驱动的自然语言处理技术》。本书作者:清华大学刘知远、薄言RSVP.ai崔安颀、腾讯张开旭、清华大学韩文弢、中国人民大学赵鑫、厦门大学苏劲松、罗格斯大学张永锋、北京大学严睿、哈尔滨工业大学&…

如何在父进程中读取子(外部)进程的标准输出和标准错误输出结果
最近接手一个小项目,要求使用谷歌的aapt.exe获取apk软件包中的信息。依稀记得去年年中时,有个同事也问过我如何获取被调用进程的输出结果,当时还研究了一番,只是没有做整理。今天花点时间,将该方法整理成文。ÿ…

Ruby环境的安装(In Ubuntu 7.10)
今天开始学习Ruby。准备的图书呢,就是《Programming Ruby - 2nd》。为了准备一个实验的环境,于是要给我的Ubuntu上安装Ruby的环境。1、安装解释器:sudo apt-get install ruby 2、安装一个即时执行工具irb:由于第一部安装的结果…

基于animation.css实现动画旋转特效
分享一款基于animation.css实现动画旋转特效。这是一款基于CSS3实现的酷炫的动画旋转特效代码。效果图如下: 在线预览 源码下载 实现的代码。 html代码: <div class"wrap"><div class"mod_bg"><div class"bg…

VC:CString用法整理(转载)
1.CString::IsEmpty BOOL IsEmpty( ) const; 返回值:如果CString 对象的长度为0,则返回非零值;否则返回0。 说明:此成员函数用来测试一个CString 对象是否是空的。 示例: 下面的例子说明了如何使用CString::IsEmp…

WMI技术介绍和应用——查询本地用户和组
本文使用了《 WMI技术介绍和应用——使用VC编写一个半同步查询WMI服务的类》中代码做为基础。本节只是列出了WQL语句,具体使用参看前面的例子。( 转载请指明出于breaksoftware的csdn博客)本文主要介绍Win32_Group和Win32_UserAccount类。 如何…
AAAI 2020 | 时间可以是二维的吗?基于二维时间图的视频内容片段检测
作者 | 彭厚文、傅建龙来源 | 微软研究院AI头条编者按:当时间从一维走向二维,时序信息处理问题中一种全新的建模思路由此产生。根据这种新思路及其产生的二维时间图概念,微软亚洲研究院提出一种新的解决时间定位问题的通用方法:二…

UITextField的代理方法
- (void)textFieldDidBeginEditing:(UITextField *)textField 当textField开始编辑的时候调用,可用完成如下的需求:点击textField可以使页面往上滑动 必须加上这句代码 [UIView animateWithDuration:.25 animations:^{ _scrollView.contentOffset CGPo…

以金山界面库(openkui)为例思考和分析界面库的设计和实现——问题
随着物质生活的丰富,人们的精神生活也越来越丰富。人们闲暇的时间也相对变多,于是很多人就开始寻找打发时间的方法。其中电视便是其中一种非常重要的消遣方式。假如我们打开电视机,看到了一个电视台正在播一部我们之前没看过的,正…
知识图谱,下一代数据中台的核心技术
作者 | 杨威,明略科技技术中心负责人 编辑 | 夕颜 出品 | AI科技大本营(ID:rgznai100) 本文为CSDN即将推出的《新战场:决胜中台》专刊的第 3 篇文章。 【导读】数据中台火的十分突然,似乎年前还在炒概念,年…

StoneAge Dict 技术方案的可行性[1]
上次和项目经理讨论了技术选型的事,现在简单地说一下,等下次开会再系统地讲。1. 平台环境:Java1.52. 用户接口:Web: JSP, JSFSpring2.5xDesktop: JavaFX3. 技术:dictzip(解析StarDict词库), XML因为StarDict是目前很受…

using的几种用途
using 常用来引用命名空间 1 using System; 2 using System.Data; 3 using System.Data.SqlClient; 4 using System.Collections.Generic; 5 using Model; 6 using IDAL; 7 using DBUnititly; using另一个用途是给类和命名空间指定别名 1 using spacename system.io2 3 using…

以金山界面库(openkui)为例思考和分析界面库的设计和实现——资源读取模块分析
按照软件的执行流程,我们首先遇到《以金山界面库(openkui)为例思考和分析界面库的设计和实现——问题》中提出的最后一个问题:界面描述文件的放置位置。我们曾提出一种方案:将界面描述文件打包后放在资源文件中&#x…
开发者如何赶上5G风口?
随着5G正式步入商用,5G 技术引发广泛关注。据信息通信研究院《5G经济社会影响白皮书》预测,2030年,5G将直接带动的总产出、经济增加值、就业机会分别为6.3万亿元、2.9万亿元和800万个。据BOSS直聘 《2019年5G相关人才数据观察》报告指出&…

使用C# 3.0编译器编译 Asp.Net 项目代码
只需要在 web.config 里添加这样的一段设置就OK了:<configuration><system.codedom><compilers><compiler language"c#;cs;csharp"extension".cs"type"Microsoft.CSharp.CSharpCodeProvider,System, Version2.0.0.0, Cultureneu…

java 它 引用(基本类型的包装,构造函数和析构函数c++不同)
一个:java 和c参考控制 他提到引用,我们会想到java它不喜欢c里面的指针。当然java内引用和c里面的引用是不同的。 比如: 比方C中,我对某一个函数的声明。int a(int &b),b即为引用类型,函数内b的改动能够…

使用程序解决一道逻辑推理题
今天看朋友发了一个老问题,一道很有意思的推理题:(转载请指明出于breaksoftware的csdn博客) 小明和小强都是张老师的学生,张老师的生日是M月N日,2人都知道张老师的生日是下列10组中的一天: 3月4…