绘画手残党的福音:涂鸦线稿秒变绝美图像

Posted 华为云开发者社区

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了绘画手残党的福音:涂鸦线稿秒变绝美图像相关的知识,希望对你有一定的参考价值。

摘要:涂鸦线稿秒变绝美图像,ControlNet-Scribble2Img适配华为云ModelArts,提供更加便利和创新的图像生成体验,将你的想象变为真实的图像。

本文分享自华为云社区《AIGC拯救手残党:涂鸦线稿秒变绝美图像》,作者:Emma_Liu 。

ControlNet

什么是ControlNet?简而言之,文本到图像的生成涉及使用预训练的扩散模型来生成基于某些文本的图像。

从输入图像中提取特定信息的过程称为注释(在paper中)或预处理(在 ControlNet extension中)。
这个扩散模型是在数十亿张图片上预训练的,当我们输入文字时,模型会根据输入的内容生成一张图片。然而,有时输出结果与我们的意图并不完全一致。这就是ControlNet的作用…

ControlNet最早是在L.Zhang等人的论文《Adding Conditional Control to Text-to-Image Diffusion Model》中提出的,目的是提高预训练的扩散模型的性能。特别是,在预训练的扩散模型中加入另一个神经网络模型(ControlNet),使我们对输出有更多的控制。

ControlNet 的工作原理是将可训练的网络模块附加到稳定扩散模型的U-Net (噪声预测器)的各个部分。Stable Diffusion 模型的权重是锁定的,在训练过程中它们是不变的。在训练期间仅修改附加模块。

研究论文中的模型图很好地总结了这一点。最初,附加网络模块的权重全部为零,使新模型能够利用经过训练和锁定的模型。

训练 ControlNet 包括以下步骤:

  1. 克隆扩散模型的预训练参数,如Stable Diffusion的潜在UNet,(称为 “可训练副本”),同时也单独保留预训练的参数(“锁定副本”)。这样做是为了使锁定的参数副本能够保留从大型数据集中学习到的大量知识,而可训练的副本则用于学习特定的任务方面。
  2. 参数的可训练副本和锁定副本通过 "零卷积 "层连接,该层作为ControlNet框架的一部分被优化。这是一个训练技巧,在训练新的条件时,保留冻结模型已经学会的语义。
    从图上看,训练ControlNet是这样的:

ControlNet提供了八个扩展,每个扩展都可以对扩散模型进行不同的控制。这些扩展是Canny, Depth, HED, M-LSD, Normal, Openpose, Scribble, and Semantic Segmentation.

ControlNet-Scribble2img

Scribble 用于预处理用户绘制的涂鸦, 这个预处理程序不应该用在真实的图像上。由于它能够根据简单的草图生成令人惊叹、逼真或改进的图像。理想情况下,不需要任何提示。通过输入一个基本的图画,模型可以推断出细节和纹理,从而产生一个更高质量的图像。

下面是用ModelArts的Notebook适配Scribble2img生成的几幅图,一起来看看吧。

浮世绘风格的海浪 || 满天繁星 || 小兔子和萝卜

发光水母||一筐橙子||小花喵||微笑的太阳

尝试用自己画的素描生成,效果也不错

接下来让我们从零开始,在ModelArts上一起来体验Scribble2img涂鸦生图的乐趣吧。

涂鸦生成图像 ControlNet-Scribble2img

本文介绍如何在ModelArts来实现 ControlNet-Scribble2img 涂鸦生成图像。

AI Gallery - Notebook链接:拯救手残党:AI涂鸦一键成图 (huaweicloud.com)

前言

ModelArts 是面向开发者的一站式 AI 开发平台,为机器学习与深度学习提供海量数据预处理及交互式智能标注、大规模分布式训练、自动化模型生成,及端-边-云模型按需部署能力,帮助用户快速创建和部署模型,管理全周期 AI 工作流。

前期准备

在使用ModelArts之前,需要进入华为云官网 https://www.huaweicloud.com/ ,然后注册华为云账号,再进行实名认证。主要分为3步(注册–>实名认证–>服务授权)(如有已完成部分,请忽略)

点去完成 实名认证,账号类型选"个人",个人认证类型推荐使用"扫码认证"。

进入ModelArts 控制台数据管理页面,上方会提示访问授权,点击【服务授权】按钮,按下图顺序操作:

