頭條創作挑戰賽#
大家好,我是汪小成。最近在學習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小時內與您取得聯系。