头条创作挑战赛#
大家好,我是汪小成。最近在学习Canvas。这篇文章是我学习Canvas图片操作时记的笔记,欢迎大家审阅。
本篇文章的示例采用下图进行图片操作演示。
图片原始尺寸为:640px * 640px。
在Canvas中,我们使用drawImage()方法绘制图片。drawImage()方法有如下3种调用方式:
语法:
ctx.drawImage(image, dx, dy);
说明:
示例源码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>X轴方向上的缓动动画</title>
<script type="text/javascript" src="ball.js"></script>
</head>
<body>
<canvas
id="canvas"
width="800"
height="800"
style="border: 1px dashed #333333"
></canvas>
<script>
window.onload = function () {
// 1、获取 Canvas 对象
var canvas = document.getElementById("canvas");
// 2、获取上下文环境对象
var ctx = canvas.getContext("2d");
// 3、开始绘制图形
var image = new Image();
image.src = "flower-20221202.png";
image.onload = function () {
ctx.drawImage(image, 0, 0);
};
};
</script>
</body>
</html>
效果图:
说明:
本示例中,我们通过JS创建了一个Image对象,然后通过设置该对象的src属性指定了图片的路径。最后,我们为Image对象添加了onload事件监听,只有当图片加载完成后再使用drawImage()方法将图片绘制在Canvas上。
注意:只有当图片完全加载后才能将图片绘制到Canvas上。如果图片还未加载完成就调用了drawImage()方法进行图片绘制操作的话,Canvas将不会显示任何图片。
示例源码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>图片操作简单示例</title>
</head>
<body>
<canvas
id="canvas"
width="800"
height="800"
style="border: 1px dashed #333333"
></canvas>
<img src="flower-20221202.png" id="pic" style="display: none" />
<script>
window.onload = function () {
// 1、获取 Canvas 对象
var canvas = document.getElementById("canvas");
// 2、获取上下文环境对象
var ctx = canvas.getContext("2d");
// 3、开始绘制图形
var image = document.getElementById("pic");
ctx.drawImage(image, 0, 0);
};
</script>
</body>
</html>
说明:
本示例中的图片来自于HTML的img元素。这种方式的优点在于在JS执行时图片已经加载完成,不需要使用`image.onload = function () {}'。
语法:
ctx.drawImage(image, dx, dy, dw, dh);
说明:
参数image、dx、dy跟drawImage(image, dx, dy)参数含义一样。
参数dw为图片宽度;参数dh为图片高度。
通过这种方式绘制图片可以先将图片进行缩放,然后再绘制到Canvas中。
示例源码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>图片操作简单示例</title>
</head>
<body>
<canvas
id="canvas"
width="400"
height="400"
style="border: 1px dashed #333333"
></canvas>
<img src="flower-20221202.png" id="pic" style="display: none" />
<script>
window.onload = function () {
// 1、获取 Canvas 对象
var canvas = document.getElementById("canvas");
// 2、获取上下文环境对象
var ctx = canvas.getContext("2d");
// 3、开始绘制图形
var image = document.getElementById("pic");
ctx.drawImage(image, 0, 0, 320, 320);
};
</script>
</body>
</html>
效果图:
说明:
可以看到,图片原始大小为640px * 640px,我们使用drawImage(image, dx, dy, dw, dh)将图片的尺寸缩放到320px * 320px,然后再绘制到Canvas上。
语法:
ctx.drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh);
说明:
参数image、dx、dy、dw、dh表示目标图的横坐标、纵坐标、宽度、高度。
参数sx、sy、sw、sh表示源图需要截取的范围。sx表示被截取部分的横坐标,sy表示被截取部分的纵坐标,sw表示被截取部分的宽度,sh表示被截取部分的高度。
示例源码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>裁减图片示例</title>
</head>
<body>
<canvas
id="canvas"
width="800"
height="800"
style="border: 1px dashed #333333"
></canvas>
<img src="flower-20221202.png" id="pic" style="display: none" />
<script>
window.onload = function () {
// 1、获取 Canvas 对象
var canvas = document.getElementById("canvas");
// 2、获取上下文环境对象
var ctx = canvas.getContext("2d");
// 3、开始绘制图形
var image = document.getElementById("pic");
ctx.drawImage(image, 200, 200, 300, 300, 0, 0, 300, 300);
};
</script>
<style>
* {
margin: 0;
padding: 0;
}
body {
display: flex;
background: black;
align-items: center;
justify-content: center;
}
</style>
</body>
</html>
效果图:
说明:
图片裁剪说明如下图:
我们将左侧图片中红色框中的部分绘制到了Canvas (0, 0)位置处。
在Canvas中,我们使用createPattern()方法定义图片的平铺方式。
语法:
var pattern = ctx.createPattern(image, type);
ctx.fillStyle = pattern;
ctx.fillRect();
示例:
参数image表示被平铺的图片。
参数type表示平铺的方式,type属性取值如下表:
属性值 | 说明 |
repeat | 默认值,在水平方向和垂直方向同时平铺 |
repeat-x | 在水平方向平铺 |
repeat-y | 在垂直方向同时平铺 |
no-repeat | 只显示一次,不平铺 |
示例源码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>图片操作简单示例</title>
</head>
<body>
<canvas
id="canvas"
width="400"
height="400"
style="border: 1px dashed #333333"
></canvas>
<img src="vip.svg" id="pic" style="display: none;" />
<script>
window.onload = function () {
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var image = document.getElementById("pic");
var pattern = ctx.createPattern(image, 'repeat-x');
ctx.fillStyle = pattern;
ctx.fillRect(0, 0, 400, 400);
};
</script>
</body>
</html>
效果图:
以Canvas中,我们使用clip()方法切割图片。
语法:
ctx.clip();
说明:
使用clip()方法切割图片的步骤:
(1) 绘制基本图形; (2) 使用clip()方法; (3) 绘制图片。
示例源码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>图片操作简单示例</title>
</head>
<body>
<canvas
id="canvas"
width="800"
height="800"
style="border: 1px dashed #333333"
></canvas>
<img src="flower-20221202.png" id="pic" style="display: none" />
<script>
window.onload = function () {
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
// 第一步:绘制基本图片
ctx.beginPath();
ctx.arc(400, 400, 320, 0, Math.PI, true);
ctx.closePath();
ctx.stroke();
// 第二步:使用clip()方法
ctx.clip();
// 第三步,绘制图片
var image = document.getElementById("pic");
ctx.drawImage(image, 80, 80, 640, 640);
};
</script>
</body>
</html>
效果图:
说明:
我们使用半圆作为切割区域切割了图片。
在,终于有可能在浏览器中运行人脸识别了!通过本文,我将介绍face-api,它是一个构建在tensorflow.js core的JavaScript模块,它实现了人脸检测、人脸识别和人脸地标检测三种类型的CNN。
我们首先研究一个简单的代码示例,以用几行代码立即开始使用该包。
第一个face-recognition.js,现在又是一个包?
如果你读过关于人脸识别与其他的NodeJS文章:(https://medium.com/@muehler.v/node-js-face-recognition-js-simple-and-robust-face-recognition-using-deep-learning-ea5ba8e852),你可能知道,不久前,我组装了一个类似的包(face-recognition.js)。
起初,我没有想到在javascript社区中对脸部识别软件包的需求如此之高。对于很多人来说,face-recognition.js似乎是一个不错的免费使用且开源的替代付费服务的人脸识别服务,就像微软或亚马逊提供的一样。其中很多人问,是否可以在浏览器中完全运行完整的人脸识别管道。
在这里,我应该感谢tensorflow.js!我设法使用tfjs-core实现了部分类似的工具,这使你可以在浏览器中获得与face-recognition.js几乎相同的结果!最好的部分是,不需要设置任何外部依赖关系,可以直接使用。并且,它是GPU加速的,在WebGL上运行操作。
这使我相信,JavaScript社区需要这样的浏览器包!你可以用这个来构建自己的各种各样的应用程序。;)
如何用深度学习解决人脸识别问题
如果你希望尽快开始,也可以直接去编码。但想要更好地理解face-api.js中用于实现人脸识别的方法,我强烈建议你看一看,这里有很多我经常被问到的问题。
简单地说,我们真正想要实现的是,识别一个人的面部图像(input image)。我们这样做的方式是为每个我们想要识别的人提供一个(或多个)图像,并标注人名(reference data)。现在我们将它们进行比较,并找到最相似的参考图像。如果两张图片足够相似,我们输出该人的姓名,否则我们输出“unknown”。
听起来不错吧!然而,还是存在两个问题。首先,如果我们有一张显示多个人的图片,我们想要识别所有的人,该怎么办?其次,我们需要能够获得这种类型的两张人脸图像的相似性度量,以便比较它们......
人脸检测
第一个问题的答案是人脸检测。简而言之,我们将首先找到输入图像中的所有人脸。对于人脸检测,face-api.js实现了SSD(Single Shot Multibox Detector),它基本上是基于MobileNetV1的CNN,只是在网络顶部叠加了一些额外的盒预测层。
网络返回每个人脸的边界框及其相应的分数,即每个边界框显示一个人脸的可能性。分数用于过滤边界框,因为图像中可能根本不包含任何人脸。请注意,即使只有一个人检索边界框,也应执行人脸检测。
人脸标志检测和人脸对齐
第一个问题解决了!但是,我们希望对齐边界框,这样我们就可以在每个框的人脸中心提取出图像,然后将它们传递给人脸识别网络,这会使人脸识别更加准确!
为此,face-api.js实现了一个简单的CNN,它返回给定人脸图像的68个点的人脸标志:
从地标位置,边界框可以准确的包围人脸。在下图,你可以看到人脸检测的结果(左)与对齐的人脸图像(右)的比较:
人脸识别
现在我们可以将提取并对齐的人脸图像提供给人脸识别网络,这个网络基于类似ResNet-34的架构并且基本上与dlib中实现的架构相对应。该网络已经被训练学习将人脸的特征映射到人脸描述符(descriptor ,具有128个值的特征矢量),这通常也被称为人脸嵌入。
现在回到我们最初的比较两个人脸的问题:我们将使用每个提取的人脸图像的人脸描述符并将它们与参考数据的人脸描述符进行比较。也就是说,我们可以计算两个人脸描述符之间的欧氏距离,并根据阈值判断两个人脸是否相似(对于150 x 150大小的人脸图像,0.6是一个很好的阈值)。使用欧几里德距离的方法非常有效,当然,你也可以使用任何你选择的分类器。以下gif通过欧几里德距离将两幅人脸图像进行比较:
学完了人脸识别的理论,我们可以开始编写一个示例。
编码
在这个简短的例子中,我们将逐步了解如何在以下显示多人的输入图像上进行人脸识别:
包括脚本
首先,从 dist/face-api.js或者dist/face-api.min.js的minifed版本中获取最新的构建且包括脚本:
<script src =“face-api.js”> </ script>
链接:https://github.com/justadudewhohacks/face-api.js/tree/master/dist
如果你使用npm:
npm i face-api.js
加载模型数据
根据应用程序的要求,你可以专门加载所需的模型,但要运行完整的端到端示例,我们需要加载人脸检测,人脸标识和人脸识别模型。模型文件在repo上可用,下方链接中找到。
https://github.com/justadudewhohacks/face-api.js/tree/master/weights
已经量化了模型权重,将模型文件大小减少了75%,以便使你的客户端仅加载所需的最小数据。此外,模型权重被分割成最大4MB的块,以允许浏览器缓存这些文件,使得它们只需加载一次。
模型文件可以简单地作为静态资源(static asset)提供给你的Web应用程序,可以在其他地方托管它们,可以通过指定文件的路径或url来加载它们。假设你在models目录中提供它们并且资源在public/models下:
const MODEL_URL = '/models' await faceapi.loadModels(MODEL_URL)
或者,如果你只想加载特定模型:
const MODEL_URL = '/models' await faceapi.loadFaceDetectionModel(MODEL_URL) await faceapi.loadFaceLandmarkModel(MODEL_URL) await faceapi.loadFaceRecognitionModel(MODEL_URL)
从输入图像接收所有面孔的完整描述
神经网络接受HTML图像,画布或视频元素或张量作为输入。要使用score> minScore检测输入的人脸边界框,我们只需声明:
const minConfidence = 0.8 const fullFaceDescriptions = await faceapi.allFaces(input, minConfidence)
完整的人脸描述检测结果(边界框+分数)、人脸标志以及计算出的描述符。正如你所看到的,faceapi.allFaces在前面讨论过的所有内容都适用于我们。但是,你也可以手动获取人脸位置和标志。如果这是你的目标,github repo上有几个示例。
请注意,边界框和标志位置是相对于原始图像/媒体大小。如果显示的图像尺寸与原始图像尺寸不一致,则可以调整它们的尺寸:
const resized = fullFaceDescriptions.map(fd => fd.forSize(width, height))
我们可以通过将边界框绘制到画布中来可视化检测结果:
fullFaceDescription.forEach((fd, i) => { faceapi.drawDetection(canvas, fd.detection, { withScore: true }) })
脸部可 以显示如下:
fullFaceDescription.forEach((fd, i) => { faceapi.drawLandmarks(canvas, fd.landmarks, { drawLines: true }) })
通常,我所做的可视化工作是在img元素的顶部覆盖一个绝对定位的画布,其宽度和高度相同(参阅github示例以获取更多信息)。
人脸识别
现在我们知道如何在给定输入图像的情况下检索所有人脸的位置和描述符,即,我们将得到一些图像,分别显示每个人并计算他们的人脸描述符。这些描述符将成为我们的参考数据。
假设我们有一些可用的示例图像,我们首先从url获取图像,然后使用faceapi.bufferToImage从其数据缓冲区创建HTML图像元素:
// fetch images from url as blobs const blobs = await Promise.all( ['sheldon.png' 'raj.png', 'leonard.png', 'howard.png'].map( uri => (await fetch(uri)).blob() ) ) // convert blobs (buffers) to HTMLImage elements const images = await Promise.all(blobs.map( blob => await faceapi.bufferToImage(blob) ))
接下来,对于每个图像,我们定位主体的面部并计算人脸描述符,就像我们之前在输入图像时所做的那样:
const refDescriptions = await Promsie.all(images.map( img => (await faceapi.allFaces(img))[0] )) const refDescriptors = refDescriptions.map(fd => fd.descriptor)
现在,我们要做的一切就是遍历输入图像的人脸描述,并在参考数据中找到距离最近的描述符:
const sortAsc = (a, b) => a - b const labels = ['sheldon', 'raj', 'leonard', 'howard'] const results = fullFaceDescription.map((fd, i) => { const bestMatch = refDescriptors.map( refDesc => ({ label: labels[i], distance: faceapi.euclideanDistance(fd.descriptor, refDesc) }) ).sort(sortAsc)[0] return { detection: fd.detection, label: bestMatch.label, distance: bestMatch.distance } })
如前所述,我们在此使用欧氏距离作为相似性度量,结果表明工作得很好。我们最终得到了在输入图像中检测到的每个人脸的最佳匹配。
最后,我们可以将边界框与标签一起绘制到画布中以显示结果:
// 0.6 is a good distance threshold value to judge // whether the descriptors match or not const maxDistance = 0.6 results.forEach(result => { faceapi.drawDetection(canvas, result.detection, { withScore: false }) const text = `${result.distance < maxDistance ? result.className : 'unkown'} (${result.distance})` const { x, y, height: boxHeight } = detection.getBox() faceapi.drawText( canvas.getContext('2d'), x, y + boxHeight, text ) })
以上我希望你首先了解如何使用api。另外,我建议查看repo中的其他示例。
来自:ATYUN
crollBy介绍
scrollBy和scrollTo滑动的都是是View的内容,ViewGroup的子View。scrollBy(int x,int y)是在上一次滑动的基础上在水平方向再滑动x,在竖直方向在滑动y.
同样在滑动过程中,不改变View在父容器中的位置(left,top,right,bottom不变);
当x>0;View的内容从右向左滑动,当x<0;View 的内容从左向右滑动;当y>0 View 的内容,从下向上滑动,当y<0时 View的内容从上向下滑动。
2.scrollBy实例
2.1自定义ScrollByView
public class ScrollByView extends View {
private static String TAG=ScrollByView.class.getSimpleName();
private Paint paint;
private Paint textPaint;
private Paint smallCirclePaint;
public ScrollByView(Context context) {
super(context);
init();
}
public ScrollByView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//设置ScrollToView的背景
setBackgroundColor(Color.YELLOW);
canvas.drawCircle(getWidth()/2,getHeight()/2,getWidth()/2,paint);
canvas.drawCircle(getWidth()/2,getHeight()/2,getWidth()/8,smallCirclePaint);
canvas.drawText("使用scrollBy()实现瞬时滑动",0,40,textPaint);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode=MeasureSpec.getMode(widthMeasureSpec);
int widthSize=MeasureSpec.getSize(widthMeasureSpec);
int heightMode=MeasureSpec.getMode(heightMeasureSpec);
int heightSize=MeasureSpec.getSize(heightMeasureSpec);
//当宽和高的测量模式为MeasureSpec.AT_MOST,将次View的测量宽高值设为屏幕宽高的一半
if(widthMode==MeasureSpec.AT_MOST && heightMode==MeasureSpec.AT_MOST){
setMeasuredDimension(Constans.screenWidth/2,Constans.screenHeight/2);
CCLog.d(TAG,"ALL AT_MOST "+"Constans.screenWidth/2="+Constans.screenWidth/2+" Constans.screenHeight/2="+Constans.screenHeight/2);
}else if(widthMode==MeasureSpec.AT_MOST){
//当宽的测量模式为MeasureSpec.AT_MOST,将屏幕宽的一半作为宽的测量值
setMeasuredDimension(Constans.screenWidth/2,heightSize);
CCLog.d(TAG,"Width_MOST "+heightSize);
}else if(heightMode==MeasureSpec.AT_MOST){
//当高的测量模式为MeasureSpec.AT_MOST,将屏幕高的一半作为宽的测量值
setMeasuredDimension(widthSize,Constans.screenHeight/2);
CCLog.d(TAG,"Height_MOST "+widthSize);
}
}
//外部调用;
public void scrollByDelta(int dx, int dy){
//父容器滑动子View,滑动子View本身
((ViewGroup)getParent()).scrollBy(dx,dy);
//滑动View的内容
//scrollBy(x,y);
}
private void init(){
paint=new Paint();
paint.setColor(Color.BLUE);
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);
textPaint=new Paint();
textPaint.setColor(Color.BLACK);
textPaint.setAntiAlias(true);
textPaint.setTextSize(40);
textPaint.setStyle(Paint.Style.STROKE);
smallCirclePaint=new Paint();
smallCirclePaint.setColor(Color.GREEN);
smallCirclePaint.setAntiAlias(true);
smallCirclePaint.setStyle(Paint.Style.FILL);
}
}
2.2布局文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#33aaaa"
tools:context="com.ifrh.app.scroller.scrollByViewActivity">
<com.ifrh.app.scroller.ScrollByView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:id="@+id/scrollByView" />
<Button
android:layout_width="wrap_content"
android:layout_height="50dp"
android:text="左上角"
android:id="@+id/leftTop"
android:layout_above="@+id/scrollByView"
android:layout_alignLeft="@+id/scrollByView"/>
<Button
android:layout_width="wrap_content"
android:layout_height="50dp"
android:text="右上角"
android:id="@+id/rightTop"
android:layout_above="@+id/scrollByView"
android:layout_alignRight="@+id/scrollByView"/>
<Button
android:layout_width="wrap_content"
android:layout_height="50dp"
android:text="右下角"
android:id="@+id/rightBottom"
android:layout_below="@+id/scrollByView"
android:layout_alignRight="@+id/scrollByView"/>
<Button
android:layout_width="wrap_content"
android:layout_height="50dp"
android:text="左下角"
android:id="@+id/leftBottom"
android:layout_below="@+id/scrollByView"
android:layout_alignLeft="@+id/scrollByView"
/>
</RelativeLayout>
2.3在Activity中使用scrollBy
public class ScrollByActivity extends Activity {
private Button leftTop;
private Button rightTop;
private Button rightBottom;
private Button leftBottom;
private ScrollByView scrollByView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.scroll_by_activity);
leftTop=(Button)findViewById(R.id.leftTop);
rightTop=(Button)findViewById(R.id.rightTop);
rightBottom=(Button)findViewById(R.id.rightBottom);
leftBottom=(Button)findViewById(R.id.leftBottom);
scrollByView=(ScrollByView) findViewById(R.id.scrollByView);
leftTop.setOnClickListener(listener);
rightTop.setOnClickListener(listener);
rightBottom.setOnClickListener(listener);
leftBottom.setOnClickListener(listener);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
//在view.PerformClick()中将调用 view.onClick()方法
leftTop.performClick();
}
},2000);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
rightTop.performClick();
}
},4000);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
leftBottom.performClick();
}
},6000);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
rightBottom.performClick();
}
},8000);
}
private View.OnClickListener listener=new View.OnClickListener(){
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.leftTop:
scrollByView.scrollByDelta(Constans.screenWidth/4,Constans.screenHeight/4);
break;
case R.id.rightTop:
scrollByView.scrollByDelta(-Constans.screenWidth/4,Constans.screenHeight/4);
break;
case R.id.leftBottom:
scrollByView.scrollByDelta(Constans.screenWidth/4,-Constans.screenHeight/4);
break;
case R.id.rightBottom:
scrollByView.scrollByDelta(-Constans.screenWidth/4,-Constans.screenHeight/4);
break;
}
}
};
}
3.演示效果
*请认真填写需求信息,我们会在24小时内与您取得联系。