用OpenCV的K-Means聚类对书法作品进行单字分割
对书法作品的单字进行分割,是为书法字典APP提供字模的智能化方法。较传统的方法是运用直方图进行分割。在OpenCV中有轮廓查找(findContours)和聚类(kmeans)两个处理函数。理论上选对书法作品进行轮廓查找,再将距离相近的轮廓运进行聚类,每个类就是一个汉字。
在OpenCV中K-Means函数的使用方法
关于K-Means函数的介绍可以观看:理解K-Means聚类
函数原型:
retval, bestLabels, centers = kmeans(data, K, bestLabels, criteria, attempts, flags, centers=None)
函数参数:
data: 需要分类数据,最好是np.float32的数据,每个特征放一列。
K: 聚类个数
bestLabels:预设的分类标签或者None
criteria:迭代停止的模式选择,这是一个含有三个元素的元组型数。格式为(type, max_iter, epsilon) 其中,type有如下模式:
- cv2.TERM_CRITERIA_EPS :精确度(误差)满足epsilon,则停止。
- cv2.TERM_CRITERIA_MAX_ITER:迭代次数超过max_iter,则停止。
- cv2.TERM_CRITERIA_EPS+cv2.TERM_CRITERIA_MAX_ITER:两者结合,满足任意一个结束。
attempts:重复试验kmeans算法次数,将会返回最好的一次结果
flags:初始中心选择,可选以下两种:
- v2.KMEANS_PP_CENTERS:使用kmeans++算法的中心初始化算法,即初始中心的选择使眼色相差最大.详细可查阅kmeans++算法。(Use kmeans++ center initialization by Arthur and Vassilvitskii)
- cv2.KMEANS_RANDOM_CENTERS:每次随机选择初始中心(Select random initial centers in each attempt.)
返回值:
compactness:紧密度,返回每个点到相应重心的距离的平方和
labels:结果标记,每个成员被标记为分组的序号,如 0,1,2,3,4...等
centers:由聚类的中心组成的数组
程序运行过程
- 确定要分割的汉字数量,即聚类个数
- 轮廓查找生成数据
- 定义停止条件
- K-Means分类
- 每个聚类的轮廓到一个列表中
- 聚类描边
程序代码
import numpy as np import cv2 as cv img = cv.imread('jk.jpg') # 要分的聚类数 sampleCount = 50 # 要分割的字符数 charCount = 50 # 转换为灰度图 gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY) # 二值化 ret, thresh = cv.threshold(gray, 127, 255, 0) # 反色,查找轮廓的要求是黑底白字 cv.bitwise_not(thresh, thresh) # 发现轮廓 contours, hierarchy = cv.findContours(thresh, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE) # 计算所有轮廓的面积和 pointCount = 0 for cnt in contours: pointCount += cv.contourArea(cnt) # 最小面积,小于这个面积的轮廓将被删除 minPointSize = pointCount / charCount / 50 # 删除较小的轮廓 ucontours = [] for cnt in contours: if cv.contourArea(cnt) > minPointSize: ucontours.append(cnt) cc = len(ucontours) # 由每个轮廓的中心点生成二维数据 points = np.zeros((cc, 2), dtype=np.int) p = 0 for cnt in ucontours: # 获取轮廓坐标和宽高 x, y, w, h = cv.boundingRect(cnt) points[p][0] = x + w / 2 points[p][1] = y + h / 2 p += 1 # 转化为float数据 Z = np.float32(points) # 定义 criteria = (cv.TERM_CRITERIA_MAX_ITER, 10, 1.0) ret, labels, centers = cv.kmeans(Z, sampleCount, None, criteria, 10000, cv.KMEANS_PP_CENTERS) print(ret) # 将每个聚类的轮廓添加到同个列表中 cntlist = [] for i in range(sampleCount): cntlist.append([]) p = 0 for lb in labels: cntlist[lb[0]].append(ucontours[p]) p += 1 rc = 255 bc = 0 # 轮廓描边 for i in range(sampleCount): cv.drawContours(img, cntlist[i], -1, (rc, bc, 0), 3) rc -= 5 bc += 5 cv.imwrite('jkkm.png', img) #cv.imshow('candy', img) #cv.waitKey()
效果分析
从上图中可以看到,大多数字都准确的分割出来了,也有几处错误。如果能结合对图片进行膨胀,手动确定聚类中心,也许会更好些。