【OpenCV 入门到精通 11】机器学习应用:KNN、SVM 与 K-Means 实战
标签 :
OpenCVKNNSVMK-Means机器学习OCR难度 :⭐⭐⭐⭐ | 阅读时长 :约 60 分钟 | 系列 :第 11/12 集

写在前面
深度学习之前,传统机器学习 是 CV 的主流方案。OpenCV 内置了 KNN、SVM、K-Means 等经典算法,至今仍在 OCR、简单分类等场景有用。本集对应官方 第8章 机器学习。
本集学习目标
- 使用 KNN 进行手写数字识别
- 使用 SVM 进行分类
- 使用 K-Means 进行图像分割
- 理解传统 ML 与深度学习的适用边界
文章目录
- [【OpenCV 入门到精通 11】机器学习应用:KNN、SVM 与 K-Means 实战](#【OpenCV 入门到精通 11】机器学习应用:KNN、SVM 与 K-Means 实战)
11.1 OpenCV ML 模块
| 算法 | 类 | 用途 |
|---|---|---|
| KNN | cv2.ml.KNearest_create() |
最近邻分类 |
| SVM | cv2.ml.SVM_create() |
支持向量机 |
| 决策树 | cv2.ml.DTrees_create() |
决策树 |
| 随机森林 | cv2.ml.RTrees_create() |
集成学习 |
| 贝叶斯 | cv2.ml.NormalBayesClassifier_create() |
朴素贝叶斯 |
| K-Means | cv2.kmeans() |
聚类 |
通用流程
#mermaid-svg-Ks1HSrQfksnZtHak{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Ks1HSrQfksnZtHak .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Ks1HSrQfksnZtHak .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Ks1HSrQfksnZtHak .error-icon{fill:#552222;}#mermaid-svg-Ks1HSrQfksnZtHak .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Ks1HSrQfksnZtHak .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Ks1HSrQfksnZtHak .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Ks1HSrQfksnZtHak .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Ks1HSrQfksnZtHak .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Ks1HSrQfksnZtHak .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Ks1HSrQfksnZtHak .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Ks1HSrQfksnZtHak .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Ks1HSrQfksnZtHak .marker.cross{stroke:#333333;}#mermaid-svg-Ks1HSrQfksnZtHak svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Ks1HSrQfksnZtHak p{margin:0;}#mermaid-svg-Ks1HSrQfksnZtHak .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Ks1HSrQfksnZtHak .cluster-label text{fill:#333;}#mermaid-svg-Ks1HSrQfksnZtHak .cluster-label span{color:#333;}#mermaid-svg-Ks1HSrQfksnZtHak .cluster-label span p{background-color:transparent;}#mermaid-svg-Ks1HSrQfksnZtHak .label text,#mermaid-svg-Ks1HSrQfksnZtHak span{fill:#333;color:#333;}#mermaid-svg-Ks1HSrQfksnZtHak .node rect,#mermaid-svg-Ks1HSrQfksnZtHak .node circle,#mermaid-svg-Ks1HSrQfksnZtHak .node ellipse,#mermaid-svg-Ks1HSrQfksnZtHak .node polygon,#mermaid-svg-Ks1HSrQfksnZtHak .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Ks1HSrQfksnZtHak .rough-node .label text,#mermaid-svg-Ks1HSrQfksnZtHak .node .label text,#mermaid-svg-Ks1HSrQfksnZtHak .image-shape .label,#mermaid-svg-Ks1HSrQfksnZtHak .icon-shape .label{text-anchor:middle;}#mermaid-svg-Ks1HSrQfksnZtHak .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Ks1HSrQfksnZtHak .rough-node .label,#mermaid-svg-Ks1HSrQfksnZtHak .node .label,#mermaid-svg-Ks1HSrQfksnZtHak .image-shape .label,#mermaid-svg-Ks1HSrQfksnZtHak .icon-shape .label{text-align:center;}#mermaid-svg-Ks1HSrQfksnZtHak .node.clickable{cursor:pointer;}#mermaid-svg-Ks1HSrQfksnZtHak .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Ks1HSrQfksnZtHak .arrowheadPath{fill:#333333;}#mermaid-svg-Ks1HSrQfksnZtHak .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Ks1HSrQfksnZtHak .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Ks1HSrQfksnZtHak .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Ks1HSrQfksnZtHak .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Ks1HSrQfksnZtHak .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Ks1HSrQfksnZtHak .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Ks1HSrQfksnZtHak .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Ks1HSrQfksnZtHak .cluster text{fill:#333;}#mermaid-svg-Ks1HSrQfksnZtHak .cluster span{color:#333;}#mermaid-svg-Ks1HSrQfksnZtHak div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Ks1HSrQfksnZtHak .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Ks1HSrQfksnZtHak rect.text{fill:none;stroke-width:0;}#mermaid-svg-Ks1HSrQfksnZtHak .icon-shape,#mermaid-svg-Ks1HSrQfksnZtHak .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Ks1HSrQfksnZtHak .icon-shape p,#mermaid-svg-Ks1HSrQfksnZtHak .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Ks1HSrQfksnZtHak .icon-shape .label rect,#mermaid-svg-Ks1HSrQfksnZtHak .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Ks1HSrQfksnZtHak .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Ks1HSrQfksnZtHak .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Ks1HSrQfksnZtHak :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 准备数据
特征提取
训练模型
预测
评估
11.2 KNN 手写数字识别
python
import cv2
import numpy as np
# 加载 OpenCV 自带的手写数字样本
samples = np.loadtxt("digits.png", np.float32)
samples = samples.reshape(-1, 20, 20) # 5000 个 20x20 图像
# 准备训练数据
train_data = samples.reshape(-1, 400).astype(np.float32)
labels = np.repeat(np.arange(10), 250)[:, np.newaxis].astype(np.float32)
knn = cv2.ml.KNearest_create()
knn.train(train_data, cv2.ml.ROW_SAMPLE, labels)
# 预测
test_sample = samples[1023].reshape(1, 400).astype(np.float32)
ret, result, neighbours, dist = knn.findNearest(test_sample, k=5)
print(f"预测: {int(result[0][0])}")
简化版:用 sklearn 数据
python
from sklearn.datasets import load_digits
import cv2
import numpy as np
digits = load_digits()
X = digits.data.astype(np.float32)
y = digits.target.astype(np.float32)
# 划分训练/测试
split = int(0.8 * len(X))
knn = cv2.ml.KNearest_create()
knn.train(X[:split], cv2.ml.ROW_SAMPLE, y[:split])
correct = 0
for i in range(split, len(X)):
_, result, _, _ = knn.findNearest(X[i:i+1], k=5)
if int(result[0][0]) == int(y[i]):
correct += 1
print(f"准确率: {correct/(len(X)-split)*100:.1f}%")
11.3 SVM 分类
python
import cv2
import numpy as np
# 生成简单二分类数据
np.random.seed(42)
data1 = np.random.randn(50, 2).astype(np.float32) + np.array([2, 2])
data2 = np.random.randn(50, 2).astype(np.float32) + np.array([-2, -2])
train_data = np.vstack([data1, data2])
labels = np.array([1]*50 + [-1]*50, dtype=np.float32)
svm = cv2.ml.SVM_create()
svm.setType(cv2.ml.SVM_C_SVC)
svm.setKernel(cv2.ml.SVM_LINEAR)
svm.train(train_data, cv2.ml.ROW_SAMPLE, labels)
# 预测
test = np.array([[0, 0]], dtype=np.float32)
_, result = svm.predict(test)
print(f"预测类别: {int(result[0][0])}")
SVM 核函数
| 核 | 适用 |
|---|---|
SVM_LINEAR |
线性可分 |
SVM_RBF |
非线性(最常用) |
SVM_POLY |
多项式 |
SVM_SIGMOID |
S 形 |
11.4 K-Means 聚类
将图像像素按颜色聚类,实现简单分割:
python
import cv2
import numpy as np
img = cv2.imread("photo.jpg")
data = img.reshape((-1, 3)).astype(np.float32)
K = 3 # 聚类数
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 20, 1.0)
_, labels, centers = cv2.kmeans(data, K, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)
centers = np.uint8(centers)
segmented = centers[labels.flatten()].reshape(img.shape)
cv2.imshow("K-Means", segmented)
cv2.waitKey(0)
| 参数 | 说明 |
|---|---|
| K | 聚类数量 |
| criteria | 终止条件 |
| attempts | 不同初始化的尝试次数 |
11.5 传统 ML vs 深度学习
| 维度 | 传统 ML (KNN/SVM) | 深度学习 (YOLO/CNN) |
|---|---|---|
| 特征 | 需手工设计 | 自动学习 |
| 数据量 | 小数据可用 | 需要大量数据 |
| 速度 | 快 | 较慢(GPU 加速) |
| 精度 | 简单任务够用 | 复杂任务更强 |
| 可解释性 | 较好 | 较差 |
💡 选择建议:OCR 简单场景、颜色聚类 → 传统 ML;目标检测、复杂分类 → 深度学习。
11.6 踩坑指南
| 坑 | 解决 |
|---|---|
| 数据类型错误 | 训练数据用 float32 |
| KNN k 值不当 | 通常 k=3~7,奇数 |
| SVM 训练慢 | 减少数据量或换 LINEAR 核 |
| K-Means 结果随机 | 增加 attempts |
11.7 本集小结
| 算法 | 场景 | 核心 API |
|---|---|---|
| KNN | 简单分类、OCR | KNearest_create().train() |
| SVM | 二/多分类 | SVM_create().train() |
| K-Means | 颜色聚类/分割 | cv2.kmeans() |
| ← 第10集 | 第12集:综合实战 → |
|---|
参考:8_1 KNN | 8_3 SVM | 8_5 K-Means