第13章:图像处理基础
13.1 图像表示
13.1.1 数字图像的概念
数字图像是由离散的像素点组成的二维数组,每个像素存储颜色或灰度值。
数字图像的结构
图像可以看作一个二维矩阵 I(x, y):
x(列)
0 1 2 3 4 ... width-1
┌───┬───┬───┬───┬───┬─────────────┐
0 │ p │ p │ p │ p │ p │ ... │
├───┼───┼───┼───┼───┼─────────────┤
1 │ p │ p │ p │ p │ p │ ... │
├───┼───┼───┼───┼───┼─────────────┤
y │ p │ p │ p │ p │ p │ ... │
├───┼───┼───┼───┼───┼─────────────┤
│ . │ . │ . │ . │ . │ │
│ . │ . │ . │ . │ . │ │
└───┴───┴───┴───┴───┴─────────────┘
height-1
像素格式:
1. 灰度图(Grayscale)
每个像素一个值:0-255
0 = 黑,255 = 白
2. RGB 彩色图
每个像素三个值:R, G, B
每个通道 0-255
3. RGBA
RGB + Alpha(透明度)
4 个通道
4. 其他格式
- HSV/HSL(色相、饱和度、亮度)
- YUV/YCbCr(亮度 + 色度)
- CMYK(印刷用)13.1.2 图像存储
javascript
/**
* 图像类
*/
class Image {
constructor(width, height, channels = 4) {
this.width = width;
this.height = height;
this.channels = channels;
this.data = new Uint8ClampedArray(width * height * channels);
}
/**
* 获取像素
*/
getPixel(x, y) {
if (x < 0 || x >= this.width || y < 0 || y >= this.height) {
return null;
}
const i = (y * this.width + x) * this.channels;
if (this.channels === 1) {
return this.data[i];
} else if (this.channels === 3) {
return {
r: this.data[i],
g: this.data[i + 1],
b: this.data[i + 2]
};
} else {
return {
r: this.data[i],
g: this.data[i + 1],
b: this.data[i + 2],
a: this.data[i + 3]
};
}
}
/**
* 设置像素
*/
setPixel(x, y, color) {
if (x < 0 || x >= this.width || y < 0 || y >= this.height) {
return;
}
const i = (y * this.width + x) * this.channels;
if (this.channels === 1) {
this.data[i] = color;
} else if (this.channels === 3) {
this.data[i] = color.r;
this.data[i + 1] = color.g;
this.data[i + 2] = color.b;
} else {
this.data[i] = color.r;
this.data[i + 1] = color.g;
this.data[i + 2] = color.b;
this.data[i + 3] = color.a ?? 255;
}
}
/**
* 从 Canvas 创建
*/
static fromCanvas(canvas) {
const ctx = canvas.getContext('2d');
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const img = new Image(canvas.width, canvas.height, 4);
img.data.set(imageData.data);
return img;
}
/**
* 导出为 ImageData
*/
toImageData() {
if (this.channels === 4) {
return new ImageData(
new Uint8ClampedArray(this.data),
this.width,
this.height
);
}
// 转换为 RGBA
const rgba = new Uint8ClampedArray(this.width * this.height * 4);
for (let i = 0; i < this.width * this.height; i++) {
if (this.channels === 1) {
const v = this.data[i];
rgba[i * 4] = v;
rgba[i * 4 + 1] = v;
rgba[i * 4 + 2] = v;
rgba[i * 4 + 3] = 255;
} else if (this.channels === 3) {
rgba[i * 4] = this.data[i * 3];
rgba[i * 4 + 1] = this.data[i * 3 + 1];
rgba[i * 4 + 2] = this.data[i * 3 + 2];
rgba[i * 4 + 3] = 255;
}
}
return new ImageData(rgba, this.width, this.height);
}
/**
* 克隆图像
*/
clone() {
const img = new Image(this.width, this.height, this.channels);
img.data.set(this.data);
return img;
}
}13.2 点操作
13.2.1 点操作的概念
点操作(Point Operations)是指只根据当前像素的值来计算新值,不考虑邻近像素。
点操作特点
输入像素 → 变换函数 → 输出像素
常见点操作:
- 亮度调整
- 对比度调整
- 反相
- 阈值化
- 伽马校正
图示:
输入图像 输出图像
┌─────────┐ ┌─────────┐
│ A B C D │ f(x) │f(A)f(B)...│
│ E F G H │ ──────► │f(E)f(F)...│
│ I J K L │ │f(I)f(J)...│
└─────────┘ └─────────┘
每个像素独立处理13.2.2 点操作实现
javascript
/**
* 点操作集合
*/
class PointOperations {
/**
* 亮度调整
* @param offset 亮度偏移 (-255 到 255)
*/
static brightness(image, offset) {
const result = image.clone();
for (let i = 0; i < result.data.length; i++) {
// 跳过 alpha 通道
if (result.channels === 4 && (i + 1) % 4 === 0) continue;
result.data[i] = Math.max(0, Math.min(255, result.data[i] + offset));
}
return result;
}
/**
* 对比度调整
* @param factor 对比度因子 (0 到 2+,1 为原始)
*/
static contrast(image, factor) {
const result = image.clone();
for (let i = 0; i < result.data.length; i++) {
if (result.channels === 4 && (i + 1) % 4 === 0) continue;
// (pixel - 128) * factor + 128
const value = (result.data[i] - 128) * factor + 128;
result.data[i] = Math.max(0, Math.min(255, value));
}
return result;
}
/**
* 反相
*/
static invert(image) {
const result = image.clone();
for (let i = 0; i < result.data.length; i++) {
if (result.channels === 4 && (i + 1) % 4 === 0) continue;
result.data[i] = 255 - result.data[i];
}
return result;
}
/**
* 阈值化
* @param threshold 阈值 (0-255)
*/
static threshold(image, threshold) {
const result = new Image(image.width, image.height, 1);
for (let y = 0; y < image.height; y++) {
for (let x = 0; x < image.width; x++) {
const pixel = image.getPixel(x, y);
// 计算亮度
let brightness;
if (typeof pixel === 'number') {
brightness = pixel;
} else {
brightness = 0.299 * pixel.r + 0.587 * pixel.g + 0.114 * pixel.b;
}
result.setPixel(x, y, brightness > threshold ? 255 : 0);
}
}
return result;
}
/**
* 伽马校正
* @param gamma 伽马值 (通常 0.4 到 2.5)
*/
static gammaCorrection(image, gamma) {
const result = image.clone();
// 预计算查找表
const lut = new Uint8Array(256);
const invGamma = 1 / gamma;
for (let i = 0; i < 256; i++) {
lut[i] = Math.round(255 * Math.pow(i / 255, invGamma));
}
for (let i = 0; i < result.data.length; i++) {
if (result.channels === 4 && (i + 1) % 4 === 0) continue;
result.data[i] = lut[result.data[i]];
}
return result;
}
/**
* 直方图均衡化
*/
static histogramEqualization(image) {
const result = image.clone();
const totalPixels = image.width * image.height;
// 计算直方图(对灰度或亮度)
const histogram = new Array(256).fill(0);
for (let y = 0; y < image.height; y++) {
for (let x = 0; x < image.width; x++) {
const pixel = image.getPixel(x, y);
let brightness;
if (typeof pixel === 'number') {
brightness = pixel;
} else {
brightness = Math.round(0.299 * pixel.r + 0.587 * pixel.g + 0.114 * pixel.b);
}
histogram[brightness]++;
}
}
// 计算累积分布函数 (CDF)
const cdf = new Array(256).fill(0);
cdf[0] = histogram[0];
for (let i = 1; i < 256; i++) {
cdf[i] = cdf[i - 1] + histogram[i];
}
// 找到 CDF 最小非零值
let cdfMin = 0;
for (let i = 0; i < 256; i++) {
if (cdf[i] > 0) {
cdfMin = cdf[i];
break;
}
}
// 创建映射
const lut = new Uint8Array(256);
for (let i = 0; i < 256; i++) {
lut[i] = Math.round((cdf[i] - cdfMin) / (totalPixels - cdfMin) * 255);
}
// 应用映射
for (let y = 0; y < result.height; y++) {
for (let x = 0; x < result.width; x++) {
const pixel = result.getPixel(x, y);
if (typeof pixel === 'number') {
result.setPixel(x, y, lut[pixel]);
} else {
result.setPixel(x, y, {
r: lut[pixel.r],
g: lut[pixel.g],
b: lut[pixel.b],
a: pixel.a
});
}
}
}
return result;
}
}13.3 卷积操作
13.3.1 卷积的概念
卷积(Convolution)是图像处理中最基本的邻域操作,用一个核(Kernel)在图像上滑动,计算加权和。
卷积运算
核(Kernel)/ 卷积矩阵:
┌───┬───┬───┐
│ a │ b │ c │
├───┼───┼───┤
│ d │ e │ f │ 3×3 核
├───┼───┼───┤
│ g │ h │ i │
└───┴───┴───┘
卷积过程:
图像区域: 核:
┌───┬───┬───┐ ┌───┬───┬───┐
│ p1│ p2│ p3│ │ k1│ k2│ k3│
├───┼───┼───┤ * ├───┼───┼───┤
│ p4│ p5│ p6│ │ k4│ k5│ k6│
├───┼───┼───┤ ├───┼───┼───┤
│ p7│ p8│ p9│ │ k7│ k8│ k9│
└───┴───┴───┘ └───┴───┴───┘
结果 = p1×k1 + p2×k2 + p3×k3 +
p4×k4 + p5×k5 + p6×k6 +
p7×k7 + p8×k8 + p9×k9
边界处理:
1. 零填充(Zero Padding)
2. 边界复制(Replicate)
3. 镜像(Mirror)
4. 环绕(Wrap)13.3.2 卷积实现
javascript
/**
* 卷积操作
*/
class Convolution {
/**
* 执行卷积
* @param image 输入图像
* @param kernel 卷积核(一维数组,按行存储)
* @param kernelSize 核大小
*/
static convolve(image, kernel, kernelSize) {
const result = new Image(image.width, image.height, image.channels);
const half = Math.floor(kernelSize / 2);
for (let y = 0; y < image.height; y++) {
for (let x = 0; x < image.width; x++) {
const sum = image.channels === 1 ? 0 : { r: 0, g: 0, b: 0 };
// 遍历核
for (let ky = 0; ky < kernelSize; ky++) {
for (let kx = 0; kx < kernelSize; kx++) {
const px = x + kx - half;
const py = y + ky - half;
// 边界处理:复制边界
const clampedX = Math.max(0, Math.min(image.width - 1, px));
const clampedY = Math.max(0, Math.min(image.height - 1, py));
const pixel = image.getPixel(clampedX, clampedY);
const weight = kernel[ky * kernelSize + kx];
if (typeof pixel === 'number') {
sum += pixel * weight;
} else {
sum.r += pixel.r * weight;
sum.g += pixel.g * weight;
sum.b += pixel.b * weight;
}
}
}
// 设置结果
if (typeof sum === 'number') {
result.setPixel(x, y, Math.max(0, Math.min(255, Math.round(sum))));
} else {
result.setPixel(x, y, {
r: Math.max(0, Math.min(255, Math.round(sum.r))),
g: Math.max(0, Math.min(255, Math.round(sum.g))),
b: Math.max(0, Math.min(255, Math.round(sum.b))),
a: 255
});
}
}
}
return result;
}
/**
* 可分离卷积(更高效)
*/
static separableConvolve(image, kernelX, kernelY) {
// 先水平卷积
let temp = this.convolve1D(image, kernelX, true);
// 再垂直卷积
return this.convolve1D(temp, kernelY, false);
}
static convolve1D(image, kernel, horizontal) {
const result = new Image(image.width, image.height, image.channels);
const half = Math.floor(kernel.length / 2);
for (let y = 0; y < image.height; y++) {
for (let x = 0; x < image.width; x++) {
const sum = image.channels === 1 ? 0 : { r: 0, g: 0, b: 0 };
for (let k = 0; k < kernel.length; k++) {
let px, py;
if (horizontal) {
px = x + k - half;
py = y;
} else {
px = x;
py = y + k - half;
}
px = Math.max(0, Math.min(image.width - 1, px));
py = Math.max(0, Math.min(image.height - 1, py));
const pixel = image.getPixel(px, py);
const weight = kernel[k];
if (typeof pixel === 'number') {
sum += pixel * weight;
} else {
sum.r += pixel.r * weight;
sum.g += pixel.g * weight;
sum.b += pixel.b * weight;
}
}
if (typeof sum === 'number') {
result.setPixel(x, y, Math.max(0, Math.min(255, Math.round(sum))));
} else {
result.setPixel(x, y, {
r: Math.max(0, Math.min(255, Math.round(sum.r))),
g: Math.max(0, Math.min(255, Math.round(sum.g))),
b: Math.max(0, Math.min(255, Math.round(sum.b))),
a: 255
});
}
}
}
return result;
}
}13.4 滤波
13.4.1 常用滤波器
常用滤波核
1. 均值滤波(Box Blur)
┌───┬───┬───┐
│1/9│1/9│1/9│
├───┼───┼───┤
│1/9│1/9│1/9│
├───┼───┼───┤
│1/9│1/9│1/9│
└───┴───┴───┘
效果:模糊,去噪
2. 高斯滤波
┌────┬────┬────┐
│1/16│2/16│1/16│
├────┼────┼────┤
│2/16│4/16│2/16│ (近似)
├────┼────┼────┤
│1/16│2/16│1/16│
└────┴────┴────┘
效果:平滑,保持边缘
3. 锐化
┌───┬───┬───┐
│ 0 │-1 │ 0 │
├───┼───┼───┤
│-1 │ 5 │-1 │
├───┼───┼───┤
│ 0 │-1 │ 0 │
└───┴───┴───┘
效果:增强边缘
4. Sobel 边缘检测(水平)
┌───┬───┬───┐
│-1 │ 0 │ 1 │
├───┼───┼───┤
│-2 │ 0 │ 2 │
├───┼───┼───┤
│-1 │ 0 │ 1 │
└───┴───┴───┘
效果:检测垂直边缘13.4.2 滤波器实现
javascript
/**
* 图像滤波器
*/
class ImageFilters {
/**
* 均值模糊
*/
static boxBlur(image, size = 3) {
const kernel = new Array(size * size).fill(1 / (size * size));
return Convolution.convolve(image, kernel, size);
}
/**
* 高斯模糊
*/
static gaussianBlur(image, radius = 1, sigma = null) {
if (sigma === null) sigma = radius / 2;
const size = radius * 2 + 1;
const kernel = [];
let sum = 0;
// 生成高斯核
for (let y = -radius; y <= radius; y++) {
for (let x = -radius; x <= radius; x++) {
const value = Math.exp(-(x * x + y * y) / (2 * sigma * sigma));
kernel.push(value);
sum += value;
}
}
// 归一化
for (let i = 0; i < kernel.length; i++) {
kernel[i] /= sum;
}
return Convolution.convolve(image, kernel, size);
}
/**
* 高斯模糊(可分离版本,更快)
*/
static gaussianBlurFast(image, radius = 1, sigma = null) {
if (sigma === null) sigma = radius / 2;
const size = radius * 2 + 1;
const kernel1D = [];
let sum = 0;
for (let i = -radius; i <= radius; i++) {
const value = Math.exp(-(i * i) / (2 * sigma * sigma));
kernel1D.push(value);
sum += value;
}
for (let i = 0; i < kernel1D.length; i++) {
kernel1D[i] /= sum;
}
return Convolution.separableConvolve(image, kernel1D, kernel1D);
}
/**
* 锐化
*/
static sharpen(image, amount = 1) {
const kernel = [
0, -amount, 0,
-amount, 1 + 4 * amount, -amount,
0, -amount, 0
];
return Convolution.convolve(image, kernel, 3);
}
/**
* Sobel 边缘检测
*/
static sobel(image) {
// 转为灰度
const gray = this.grayscale(image);
// Sobel X
const kernelX = [
-1, 0, 1,
-2, 0, 2,
-1, 0, 1
];
// Sobel Y
const kernelY = [
-1, -2, -1,
0, 0, 0,
1, 2, 1
];
const gx = Convolution.convolve(gray, kernelX, 3);
const gy = Convolution.convolve(gray, kernelY, 3);
// 计算梯度幅值
const result = new Image(image.width, image.height, 1);
for (let y = 0; y < image.height; y++) {
for (let x = 0; x < image.width; x++) {
const px = gx.getPixel(x, y);
const py = gy.getPixel(x, y);
const magnitude = Math.sqrt(px * px + py * py);
result.setPixel(x, y, Math.min(255, magnitude));
}
}
return result;
}
/**
* 拉普拉斯边缘检测
*/
static laplacian(image) {
const gray = this.grayscale(image);
const kernel = [
0, 1, 0,
1, -4, 1,
0, 1, 0
];
return Convolution.convolve(gray, kernel, 3);
}
/**
* 转为灰度
*/
static grayscale(image) {
const result = new Image(image.width, image.height, 1);
for (let y = 0; y < image.height; y++) {
for (let x = 0; x < image.width; x++) {
const pixel = image.getPixel(x, y);
if (typeof pixel === 'number') {
result.setPixel(x, y, pixel);
} else {
// 加权平均
const gray = Math.round(
0.299 * pixel.r + 0.587 * pixel.g + 0.114 * pixel.b
);
result.setPixel(x, y, gray);
}
}
}
return result;
}
/**
* 中值滤波(去噪)
*/
static median(image, size = 3) {
const result = new Image(image.width, image.height, image.channels);
const half = Math.floor(size / 2);
for (let y = 0; y < image.height; y++) {
for (let x = 0; x < image.width; x++) {
const values = image.channels === 1 ? [] : { r: [], g: [], b: [] };
// 收集邻域像素
for (let ky = -half; ky <= half; ky++) {
for (let kx = -half; kx <= half; kx++) {
const px = Math.max(0, Math.min(image.width - 1, x + kx));
const py = Math.max(0, Math.min(image.height - 1, y + ky));
const pixel = image.getPixel(px, py);
if (typeof pixel === 'number') {
values.push(pixel);
} else {
values.r.push(pixel.r);
values.g.push(pixel.g);
values.b.push(pixel.b);
}
}
}
// 取中值
if (Array.isArray(values)) {
values.sort((a, b) => a - b);
result.setPixel(x, y, values[Math.floor(values.length / 2)]);
} else {
values.r.sort((a, b) => a - b);
values.g.sort((a, b) => a - b);
values.b.sort((a, b) => a - b);
const mid = Math.floor(values.r.length / 2);
result.setPixel(x, y, {
r: values.r[mid],
g: values.g[mid],
b: values.b[mid],
a: 255
});
}
}
}
return result;
}
}13.5 形态学操作
13.5.1 形态学基础
形态学操作
形态学操作主要用于二值图像和灰度图像的形状处理。
基本操作:
1. 腐蚀(Erosion)
- 缩小白色区域
- 去除小的白色噪点
原图: 腐蚀后:
┌─────────────┐ ┌─────────────┐
│ ██████ │ │ ████ │
│ █████████ │ → │ ███████ │
│ ██████ │ │ ████ │
└─────────────┘ └─────────────┘
2. 膨胀(Dilation)
- 扩大白色区域
- 填充小的黑色空洞
原图: 膨胀后:
┌─────────────┐ ┌─────────────┐
│ ████ │ │ ██████ │
│ ███████ │ → │ █████████ │
│ ████ │ │ ██████ │
└─────────────┘ └─────────────┘
3. 开运算(Opening)= 腐蚀 + 膨胀
- 去除噪点,保持形状
4. 闭运算(Closing)= 膨胀 + 腐蚀
- 填充孔洞,保持形状13.5.2 形态学实现
javascript
/**
* 形态学操作
*/
class Morphology {
/**
* 创建结构元素
*/
static createStructuringElement(shape, size) {
const element = [];
const half = Math.floor(size / 2);
for (let y = -half; y <= half; y++) {
for (let x = -half; x <= half; x++) {
if (shape === 'rect') {
element.push({ x, y });
} else if (shape === 'cross') {
if (x === 0 || y === 0) {
element.push({ x, y });
}
} else if (shape === 'ellipse') {
if (x * x + y * y <= half * half) {
element.push({ x, y });
}
}
}
}
return element;
}
/**
* 腐蚀
*/
static erode(image, structElement) {
const result = new Image(image.width, image.height, 1);
for (let y = 0; y < image.height; y++) {
for (let x = 0; x < image.width; x++) {
let minValue = 255;
for (const offset of structElement) {
const px = x + offset.x;
const py = y + offset.y;
if (px >= 0 && px < image.width && py >= 0 && py < image.height) {
const pixel = image.getPixel(px, py);
const value = typeof pixel === 'number' ? pixel : pixel.r;
minValue = Math.min(minValue, value);
} else {
minValue = 0;
}
}
result.setPixel(x, y, minValue);
}
}
return result;
}
/**
* 膨胀
*/
static dilate(image, structElement) {
const result = new Image(image.width, image.height, 1);
for (let y = 0; y < image.height; y++) {
for (let x = 0; x < image.width; x++) {
let maxValue = 0;
for (const offset of structElement) {
const px = x + offset.x;
const py = y + offset.y;
if (px >= 0 && px < image.width && py >= 0 && py < image.height) {
const pixel = image.getPixel(px, py);
const value = typeof pixel === 'number' ? pixel : pixel.r;
maxValue = Math.max(maxValue, value);
}
}
result.setPixel(x, y, maxValue);
}
}
return result;
}
/**
* 开运算 = 腐蚀 + 膨胀
*/
static opening(image, structElement) {
const eroded = this.erode(image, structElement);
return this.dilate(eroded, structElement);
}
/**
* 闭运算 = 膨胀 + 腐蚀
*/
static closing(image, structElement) {
const dilated = this.dilate(image, structElement);
return this.erode(dilated, structElement);
}
/**
* 形态学梯度 = 膨胀 - 腐蚀
*/
static gradient(image, structElement) {
const dilated = this.dilate(image, structElement);
const eroded = this.erode(image, structElement);
const result = new Image(image.width, image.height, 1);
for (let y = 0; y < image.height; y++) {
for (let x = 0; x < image.width; x++) {
const d = dilated.getPixel(x, y);
const e = eroded.getPixel(x, y);
result.setPixel(x, y, Math.max(0, d - e));
}
}
return result;
}
}13.6 图像缩放
13.6.1 缩放算法
图像缩放算法
1. 最近邻插值
- 选择最近的像素
- 快速但有锯齿
2. 双线性插值
- 4 个相邻像素的加权平均
- 平滑但可能模糊
3. 双三次插值
- 16 个相邻像素
- 更锐利,计算量大
4. Lanczos
- 使用 sinc 函数
- 高质量,计算量最大13.6.2 缩放实现
javascript
/**
* 图像缩放
*/
class ImageResize {
/**
* 最近邻插值
*/
static nearest(image, newWidth, newHeight) {
const result = new Image(newWidth, newHeight, image.channels);
const scaleX = image.width / newWidth;
const scaleY = image.height / newHeight;
for (let y = 0; y < newHeight; y++) {
for (let x = 0; x < newWidth; x++) {
const srcX = Math.floor(x * scaleX);
const srcY = Math.floor(y * scaleY);
const pixel = image.getPixel(srcX, srcY);
result.setPixel(x, y, pixel);
}
}
return result;
}
/**
* 双线性插值
*/
static bilinear(image, newWidth, newHeight) {
const result = new Image(newWidth, newHeight, image.channels);
const scaleX = (image.width - 1) / (newWidth - 1);
const scaleY = (image.height - 1) / (newHeight - 1);
for (let y = 0; y < newHeight; y++) {
for (let x = 0; x < newWidth; x++) {
const srcX = x * scaleX;
const srcY = y * scaleY;
const x0 = Math.floor(srcX);
const y0 = Math.floor(srcY);
const x1 = Math.min(x0 + 1, image.width - 1);
const y1 = Math.min(y0 + 1, image.height - 1);
const tx = srcX - x0;
const ty = srcY - y0;
const c00 = image.getPixel(x0, y0);
const c10 = image.getPixel(x1, y0);
const c01 = image.getPixel(x0, y1);
const c11 = image.getPixel(x1, y1);
if (typeof c00 === 'number') {
const c0 = c00 * (1 - tx) + c10 * tx;
const c1 = c01 * (1 - tx) + c11 * tx;
result.setPixel(x, y, Math.round(c0 * (1 - ty) + c1 * ty));
} else {
const lerp = (a, b, t) => a * (1 - t) + b * t;
result.setPixel(x, y, {
r: Math.round(lerp(lerp(c00.r, c10.r, tx), lerp(c01.r, c11.r, tx), ty)),
g: Math.round(lerp(lerp(c00.g, c10.g, tx), lerp(c01.g, c11.g, tx), ty)),
b: Math.round(lerp(lerp(c00.b, c10.b, tx), lerp(c01.b, c11.b, tx), ty)),
a: Math.round(lerp(lerp(c00.a, c10.a, tx), lerp(c01.a, c11.a, tx), ty))
});
}
}
}
return result;
}
}13.7 本章小结
图像处理操作分类
| 类型 | 操作 | 特点 |
|---|---|---|
| 点操作 | 亮度、对比度、伽马 | 独立处理每个像素 |
| 邻域操作 | 卷积、滤波 | 使用相邻像素 |
| 形态学 | 腐蚀、膨胀 | 形状处理 |
| 几何操作 | 缩放、旋转 | 坐标变换 |
常用滤波核
| 滤波器 | 效果 | 应用 |
|---|---|---|
| 均值 | 模糊 | 去噪 |
| 高斯 | 平滑 | 预处理 |
| 锐化 | 增强边缘 | 细节增强 |
| Sobel | 检测边缘 | 边缘提取 |
| 中值 | 去噪 | 椒盐噪声 |
关键要点
- 点操作只涉及单个像素,速度快
- 卷积是图像处理的基础操作
- 可分离卷积可以大幅提高效率
- 形态学操作用于二值图像的形状处理
- 图像缩放需要插值算法保证质量
下一章预告:在第14章中,我们将学习颜色科学,包括颜色空间、颜色转换和色彩管理。
文档版本:v1.0
字数统计:约 10,000 字
