MENU

第十届上海工训赛小记:图像配准识别

April 1, 2021 • 学习

本文作者保留所有权利

背景

第十届上海市大学生工程训练综合能力竞赛・配送无人机选题

这个比赛,我其实有很大划水的成分,我一个电子电气工程学院的跑航空学院做一个飞行类的比赛,似乎也有些难施拳脚。比赛筹备过程中,我主要负责降落阶段的落点目标识别(主要分三类,第一类为印刷体字母标记,第二类为同心圆、第三类为人、车或房的卡通图片)。当时那个阶段我正在研究 K210 这款搭载 KPU 的平台,自然而然的想到了用 Yolo 算法进行训练,没准能达到所需要的的效果。可匆忙的临门一脚的结果便是,效果不尽人意。我对人工智能这块仅停留于不久之前的一门导论课,而人工智能的根本:概率论和线性代数,我也学的一刀烂

事实证明,我对 Yolo 算法的实践还停留于跑出一个识别率并不高的模型,在实际运用中,模型对于相同场景下但未在训练集中的视频帧的识别结果置信度并不高(只有可怜的 0.4 左右)。从两方面考量,一是训练集仅有大约两百多张图片,要实现精准的目标识别还存在问题,尤其是 Yolo 算法中较为关键的 Anchor 参数(见文 Yolo Anchor 数值 聚类分析);第二点是 K210 所适配的摄像头约为 240P,实际输入进模型的帧大小为 224*320,数据量小,摄像头最低照度参数差。最终还是被我否决了

经过团队里的头脑风暴,我们大概总结了以下几种方案。

  1. 基于 Yolo 算法自行训练
  2. 基于 Minist 数据集进行识别
  3. 基于 SIFT 算法的图片匹配

基于 Minist 数据集进行识别,我觉得方法很巧,这里也简单写写,基本思路是将所识别的物体参照 Minist 集进行分割。比如字母标记,毋庸置疑,可以直接用 Minist 集进行训练。对于那些人、车和房,可以抓住其中长得像字母的部分,做色调分离后的二值化。唯一不方便的是,可能需要根据赛事议题进行调整模型的训练集。识别时置信度比较高,几乎达到了 1。

基于 SIFT 算法的图片匹配主要由我来实施,测试单例如下:

  • import numpy as np
  • import cv2 #在OpenCV 3.4之后因专利版权问题移除了SIFT/SURF的相关库,本文稍后会附上相关说明。
  • def load_image(path, gray=False):
  • if gray:
  • img = cv2.imread(path)
  • return cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
  • else:
  • return cv2.imread(path)
  • def transform(origin):
  • h, w, _ = origin.shape
  • generate_img = np.zeros(origin.shape)
  • for i in range(h):
  • for j in range(w):
  • generate_img[i, w - 1 - j] = origin[i, j]
  • return generate_img.astype(np.uint8)
  • def main():
  • # 数据要求。两个图片要大小一致,色彩空间一致。如800px^2,RGB 32位色。比赛的时候就将img1设定为拍摄到的,并且编辑成和树莓派CAM帧大小一致的图片,可以留白,目前测试不影响。img2就取视频帧即可。
  • img1 = load_image('Source.png')
  • img2 = load_image('Target.jpg')
  • # 实例化
  • sift = cv2.xfeatures2d.SIFT_create()
  • # 计算关键点和描述子
  • # 其中kp为关键点、des为描述子
  • kp1, des1 = sift.detectAndCompute(img1, None)
  • kp2, des2 = sift.detectAndCompute(img2, None)
  • # 绘出关键点
  • # 其中参数分别是源图像、关键点、输出图像、显示颜色
  • img3 = cv2.drawKeypoints(img1, kp1, img1, color=(0, 255, 255)) # 在源图片上标注关键点
  • img4 = cv2.drawKeypoints(img2, kp2, img2, color=(0, 255, 255)) # 在目标图片上标注关键点
  • # 参数设计和实例化
  • index_params = dict(algorithm=1, trees=6)
  • search_params = dict(checks=40)
  • flann = cv2.FlannBasedMatcher(index_params, search_params)
  • # 利用knn计算两个描述子的匹配
  • matche = flann.knnMatch(des1, des2, k=2)
  • matchesMask = [[0, 0] for i in range(len(matche))]
  • # 绘出匹配效果
  • result = []
  • resultItem = []
  • for m, n in matche:
  • if m.distance < 0.3 * n.distance: #判断欧式距离,按照SIFT理论,关键点向量越接近,欧式距离越短,即两点越像。 常数0.3可以继续调整,数字越大,匹配的点位越多,也越不精准。
  • result.append([m])
  • resultItem.append(m)
  • img5 = cv2.drawMatchesKnn(img1, kp1, img2, kp2, result, None, flags=2)
  • # combine = np.hstack((img3, img4))
  • # cv2.imshow("KeyPoints", combine)
  • cv2.imshow("K", img5)
  • for m in resultItem:
  • print(kp2[m.queryIdx].pt) # 该参数为目标图片中关键点的坐标。
  • cv2.waitKey(15000)
  • if __name__ == '__main__':
  • main()

