使用opencv.js边缘计算
新建html文件,引入opencv.js
html
<script src="https://docs.opencv.org/4.x/opencv.js"></script>
添加canvas容器
html
<canvas id="myCanvas"></canvas>
添加样式使canvas占满全屏
css
canvas {
position: fixed;
top: 0;
left: 0;
height: 100vh;
width: 100vw;
}
使用opencv.js提供的对图标进行边缘检测算法拿到图像数据
javascript
function getCannyEdges(imagePath) {
return new Promise((resolve, reject) => {
const img = new Image();
img.src = imagePath;
img.onload = function () {
const imageInput = cv.imread(img);
// 将图像转换为灰度图像
const gray = new cv.Mat();
cv.cvtColor(imageInput, gray, cv.COLOR_RGBA2GRAY);
// 使用Canny边缘检测算法
const edges = new cv.Mat();
cv.Canny(gray, edges, 50, 150);
// 放大一倍(或按需要放大)
const outputWidth = edges.cols * 1;
const outputHeight = edges.rows * 1;
const enlargedOutput = new cv.Mat();
cv.resize(edges, enlargedOutput, new cv.Size(outputWidth, outputHeight), 0, 0, cv.INTER_LINEAR);
// 返回 OpenCV 的 Mat 边缘检测结果
resolve(enlargedOutput);
// 释放内存
imageInput.delete();
gray.delete();
edges.delete();
};
img.onerror = reject;
});
}
canvas实现图像粒子动画
设置canvas容器的宽高为图片的宽高,分析边缘计算的数据拿到黑色像素的位置信息,为了优化效率,只取偶数位的像素信息。
javascript
function drawEdgesWithAnimation(mat, canvas) {
const ctx = canvas.getContext('2d');
const width = mat.cols;
const height = mat.rows;
// 设置 canvas 尺寸
canvas.width = width;
canvas.height = height;
console.log(width,height)
// 获取黑色像素点
let blackPixels = [];
for (let i = 0; i < height; i++) {
let last=0
for (let j = 0; j < width; j++) {
const pixel = mat.ucharPtr(i, j);
if (pixel[0] === 255&&(i+j)%2==0) { // 黑色边缘像素
blackPixels.push({ x: j, y: i });
last=j
}
}
}
}
创建动画函数,使粒子动画在20帧完成。函数传入所有像素粒子的初始位置信息,根据最终位置信息计算每一帧的粒子位置,进行渲染,以此来达到动画的效果。、
添加动画触发方法,这里我是直接给canvas加了点击事件
因为粒子的大小是像素级别的,所以粒子数会非常多,所以在获取黑色像素信息那一步只取偶数位的粒子。
javascript
// 点击事件响应
canvas.addEventListener('click', (event) => {
const rect = canvas.getBoundingClientRect();
const clickX = ((event.clientX - rect.left) / rect.width) * canvas.width;
const clickY = ((event.clientY - rect.top) / rect.height) * canvas.height;
// 找到点击范围内的黑色像素
const affectedPixels = blackPixels.filter(pixel => {
const dx = pixel.x - clickX;
const dy = pixel.y - clickY;
return Math.sqrt(dx * dx + dy * dy) < 5000; // 半径为50的范围
});
// 随机移动这些像素
affectedPixels.forEach(pixel => {
pixel.targetX = pixel.x + (Math.random() - 0.5) * 200;
pixel.targetY = pixel.y + (Math.random() - 0.5) * 200;
});
// 开始动画
animatePixels(affectedPixels);
});
初始化调用方法
javascript
// 使用示例
window.onload = function () {
getCannyEdges('./1.jpg').then((mat) => {
const canvas = document.getElementById('myCanvas');
drawEdgesWithAnimation(mat, canvas);
canvas.click()
});
}
加载效率优化
因为opencv.js非常大,差不多有10M,这对于浏览器来说会使网页加载很慢。所以我们可以使用预处理的方式来优化掉opencv.js。
我们的目标是实现粒子动画,所以我们完全可以直接保持图标边缘计算后的黑色像素坐标信息,然后直接使用这些坐标信息来进行动画的实现。
可以选择将数据保存为json格式,类似这样{x:100,y:120}
。这是一个好注意,但是依然牢记这是在浏览器环境,我们可以尽可能的对数据进行压缩来保证文件的传输速度。以下是优化思路:
- 因为数据都只有x,y属性,所以我们可以直接村数据,然后使用英文逗号分割,类似这样
100,120
。(经测试,json是1.6M,使用后变成800Kb) - 观察数据,粒子总是从左到右,丛上到下的,也就是说对于同一行的粒子他们的y是相同的,而下一行的粒子的y是上一行+1;所以我们只存x的数据,遇到下一行就添加一个@,所有数据依然使用英文逗号分隔。(经测试,数据从800Kb变为400Kb)
- 400Kb已经基本达到预期,但还可以优化一下。我们观察数据,对于一行的粒子,他们的x总是从0到结尾,中间是持续增长的。所以我们可以不用存储x的值,而是x(i)-x(i-1)的值,也就是后一项与前一项的差值。(经测试,数据从400Kb变为200+Kb)
实现方式:修改drawEdgesWithAnimation方法
javascript
function drawEdgesWithAnimation(mat, canvas) {
const ctx = canvas.getContext('2d');
const width = mat.cols;
const height = mat.rows;
// 设置 canvas 尺寸
canvas.width = width;
canvas.height = height;
console.log(width,height)
// 获取黑色像素点
let blackPixels = [];
let str=""
for (let i = 0; i < height; i++) {
let last=0
for (let j = 0; j < width; j++) {
const pixel = mat.ucharPtr(i, j);
if (pixel[0] === 255&&(i+j)%3==0) { // 黑色边缘像素
blackPixels.push({ x: j, y: i });
str+=`${j-last},`
last=j
}
}
str+='@,'
}
//保存为json文件
str=str.slice(0,-3)
const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(str));
const downloadAnchorNode = document.createElement('a');
downloadAnchorNode.setAttribute("href", dataStr);
downloadAnchorNode.setAttribute("download", "data.txt");
document.body.appendChild(downloadAnchorNode); // required for firefox
downloadAnchorNode.click();
downloadAnchorNode.remove();
console.log(JSON.stringify(str))
}
像素位置还原方法:
typescript
function(pixelData:string){
let blackPixels: Pixel[] = [];
const pixelArr=pixelData.split(',')
let j=0;
let last =0;
let xPixel=0;
for( let i=0;i<pixelArr.length;i++){
if(pixelArr[i]=='@'){
i++;
j++;
last=0
}
xPixel=last+Number(pixelArr[i])
blackPixels.push({ x: xPixel, y: j, originalX: xPixel, originalY: j, targetX: xPixel, targetY: j });
last=xPixel
}
}