注意事项

  • 本案例需使用 Pytorch-1.8 GPU-P100 及以上规格运行;
  • 点击Run in ModelArts,将会进入到ModelArts CodeLab中,如果您没有登录需要进行登录。 登录之后,等待片刻,即可进入到CodeLab的运行环境;
  • 出现 Out Of Memory ,请检查是否为您的参数配置过高导致,修改参数配置,重启kernel或更换更高规格资源进行规避;
  • 运行代码方法:点击本页面顶部菜单栏的三角运行按钮或按Ctrl+Enter或cell左侧三角按钮运行每个方块中的代码;
  • 如果您是第一次使用 JupyterLab,请查看《ModelArts JupyterLab使用指导》了解使用方法;
  • 如果您在使用 JupyterLab 过程中碰到报错,请参考《ModelArts JupyterLab常见问题解决办法》尝试解决问题。

 

1.环境设置

check GPU & 拷贝代码及数据

为了更快的准备数据和模型,将其转存在了华为云OBS中,方便大家使用。

!nvidia-smi
import os
import moxing as mox
parent = "/home/ma-user/work/ControlNet"
bfp = "/home/ma-user/work/ControlNet/openai/clip-vit-large-patch14/pytorch_model.bin"
sfp = "/home/ma-user/work/ControlNet/models/control_sd15_scribble.pth"
if not os.path.exists(parent):
 mox.file.copy_parallel(\'obs://modelarts-labs-bj4-v2/case_zoo/scribble2img/ControlNet\',parent)
 if os.path.exists(parent):
 print(\'Download success\')
 else:
        raise Exception(\'Download Failed\')
elif os.path.exists(bfp)==False or os.path.getsize(bfp)!=1710671599: 
 mox.file.copy_parallel(\'obs://modelarts-labs-bj4-v2/case_zoo/scribble2img/ControlNet/openai/clip-vit-large-patch14/pytorch_model.bin\', bfp)
elif os.path.exists(sfp)==False or os.path.getsize(sfp)!=5710757851: 
 mox.file.copy_parallel(\'obs://modelarts-labs-bj4-v2/case_zoo/scribble2img/ControlNet/models/control_sd15_scribble.pth\', sfp)
else:
 print("Model Package already exists!")

安装库,大约耗时1min,请耐心等待。

%cd /home/ma-user/work/ControlNet
!pip uninstall torch torchtext -y
!pip install torch==1.12.1 torchvision==0.13.1 torchaudio==0.12.1 
!pip install omegaconf==2.1.1 einops==0.3.0
!pip install pytorch-lightning==1.5.0
!pip install transformers==4.19.2 open_clip_torch==2.0.2
!pip install gradio==3.24.1
!pip install translate==3.6.1

2. 加载模型

导包并加载模型,加载约40s,请耐心等待。

import numpy as np
from PIL import Image as PilImage
import cv2
import einops
import matplotlib.pyplot as plt
from IPython.display import HTML, Image
from base64 import b64decode
from translate import Translator
import torch
from pytorch_lightning import seed_everything
import config
from cldm.model import create_model, load_state_dict
from ldm.models.diffusion.ddim import DDIMSampler
from annotator.util import resize_image, HWC3
model = create_model(\'./models/cldm_v15.yaml\')
model.load_state_dict(load_state_dict(\'./models/control_sd15_scribble.pth\', location=\'cuda\'))
model = model.cuda()
ddim_sampler = DDIMSampler(model)

3. 涂鸦生成图像

涂鸦生成图像函数定义

def process(input_image, prompt, a_prompt, n_prompt, num_samples, image_resolution, ddim_steps, strength, scale, seed, eta):
    trans = Translator(from_lang="ZH",to_lang="EN-US")
    prompt = trans.translate(prompt)
 a_prompt = trans.translate(a_prompt)
 n_prompt = trans.translate(n_prompt)
 guess_mode = False
    # 图像预处理
 with torch.no_grad():
 if type(input_image) is str:
 input_image = np.array(PilImage.open(input_image))
 img = resize_image(HWC3(input_image), image_resolution)
 else:
 img = resize_image(HWC3(input_image[\'mask\'][:, :, 0]), image_resolution)  # scribble
 H, W, C = img.shape
        # 初始化检测映射
 detected_map = np.zeros_like(img, dtype=np.uint8)
 detected_map[np.min(img, axis=2) > 127] = 255
        control = torch.from_numpy(detected_map.copy()).float().cuda() / 255.0
        control = torch.stack([control for _ in range(num_samples)], dim=0)
        control = einops.rearrange(control, \'b h w c -> b c h w\').clone()
        # 设置随机种子
 if seed == -1:
            seed = random.randint(0, 65535)
 seed_everything(seed)
 if config.save_memory:
 model.low_vram_shift(is_diffusing=False)
 cond = "c_concat": [control], "c_crossattn": [model.get_learned_conditioning([prompt + \', \' + a_prompt] * num_samples)]
 un_cond = "c_concat": None if guess_mode else [control], "c_crossattn": [model.get_learned_conditioning([n_prompt] * num_samples)]
        shape = (4, H // 8, W // 8)
 if config.save_memory:
 model.low_vram_shift(is_diffusing=True)
        # 采样
 model.control_scales = [strength * (0.825 ** float(12 - i)) for i in range(13)] if guess_mode else ([strength] * 13)  # Magic number. 
        samples, intermediates = ddim_sampler.sample(ddim_steps, num_samples,
                                                     shape, cond, verbose=False, eta=eta,
 unconditional_guidance_scale=scale,
 unconditional_conditioning=un_cond)
 if config.save_memory:
 model.low_vram_shift(is_diffusing=False)
        # 后处理
 x_samples = model.decode_first_stage(samples)
 x_samples = (einops.rearrange(x_samples, \'b c h w -> b h w c\') * 127.5 + 127.5).cpu().numpy().clip(0, 255).astype(np.uint8)
        results = [x_samples[i] for i in range(num_samples)]
 return [255 - detected_map] + results

3.1设置参数,生成图像

在/home/ma-user/work/ControlNet/test_imgs/ 此路径下,我们预置了一些线稿供您测试。当然您可以自己上传您的涂鸦画至此路径下,然后更改图像路径及其他参数后,点击运行。

参数说明

images:生成图像张数

img_path:输入图像路径,黑白稿

prompt:提示词(建议填写)

a_prompt:正面提示(可选,要附加到提示的其他文本)

n_prompt: 负面提示(可选)

image_resolution: 对输入的图片进行最长边等比resize

scale:classifier-free引导比例

seed: 随机种子

ddim_steps: 采样步数,一般15-30,值越大越精细,耗时越长

eta: 控制在去噪扩散过程中添加到输入数据的噪声量。0表示无噪音,1.0表示更多噪音。eta对图像有微妙的、不可预测的影响,所以您需要尝试一下这如何影响您的项目。

strength: 这是应用 ControlNet 的步骤数。它类似于图像到图像中的去噪强度。如果指导强度为 1,则 ControlNet 应用于 100% 的采样步骤。如果引导强度为 0.7 并且您正在执行 50 个步骤,则 ControlNet 将应用于前 70% 的采样步骤,即前 35 个步骤。

#@title Scribble2img 
img_path = "test_imgs/cat.jpg" #@param type:"string"
prompt = "小花猫" #@param type:"string"
num_samples = 1
# Added Prompt
a_prompt = "质量最好,非常详细" #@param type:"string"
# Negative Prompt
n_prompt = "裁剪,质量最差,质量低" #@param type:"string"
image_resolution = 512 #@param type:"raw", dropdown
scale = 4.3 #@param type:"slider", min:0.1, max:30, step:0.1
seed = 1773327477 #@param type:"slider", min:-1, max:2147483647, step:1
eta = 0.02 #@param type:"slider", min:-1.00, max:3.00, step:0.01
ddim_steps = 15 #@param type:"slider", min:1, max:100, step:1
guess_mode = False
strength = 1.0
np_imgs = process(img_path, prompt, a_prompt, n_prompt, num_samples, image_resolution, ddim_steps, strength, scale, seed, eta)
src = PilImage.fromarray(~np_imgs[0])
dst = PilImage.fromarray(np_imgs[1])
fig = plt.figure(figsize=(25, 10))
ax1 = fig.add_subplot(1, 2, 1)
plt.title(\'Scribble image\', fontsize=16)
ax1.axis(\'off\')
ax1.imshow(src)
ax2 = fig.add_subplot(1, 2, 2)
plt.title(\'Generate image\', fontsize=16)
ax2.axis(\'off\')
ax2.imshow(dst)
plt.show()

在右侧有交互式控件,可以简单调整参数,然后运行即可,等待生成。

3.2模型局限性以及可能的偏差

  • Prompt只支持中英文输入。
  • 所提供的图像或简笔画过于简单或意义不明确时,模型可能生成与上传图像相关度低的物体或是一些无意义的前景物体,可以修改上传图像重新尝试。
  • 在一些场景下,描述Prompt不够明确时,模型可能生成错误的前景物体,可以更改Prompt并生成多次,取效果较好的结果。

当所提供的图像或简笔画与描述Prompt相关度低或无关时,模型可能生成偏向图像或偏向Prompt的内容,也可能生成无意义的内容;因此建议描述Prompt与所上传的图像紧密相关并且尽可能详细。

4. Gradio可视化部署

如果想进行可视化部署,可以继续以下步骤: Gradio应用启动后可在下方页面进行涂鸦生成图像,您也可以分享public url在手机端,PC端进行访问生成图像。

4.1 ControlNet扩展说明

  • 图像画布:您可以拖动设置画布宽度和画布高度,然后点击 开启画布! 来创建一张空白画布。
  • 调整笔刷进行绘画
  • 输入描述词(推荐),点击 Run
  • 高级选项(可选),您可点击此选项卡,打开折叠部分,按照上述参数说明进行设置,设置完成后点击 Run
import gradio as gr
# 画布生成函数
def create_canvas(w, h):
 img = np.zeros(shape=(h-2, w-2, 3), dtype=np.uint8) + 255
 im = cv2.copyMakeBorder(img,1,1,1,1,cv2.BORDER_CONSTANT)
 return im
block = gr.Blocks().queue()
with block:
 with gr.Row():
 gr.Markdown("##  涂鸦生成图像 ")
 with gr.Row():
 with gr.Column():
 canvas_width = gr.Slider(label="画布宽度", minimum=256, maximum=1024, value=512, step=1)
 canvas_height = gr.Slider(label="画布高度", minimum=256, maximum=1024, value=512, step=1)
 create_button = gr.Button(label="Start", value=\'开启画布!\')
 gr.Markdown(value=\'点击下面右上角小铅笔图标,改变你的刷子宽度,让它变的更细 (Gradio不允许开发人员设置画笔宽度,因此需要手动设置) \')
 input_image = gr.Image(source=\'upload\', type=\'numpy\', tool=\'sketch\')
 create_button.click(fn=create_canvas, inputs=[canvas_width, canvas_height], outputs=[input_image])
            prompt = gr.Textbox(label="Prompt")
 run_button = gr.Button(label="运行")
 with gr.Accordion("高级选项", open=False):
 num_samples = gr.Slider(label="Images", minimum=1, maximum=12, value=1, step=1)
 image_resolution = gr.Slider(label="Image Resolution", minimum=256, maximum=768, value=512, step=64)
                strength = gr.Slider(label="Control Strength", minimum=0.0, maximum=2.0, value=1.0, step=0.01)
 ddim_steps = gr.Slider(label="Steps", minimum=1, maximum=100, value=20, step=1)
                scale = gr.Slider(label="Guidance Scale", minimum=0.1, maximum=30.0, value=9.0, step=0.1)
                seed = gr.Slider(label="Seed", minimum=-1, maximum=2147483647, step=1, randomize=True)
                eta = gr.Number(label="eta (DDIM)", value=0.0)
 a_prompt = gr.Textbox(label="Added Prompt", value=\'质量最好,非常详细\')
 n_prompt = gr.Textbox(label="Negative Prompt",
                                      value=\'裁剪,质量最差,质量低\')
 with gr.Column():
 result_gallery = gr.Gallery(label=\'Output\', show_label=False, elem_id="gallery").style(grid=2, height=\'auto\')
 ips = [input_image, prompt, a_prompt, n_prompt, num_samples, image_resolution, ddim_steps, strength, scale, seed, eta]
 run_button.click(fn=process, inputs=ips, outputs=[result_gallery])
block.launch(share=True)

请注意: 图像生成消耗显存,您可以在左侧操作栏查看您的实时资源使用情况,点击GPU显存使用率即可查看,当显存不足时,您生成图像可能会报错,此时,您可以通过重启kernel的方式重置,然后重头运行即可规避。或更换更高规格的资源

 

点击关注,第一时间了解华为云新鲜技术~

几分钟让小孩的人物涂鸦「动起来」,Meta AI创建了一个奇妙的火柴人世界

点击上方“迈微AI研习社”,选择“星标★”公众号

重磅干货,第一时间送达

Meta AI 让儿童手绘「活」了起来。

你有没有想过将一张儿童绘画制作成动画?就如下图,儿童能够绘制出独特和富有创造力的人物和动物:长着双脚的星星、腿超级长的鸟……

父母和老师可以很容易地理解孩子绘画想要表达什么,但 AI 很难完成这项任务,因为儿童绘画通常以抽象、奇特的方式构建,就以儿童绘画中的「人」来说,绘画中的「人」有许多不同的形式、颜色、大小和比例,在身体对称性、形态和视角方面几乎没有相似之处。对 AI 来说,识别儿童绘画还存在一定的困难。

目前,出现了许多 AI 工具和技术来处理逼真的绘图,但儿童绘画增加了一定程度的多样性和不可预测性,这使得识别所描绘的内容变得更加复杂。

许多 AI 研究人员正在试图克服这一挑战,以便 AI 系统能够更好地识别儿童创作的各种人物绘画。

近日,Meta 宣布首创了一种 AI 系统,该系统可以在没有任何人工指导的情况下,高成功率的自动为儿童手绘人物和类人角色(即有两条胳膊、两条腿、一个头等的角色)制作动画,几分钟就可以实现从一张静态图到动画的转变。

例如,儿童绘制的小猫咪和小蜜蜂,上传到 Meta AI,你就会看到绘画变成会跳舞的角色,动作非常逼真。

试玩地址:https://sketch.metademolab.com/

通过将绘画上传到 Meta 原型系统,用户就可以体验绘画变成会跳跃的角色。此外,用户还可以下载动画与朋友家人分享。如果用户愿意,他们也可以提交这些绘画以帮助改进 AI 模型。

Meta 通过四个步骤来完成从绘画到动画的转变:目标检测识别人形;使用角色 mask 从场景中提升人形;通过「rigging」为动画做准备;使用 3D 动作捕捉制作 3D 人形动画。

目标检测识别人形图

第一步是将绘画中的人物与背景以及绘画中的其他类型的角色区分开来。使用现有的目标检测方法在儿童绘画上识别效果较好,但分割掩码不够准确,无法用于动画。为了解决这个问题,Meta 改为使用从目标检测器获得的边界框(bounding boxes),并应用一系列形态学操作和图像处理步骤来获得掩码。

Meta AI 采用基于卷积神经网络的目标检测模型 Mask R-CNN 来提取儿童绘画中的人物。虽然 Mask R-CNN 是在最大的分割数据集上进行了预训练,但该数据集是由真实世界物体照片组成,而不是绘画。为了让模型能够处理绘图,需要对模型进行微调,Meta AI 使用 ResNet-50+FPN 进行了微调,以预测单个类别「人形图」。  Meta AI 在大约 1,000 幅绘画上微调了模型。

微调后,模型很好地检测到了测试数据集中的人形图。但是也有失败的案例,如下图可分为四类:检测到的人形图没有包含整个图像(例如图中尾巴没有包含);没有把人形图和背景分开;没有把几个凑在一起的人形图分开;错误地识别非人类人物(例如树)。

 使用角色 mask 从场景中提升人形 

从画作中识别和提取人物后,生成动画的下一步是将其与场景的其他部分和背景分离,该过程被称为 masking。mask 必须准确映射人物的轮廓,因为它将被用于创建网格,然后变形以生成动画。一切妥当后,mask 将包含角色的所有组件,而消除任何背景内容。

尽管 Mask R-CNN 可以输出 mask,但 Meta AI 发现它们并不适合动画。当身体部位的外观变化很大时,预测的 mask 通常无法捕捉到整个人物。如下图下行图例所示,一个大的黄色三角形代表身体,一个铅笔笔画代表手臂,使用 Mask R-CNN 预测 mask 时,通常会漏掉连接双手的铅笔笔画部分。

基于此,Meta AI 开发了一种基于经典图像处理的方法,该方法对人物变化具有更强的稳健性。基于这种方法,Meta AI 使用预测到的人形边界框来裁剪图像。然后,使用自适应阈值和形态学 closing/dialating 操作,从边界框边缘填充,并假设 mask 是未被填充的最大多边形。

Mask R-CNN 与基于经典图像处理方法的效果比较。

然而,这种方法虽然对于提取适合动画的准确 mask 来说简单有效,但当背景杂乱、人物靠得太近或者纸张页面上有褶皱撕裂或阴影时,也有可能会失败。

通过「rigging」为动画做准备

儿童会画出千奇百怪的身体形状,远远超出了具有完整头部、手臂、腿和躯干的传统人形概念。一些儿童画出来的火柴人没有躯干,只有手臂和腿直接与头部相连。另一些儿童画下的人形更诡异,腿从头部延伸出来,手臂从大腿眼神出来。

因此,Meta AI 需要找到一种能够出来身形变化的 rigging 方法。

他们选择使用了人体姿态检测模型 AlphaPose,用来识别人画中作为臀部、肩膀、肘部、膝盖、手腕和脚踝的关键点。该模型是在真人图像上训练的,因此在将它调整以检测儿童画作中人形姿势之前,Meta AI 必须重新训练以处理儿童画作中存在的变化(variation)类型。

具体地,Meta AI 通过内部收集和注释儿童人形画面的小数据集实现了上述目标。然后,使用这些初始数据集上训练的姿态检测器创建了一个内部工具,使得父母可以上传并对他们孩子的画作进行动画处理。随着更多数据的加入,Meta AI 迭代地对模型进行再训练,直到达到较高的准确度。

使用 3D 动作捕捉制作 3D 人形动画

有了蒙版和联合预测,就有了制作动画所需要的一切。Meta AI 首先使用提取的蒙版生成网格,并使用原始画作进行纹理化处理。利用预测到的关节位置,他们为角色创建骨骼。之后,通过旋转骨骼并使用新的关节位置使网格变形,将角色移植到各种姿态中。通过将角色移植到一系列连续的姿态中,然后就可以创建动画了。

儿童作画时很常见的一种情况是从他们最容易辨认的角度来画身体部位,比如倾向于从侧面画腿和脚,从正面画头部和躯干。Meta AI 在动作重定位步骤中利用到了这一现象。对于下半身和生半身,他们会自动确定是从正面还是侧面来对动作进行识别。

具体地,他们将动作映射到单个 2D 平面并使用它来驱动角色,并使用 Mechanical Turk 运行的感知用户研究来对这种动作重定位的结果进行验证。分段检测流程如下图所示:

Meta AI 表示,将扭曲视角考虑在内是有帮助的,因为很多类型的动作并不会完成落在单个投影平面上。比如跳绳时,手臂和手腕主要在额平面内运动,弯曲的腿则倾向于在矢状平面内运动。因此,Meta AI 并没有为动作捕捉姿态确定单个动作平台,而是分别确定上半身和下半身的投影平面。

与此同时,有了 AR 眼睛,画作中的故事可以在现实世界中栩栩如生,画中的角色更可以与画出它的儿童一起跳舞或说话。

原文链接:https://ai.facebook.com/blog/using-ai-to-bring-childrens-drawings-to-life/

© THE END 

投稿或寻求报道微信:MaiweiE_com

更多细节可参考论文原文,更多精彩内容请关注迈微AI研习社,每天晚上七点不见不散!

GitHub中文开源项目《计算机视觉实战演练:算法与应用》,“免费”“全面“”前沿”,以实战为主,编写详细的文档、可在线运行的notebook和源代码。

  • 项目地址 https://github.com/Charmve/computer-vision-in-action

  • 项目主页 https://charmve.github.io/L0CV-web/

推荐阅读

(更多“抠图”最新成果)

迈微AI研习社

微信号: MaiweiE_com

GitHub: @Charmve

CSDN、知乎: @Charmve

投稿: yidazhang1@gmail.com

主页: github.com/Charmve

如果觉得有用,就请点赞、转发吧!

以上是关于绘画手残党的福音:涂鸦线稿秒变绝美图像的主要内容,如果未能解决你的问题,请参考以下文章

两个技巧教你怎么裁剪视频尺寸,手残党也能掌握

Qt 官方示例 | 网络入门 | http 下载小工具

基于ManagedDataAccess开发的OracleDBHelpe工具集伸手党的福音

涂鸦板是啥?

SketchAR上线HoloLens,解放你的双手去绘画!

Unity实现在白板上绘画涂鸦