网站首页 > 基础教程 正文
前言
前文我们讲了openCV如何在前端应用,以及前端使用openCV处理图片的基础。
本文我们跟着官方文档的图片处理部分,看看能做些什么。
我们继续在上文的基础上编码,这里简单地回顾一下。
页面中有个上传图片的按钮:
<input type="file" id="fileInput"/>
点击按钮上传图片,触发input的onchange事件:
let imgElement = document.getElementById('imageUpload');
let inputElement = document.getElementById('fileInput');
inputElement.onchange = function() {
imgElement.src = URL.createObjectURL(event.target.files[0]);
}
上传事件中我们设置页面中img标签的图片地址:
<img id="imageUpload" alt="No Image" />
由于我们给img绑定了onload事件,设置图片地址后就会触发:
imgElement.onload = function() {
// 图片处理程序
};
我们的图片处理程序放在onload中,所以上传图片后就自动处理,
然后页面中有个canvas:
<canvas id="canvasOutput"></canvas>
我们通过imshow把处理结果显示到canvas中:
cv.imshow('canvasOutput', dst);
这样我们修改onload中的图片处理程序,就可以在上传图片后看到自动处理后的图片,下面我们来看一些例子。
改变颜色
- 转换图片的颜色通道,比如RGB?Gray,RGB?HSV等等
cvtColor
cv.cvtColor (src, dst, code, dstCn = 0)改变图片的颜色通道。
其中code参数是颜色转换码,在OpenCV中有150多种可以使用,可以在cv.ColorConversionCodes中查询,比如COLOR_BGR2BGRA、COLOR_BGR2HSV、COLOR_BGR2HLS、COLOR_BayerBG2BGR_EA等等。
现在我们看看应用最广泛的RGB?Gray:
let src = cv.imread('canvasInput');
let dst = new cv.Mat();
cv.cvtColor(src, dst, cv.COLOR_RGBA2GRAY, 0);
cv.imshow('canvasOutput', dst);
src.delete(); dst.delete();
inRange
cv.inRange (src, lowerb, upperb, dst)检查颜色是否在范围内。
几何变换
- 对图片应用不同的几何变换,比如平移、旋转、仿射变换等等
缩放
缩放其实就是改变图片大小,OpenCV中用cv.resize (src, dst, dsize, fx = 0, fy = 0, interpolation = cv.INTER_LINEAR)实现缩放,图片大小可以手动填写任意值,或者是缩放系数。
let src = cv.imread('imageUpload');
let dst = new cv.Mat();
let dsize = new cv.Size(300, 80);
cv.resize(src, dst, dsize, 0INTER_AREA);
cv.imshow('canvasOutput', dst);
src.delete(); dst.delete();
这里我们通过直接设置图片宽高的方式,把600x473的图片,变成了300x80。
平移
cv.warpAffine (src, dst, M, dsize, flags = cv.INTER_LINEAR, borderMode = cv.BORDER_CONSTANT, borderValue = new cv.Scalar())
let src = cv.imread('imageUpload');
let dst = new cv.Mat();
let M = cv.matFromArray(2, 3, cv.CV_64FC1, [1, 0, 50, 0, 1, 100]);
let dsize = new cv.Size(src.cols, src.rows);
cv.warpAffine(src, dst, M, dsize, cv.INTER_LINEAR, cv.BORDER_CONSTANT, new cv.Scalar());
cv.imshow('canvasOutput', dst);
src.delete(); dst.delete(); M.delete();
可以看到,dsize设置为和输入图片宽高一致,M代表2x3的矩阵,水平移动50,垂直移动100。
旋转
和平移一样,也是warpAffine函数,只是M代表的矩阵不同,这里用cv.getRotationMatrix2D(center, angle, scale)计算出旋转矩阵,比如下面以图片中心为旋转点,旋转45°:
let src = cv.imread('imageUpload');
let dst = new cv.Mat();
let dsize = new cv.Size(src.cols, src.rows);
let center = new cv.Point(src.cols / 2, src.rows / 2);
let M = cv.getRotationMatrix2D(center, 45, 1);
cv.warpAffine(src, dst, M, dsize, cv.INTER_LINEAR, cv.BORDER_CONSTANT, new cv.Scalar());
cv.imshow('canvasOutput', dst);
src.delete(); dst.delete(); M.delete();
仿射变换
通过cv.getAffineTransform (src, dst)得到仿射变换的矩阵M,然后调用warpAffine函数。
getAffineTransform需要输入图像中的3个点在输出图像中的对应点,比如下面的srcTri中的3个点[0, 0, 0, 1, 1, 0]对应dstTri中的[0.6, 0.2, 0.1, 1.3, 1.5, 0.3]。
let src = cv.imread('imageUpload');
let dst = new cv.Mat();
let srcTri = cv.matFromArray(3, 1, cv.CV_32FC2, [0, 0, 0, 1, 1, 0]);
let dstTri = cv.matFromArray(3, 1, cv.CV_32FC2, [0.6, 0.2, 0.1, 1.3, 1.5, 0.3]);
let M = cv.getAffineTransform(srcTri, dstTri);
let dsize = new cv.Size(src.cols, src.rows);
cv.warpAffine(src, dst, M, dsize, cv.INTER_LINEAR, cv.BORDER_CONSTANT, new cv.Scalar());
cv.imshow('canvasOutput', dst);
src.delete(); dst.delete(); M.delete(); srcTri.delete(); dstTri.delete();
图像阈值化
cv.threshold(src, dst, thresh, maxval, type)
如果像素值大于阈值,它被赋一个值(可能是白色),否则被赋另一个值(可能是黑色)。
图像模糊
- 自定义滤波
- 低通滤波模糊图片
卷积滤波
和一维信号一样,图像也可以用各种低通滤波器(LPF)、高通滤波器(HPF)等进行滤波。
LPF有助于去除噪声,模糊图像等。HPF有助于在图像中找到边缘。
OpenCV提供cv.filter2D(src, dst, ddepth, kernel[, anchor[, delta[, borderType]]])函数来对图片进行卷积核运算。关于卷积,可以看看之前写的《卷积在前端图像处理上的应用》,比如前文的模糊卷积核:
const kernel = [1 / 9, 1 / 9, 1 / 9,
1 / 9, 1 / 9, 1 / 9,
1 / 9, 1 / 9, 1 / 9]; // 模糊卷积核
这里可以这么用:
let src = cv.imread('imageUpload');
let dst = new cv.Mat();
let M = cv.matFromArray(3, 3, cv.CV_64FC1, [1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9]);
cv.filter2D(src, dst, cv.CV_8U, M);
cv.imshow('canvasOutput', dst);
src.delete(); dst.delete(); M.delete();
下面看看OpenCV内置的4个模糊处理。
均值模糊
上面例子中的模糊卷积核,OpenCV提供了cv.blur()函数直接调用。下面的两种写法效果一致。
// 运用filter2D
let M = cv.matFromArray(3, 3, cv.CV_64FC1, [1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9]);
cv.filter2D(src, dst, cv.CV_8U, M);
// 运用blur
let ksize = new cv.Size(3, 3);
cv.blur(src, dst, ksize);
高斯模糊
cv.GaussianBlur(src, dst, ksize, sigmaX[, sigmaY[, borderType]])
不同于均值模糊直接取周围像素的平均值,高斯模糊取像素周围的高斯加权平均值。
let src = cv.imread('imageUpload');
let dst = new cv.Mat();
let ksize = new cv.Size(3, 3);
cv.GaussianBlur(src, dst, ksize, 0);
cv.imshow('canvasOutput', dst);
src.delete(); dst.delete();
中值模糊
cv.medianBlur (src, dst, ksize)
中值模糊取核内所有像素的中值,中心元素被替换为这个中值。
这对图像中的椒盐噪声非常有效。
上面的过滤器,中心元素是一个新计算的值,它可能是图像中的一个像素值,也可能是一个新值。但在中值模糊中,中心元素总是被图像中的某个像素值所替代。它有效地降低了噪声。
let src = cv.imread('imageUpload');
let dst = new cv.Mat();
cv.medianBlur(src, dst, 5);
cv.imshow('canvasOutput', dst);
src.delete(); dst.delete();
双边滤波
cv.bilateralFilter(src, dst, d, sigmaColor, sigmaSpace[, borderType])
双边滤波在保持边缘锐利的同时对去除噪音非常有效。
但与其他过滤器相比,该操作较慢。
比如高斯滤波器取像素周围的一个邻域并找到它的高斯加权平均值,在滤波时考虑附近的像素,但不考虑像素是否具有几乎相同的强度,不考虑像素是否是边缘像素。所以边缘也会模糊,这不是我们想要的。
双边滤波器实际上是2个高斯滤波器组成,一个对周围像素进行模糊,一个确保只有强度差别不大的像素会被模糊处理,所以能在模糊的同时保留边缘。
let src = cv.imread('imageUpload');
let dst = new cv.Mat();
cv.cvtColor(src, src, cv.COLOR_RGBA2RGB, 0);
cv.bilateralFilter(src, dst, 9, 75, 75);
cv.imshow('canvasOutput', dst);
src.delete(); dst.delete();
形态变换
形态变换是对图片形状的简单运算。它通常在二进制图像上执行。
它需要两个输入,一个是我们的原始图像,另一个是卷积核。
两个基本的形态学运算是侵蚀和膨胀。然后它的进阶形式有打开,关闭,梯度等。
侵蚀
cv.erode(src, dst, kernel[, anchor[, iterations[, borderType[, borderValue]]]])
卷积运算时,核内所有像素都为1时运算结果为1,否则为0。所以靠近边缘的像素都被丢弃,图像尺寸会变小。
let src = cv.imread('imageUpload');
let dst = new cv.Mat();
let M = cv.Mat.ones(5, 5, cv.CV_8U);
cv.erode(src, dst, M);
cv.imshow('canvasOutput', dst);
src.delete(); dst.delete(); M.delete();
膨胀
cv.dilate(src, dst, kernel[, anchor[, iterations[, borderType[, borderValue]]]])
与侵蚀相反,卷积运算时,核内有一个像素为1时,结果就为1,否则为0。所以图片尺寸会变大。
通常,在消除噪音时,侵蚀之后是膨胀,因为侵蚀消除了白噪音,但它也缩小了我们的目标,所以需要再放大。
let src = cv.imread('imageUpload');
let dst = new cv.Mat();
let M = cv.Mat.ones(5, 5, cv.CV_8U);
cv.dilate(src, dst, M);
cv.imshow('canvasOutput', dst);
src.delete(); dst.delete(); M.delete();
打开
cv.morphologyEx(src, dst, op, kernel[, anchor[, iterations[, borderType[, borderValue]]]])
打开只是侵蚀之后是再膨胀的另一个说法,常在去除噪音时使用。
let src = cv.imread('imageUpload');
let dst = new cv.Mat();
let M = cv.Mat.ones(5, 5, cv.CV_8U);
cv.morphologyEx(src, dst, cv.MORPH_OPEN, M);
cv.imshow('canvasOutput', dst);
src.delete(); dst.delete(); M.delete();
关闭
关闭和打开相反,是膨胀之后再侵蚀,对去除图片中的小黑点比较有用。
let src = cv.imread('imageUpload');
let dst = new cv.Mat();
let M = cv.Mat.ones(5, 5, cv.CV_8U);
cv.morphologyEx(src, dst, cv.MORPH_CLOSE, M);
cv.imshow('canvasOutput', dst);
src.delete(); dst.delete(); M.delete();
梯度
与侵蚀和膨胀不同,它的处理结果看起来是形状的边缘。
let src = cv.imread('imageUpload');
let dst = new cv.Mat();
let M = cv.Mat.ones(5, 5, cv.CV_8U);
cv.cvtColor(src, src, cv.COLOR_RGBA2RGB);
cv.morphologyEx(src, dst, cv.MORPH_GRADIENT, M);
cv.imshow('canvasOutput', dst);
src.delete(); dst.delete(); M.delete();
图像梯度
OpenCV提供3种图像梯度过滤器:Sobel、Scharr 和 Laplacian。
let src = cv.imread('imageUpload');
let dstx = new cv.Mat();
let dsty = new cv.Mat();
cv.cvtColor(src, src, cv.COLOR_RGB2GRAY, 0);
cv.Sobel(src, dstx, cv.CV_8U, 1, 0); // 下图一左
// cv.Sobel(src, dsty, cv.CV_8U, 0, 1); // 下图一右
// cv.Scharr(src, dstx, cv.CV_8U, 1, 0); // 下图二左
// cv.Scharr(src, dsty, cv.CV_8U, 0, 1); // 下图二右
cv.imshow('canvasOutput', dstx);
src.delete(); dstx.delete(); dsty.delete();
下面4张图分别是Sobel和Scharr算子取横向和纵向:
Laplacian使用的卷积核是:
const kernel = [0, 1, 0,
1, -4, 1,
0, 1, 0];
先灰度,再Laplacian:
cv.cvtColor(src, src, cv.COLOR_RGB2GRAY, 0);
cv.Laplacian(src, dst, cv.CV_8U);
注意:输出的cv.CV_8U数据类型会导致检测结果不准确,需要用其他格式,比如 cv.CV_16S, cv.CV_64F等等,然后取绝对值再输出。具体的可以参考官网。
Canny边缘检测
Canny边缘检测是一种流行的边缘检测算法,由John F. Canny在1986年开发。算法的实现及原理这里就不讲了,我们可以直接调用cv.Canny函数:
let src = cv.imread('imageUpload');
let dst = new cv.Mat();
cv.cvtColor(src, src, cv.COLOR_RGB2GRAY, 0);
cv.Canny(src, dst, 50, 100);
cv.imshow('canvasOutput', dst);
src.delete(); dst.delete();
图像金字塔
通常,我们使用固定大小的图像。但有时,我们需要使用不同分辨率的图像。
例如,当我们在一幅图像中搜索某物时,比如人脸,我们不确定人脸在图像中的大小。
这时我们需要创建一组具有不同分辨率的图像,并在所有图像中搜索对象。
这些不同分辨率的图像集合被称为图像金字塔(因为当它们被保存在一个堆栈中,高分辨率高的在底部,分辨率低的在顶部,它看起来像一个金字塔)。
1、降低分辨率
let src = cv.imread('imageUpload');
let dst = new cv.Mat();
cv.pyrDown(src, dst, new cv.Size(0, 0));
cv.imshow('canvasOutput', dst);
src.delete(); dst.delete();
2、提高分辨率
cv.pyrUp(src, dst, new cv.Size(0, 0));
傅里叶变换
傅里叶变换常用于图片的频域分析。
代码较多,这里就不详细分析了。
总结
上面列了OpenCV的一些基础的图像处理接口,比如几何变换、阈值化、形态变换等等,在实际应用中,通过这些接口,可以实现图片频域分析及处理、图像分割、图片匹配、图片轮廓或边缘检测等等。比如对图片频域加盲水印会用到傅里叶变换。
- 上一篇: Axure 8.0教程|实现环形动态进度条
- 下一篇: 自动化模式中的MySQL 自动化数据
猜你喜欢
- 2024-12-23 自动化模式中的MySQL 自动化数据
- 2024-12-23 Axure 8.0教程|实现环形动态进度条
- 2024-12-23 React 事件机制原理 react案例
- 2024-12-23 为何强烈推荐 Facebook 开源强大富文本编辑器 Draft.js ?
- 2024-12-23 重新认识受控和非受控组件 重新认识受控和非受控组件的关系
- 2024-12-23 HarmonyOS应用开发-常用组件与布局
- 2024-12-23 鸿蒙应用开发实战-常用组件-滑动条组件
- 2024-12-23 用户界面干货盘点 用户界面分哪两种?
- 2024-12-23 鸿蒙开发-Stepper 与 StepperItem 组件
- 2024-12-23 鸿蒙Navigation处理启动页跳转到首页问题
- 最近发表
- 标签列表
-
- gitpush (61)
- pythonif (68)
- location.href (57)
- tail-f (57)
- pythonifelse (59)
- deletesql (62)
- c++模板 (62)
- css3动画 (57)
- c#event (59)
- linuxgzip (68)
- 字符串连接 (73)
- nginx配置文件详解 (61)
- html标签 (69)
- c++初始化列表 (64)
- exec命令 (59)
- canvasfilltext (58)
- mysqlinnodbmyisam区别 (63)
- arraylistadd (66)
- node教程 (59)
- console.table (62)
- c++time_t (58)
- phpcookie (58)
- mysqldatesub函数 (63)
- window10java环境变量设置 (66)
- c++虚函数和纯虚函数的区别 (66)