当前位置: 首页 > 编程日记 > 正文

利用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台&#xff…

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,在快捷方式上,右击&#xf…

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软件包中的信息。依稀记得去年年中时,有个同事也问过我如何获取被调用进程的输出结果,当时还研究了一番,只是没有做整理。今天花点时间,将该方法整理成文。&#xff…

Ruby环境的安装(In Ubuntu 7.10)

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

基于animation.css实现动画旋转特效

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

VC:CString用法整理(转载)

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

WMI技术介绍和应用——查询本地用户和组

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

AAAI 2020 | 时间可以是二维的吗?基于二维时间图的视频内容片段检测

作者 | 彭厚文、傅建龙来源 | 微软研究院AI头条编者按&#xff1a;当时间从一维走向二维&#xff0c;时序信息处理问题中一种全新的建模思路由此产生。根据这种新思路及其产生的二维时间图概念&#xff0c;微软亚洲研究院提出一种新的解决时间定位问题的通用方法&#xff1a;二…

UITextField的代理方法

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

以金山界面库(openkui)为例思考和分析界面库的设计和实现——问题

随着物质生活的丰富&#xff0c;人们的精神生活也越来越丰富。人们闲暇的时间也相对变多&#xff0c;于是很多人就开始寻找打发时间的方法。其中电视便是其中一种非常重要的消遣方式。假如我们打开电视机&#xff0c;看到了一个电视台正在播一部我们之前没看过的&#xff0c;正…

知识图谱,下一代数据中台的核心技术

作者 | 杨威&#xff0c;明略科技技术中心负责人 编辑 | 夕颜 出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09; 本文为CSDN即将推出的《新战场&#xff1a;决胜中台》专刊的第 3 篇文章。 【导读】数据中台火的十分突然&#xff0c;似乎年前还在炒概念&#xff0c;年…

StoneAge Dict 技术方案的可行性[1]

上次和项目经理讨论了技术选型的事&#xff0c;现在简单地说一下&#xff0c;等下次开会再系统地讲。1. 平台环境&#xff1a;Java1.52. 用户接口&#xff1a;Web: JSP, JSFSpring2.5xDesktop: JavaFX3. 技术&#xff1a;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)为例思考和分析界面库的设计和实现——资源读取模块分析

按照软件的执行流程&#xff0c;我们首先遇到《以金山界面库&#xff08;openkui&#xff09;为例思考和分析界面库的设计和实现——问题》中提出的最后一个问题&#xff1a;界面描述文件的放置位置。我们曾提出一种方案&#xff1a;将界面描述文件打包后放在资源文件中&#x…

开发者如何赶上5G风口?

随着5G正式步入商用&#xff0c;5G 技术引发广泛关注。据信息通信研究院《5G经济社会影响白皮书》预测&#xff0c;2030年&#xff0c;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++不同)

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

使用程序解决一道逻辑推理题

今天看朋友发了一个老问题&#xff0c;一道很有意思的推理题&#xff1a;&#xff08;转载请指明出于breaksoftware的csdn博客&#xff09; 小明和小强都是张老师的学生&#xff0c;张老师的生日是M月N日&#xff0c;2人都知道张老师的生日是下列10组中的一天&#xff1a; 3月4…