测试过程中,正好在吃麦当劳,就用麦当劳里的 LOGO 做了一次测试。(这里请麦当劳给我打钱,谢谢。)
2477853893.png3901928489.jpg 嗯 效果还不错。


接下来就是结合视频帧进行处理了,逻辑也很简单,一个死循环,不断读视频帧,然后处理,输出结果。代码如下:

  • import numpy as np
  • import cv2
  • def load_image(path, gray=False):
  • if gray:
  • img = cv2.imread(path)
  • return cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
  • else:
  • return cv2.imread(path)
  • def transform(origin):
  • h, w, _ = origin.shape
  • generate_img = np.zeros(origin.shape)
  • for i in range(h):
  • for j in range(w):
  • generate_img[i, w - 1 - j] = origin[i, j]
  • return generate_img.astype(np.uint8)
  • def main():
  • cap = cv2.VideoCapture(0)
  • imgSource = load_image('source.png')
  • # 实例化
  • imgSource = cv2.resize(imgSource, None, fx=0.5, fy=0.5, interpolation = cv2.INTER_CUBIC)
  • sift = cv2.xfeatures2d.SIFT_create()
  • kpSource, desSource = sift.detectAndCompute(imgSource, None)
  • imgSourceWithKp = cv2.drawKeypoints(imgSource, kpSource, desSource, color=(0, 255, 255))
  • index_params = dict(algorithm=1, trees=6)
  • search_params = dict(checks=100)
  • flann = cv2.FlannBasedMatcher(index_params, search_params)
  • while(True):
  • ret, frame = cap.read()
  • frame = cv2.resize(frame, None, fx=0.5, fy=0.5, interpolation = cv2.INTER_CUBIC)
  • kpFrame, desFrame = sift.detectAndCompute(frame, None)
  • imgFrameWithKp = cv2.drawKeypoints(frame, kpFrame, frame, color=(0, 255, 255))
  • matche = flann.knnMatch(desSource, desFrame, k=2)
  • #matchesMask = [[0, 0] for i in range(len(matche))]
  • # 绘出匹配效果
  • result = []
  • resultItem = []
  • for m, n in matche:
  • if m.distance < 0.4 * n.distance: #判断欧式距离,按照SIFT理论,关键点向量越接近,欧式距离越短,即两点越像。 常数0.3可以继续调整,数字越大,匹配的点位越多,也越不精准。
  • result.append([m])
  • resultItem.append(m)
  • img5 = cv2.drawMatchesKnn(imgSource, kpSource, frame, kpFrame, result, None, flags=2)
  • #combine = np.hstack((imgSourceWithKp, imgFrameWithKp))
  • #cv2.imshow("KeyPoints", combine)
  • cv2.imshow("K", img5)
  • for m in resultItem:
  • print(kpFrame[m.queryIdx].pt) # 该参数为目标图片中关键点的坐标。可以对半分。这个相信你们应该懂的。
  • if cv2.waitKey(1) & 0xFF == ord('q'):
  • break
  • print("Next Frame")
  • cap.release()
  • cv2.distoryAllWindows()
  • if __name__ == '__main__':
  • main()

嗯,感觉识别率还可以。

屏幕截图 2021-04-02 202141.jpg

但这个颜色,感觉有这么点阴间。

用一个简单的通道均值算法来解决这个问题。

  • def whiteBalance(frame):
  • r, g, b = cv2.split(frame)
  • r_avg = cv2.mean(r)[0]
  • g_avg = cv2.mean(g)[0]
  • b_avg = cv2.mean(b)[0]
  • k = (r_avg + g_avg + b_avg) / 3
  • kr = k / r_avg
  • kg = k / g_avg
  • kb = k / b_avg
  • r = cv2.addWeighted(src1=r, alpha=kr, src2=0, beta=0, gamma=0)
  • g = cv2.addWeighted(src1=g, alpha=kg, src2=0, beta=0, gamma=0)
  • b = cv2.addWeighted(src1=b, alpha=kb, src2=0, beta=0, gamma=0)
  • return cv2.merge([b, g, r])

插曲

这次市赛突然修改了规则,识别物从原先的 $500mm^2$ 变成了 $250mm^2$。因此大部分参赛队伍都是手动飞的,只有一组顶流大学,使用的是 SLAM 方案,自动完成了比赛,实在是佩服。
从算法角度来说,使用 SURF 代替 SIFT 可以获得更高性能,并可以考虑使用完整帧。

关于 SIFT 算法专利版权问题

OpenCV 3.4 之后因专利版权问题移除了 SIFT/SURF 的相关库,因此在使用较新版本的 CV 库时会报错。
而网上如今绝大多数解决方法都是回退 OpenCV 的版本并且安装 Contrib 支持库,这样还意味着你需要回退或者创建一个支持 3.4 之前的 Python 版本的环境(3.5、3.6),总而言之非常麻烦!
而在 2020 年 3 月 17 日之后,SIFT 专利到期了,因此只需更新 OpenCV 版本即可免费使用。
推荐将原先的:cv2.xfeatures2d.SIFT_create() 改成 cv2.SIFT_create()

Last Modified: July 18, 2021
Archives QR Code
QR Code for this page
Tipping QR Code