You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

求助:基于OpenCV确定UV打印徽章与模板的旋转角度

UV打印机徽章打印的旋转角度检测解决方案

项目目标

实现UV打印机在徽章上精准打印图像,核心流程为:输入含徽章的图像→定位徽章位置→计算徽章相对模板的旋转角度→将待打印图像匹配到对应位置并旋转至目标角度。

已完成的徽章定位阶段

采用HSV色彩空间的饱和度图层,结合Canny边缘检测、轮廓提取实现了徽章边界框定位,效果稳定。

定位代码(OpenCV Python)

import numpy as np
from matplotlib import pyplot as plt

import improclib
import cv2 as cv

import random as rng

from z_final_lib import getOrientation


# load the image, convert it to grayscale, and blur it slightly
image = cv.imread('b2.jpg')
gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)

toCanny = image
toCanny = improclib.select_colorsp(toCanny, colorsp='sat')

cv.imshow("Original", image)

canny_output = cv.Canny(toCanny, 30, 250)

canny_output = improclib.morph_op(canny_output, 'close', 3)

cv.imshow("canny_output", canny_output)

contours, hierarchy = cv.findContours(canny_output, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
print('contours len = ', len(contours))

sorted_cnt = sorted(contours, key=cv.contourArea, reverse=True)

bx, by, w, h = cv.boundingRect(sorted_cnt[0])
max_area = w * h
print('max_area')
print(max_area)

external_contours = []
min_area_ratio = 0.05
# 图像面积
im_area = image.shape[0] * image.shape[1]
for cnt in sorted_cnt:
    bx, by, w, h = cv.boundingRect(cnt)
    cnt_area = w * h
    print(cnt_area)
    # 移除极小的缺陷
    if (1 - cnt_area / max_area) < 0.6:
        external_contours.append(cnt)

contours = external_contours
print('contours len = ', len(contours))

filled_image = np.zeros_like(canny_output)
for c in contours:
    cv.drawContours(filled_image, [c], 0, (255, 255, 255), -1)

filled_image = improclib.morph_op(filled_image, 'open', 3)


cv.imshow('+++', filled_image)

rng.seed(12345)

contours_poly = [None] * len(contours)
boundRect = [None] * len(contours)
centers = [None] * len(contours)
radius = [None] * len(contours)
for i, c in enumerate(contours):
    contours_poly[i] = cv.approxPolyDP(c, 3, True)
    boundRect[i] = cv.boundingRect(contours_poly[i])
    centers[i], radius[i] = cv.minEnclosingCircle(contours_poly[i])

drawing = image.copy()
for i in range(len(contours)):
    color = (rng.randint(0, 256), rng.randint(0, 256), rng.randint(0, 256))
    cv.drawContours(drawing, contours_poly, i, color)
    cv.rectangle(drawing, (int(boundRect[i][0]), int(boundRect[i][1])),
                 (int(boundRect[i][0] + boundRect[i][2]), int(boundRect[i][1] + boundRect[i][3])), color, 2)

cv.imshow('Contours', drawing)

cv.waitKey()
cv.destroyAllWindows()

定位效果

徽章定位结果图

当前瓶颈:旋转角度检测

由于徽章存在个体差异与表面缺陷,尝试SIFT特征检测无法实现有效匹配,无法准确计算相对模板的旋转角度。

尝试的SIFT代码

sift = cv.SIFT_create()
kp = sift.detect(gray, None)
img = cv.drawKeypoints(gray, kp, img)
cv.imshow('sift_keypoints.jpg', img)

SIFT检测结果

SIFT特征检测结果图

裁剪后的徽章示例

裁剪徽章1
裁剪徽章2
裁剪徽章3
裁剪徽章4

可行解决方案建议

1. 基于轮廓几何特征的角度计算

利用已提取的徽章轮廓,通过最小外接矩形直接获取旋转角度,或对比模板的轮廓全局特征(如傅里叶描述子)忽略局部缺陷:

# 从最大轮廓计算最小外接矩形及角度
rect = cv.minAreaRect(sorted_cnt[0])
angle = rect[2]
# 调整角度至[0,90)范围
if rect[1][0] < rect[1][1]:
    angle = 90 + angle
print(f"徽章旋转角度: {angle}°")

2. 优化关键点匹配方案

替换SIFT为鲁棒性更强的ORB特征,结合RANSAC剔除错误匹配:

orb = cv.ORB_create(nfeatures=500)
# 检测模板与目标徽章的特征点
kp1, des1 = orb.detectAndCompute(template_gray, None)
kp2, des2 = orb.detectAndCompute(roi_gray, None)
# 暴力匹配并筛选优质结果
bf = cv.BFMatcher(cv.NORM_HAMMING, crossCheck=True)
matches = sorted(bf.match(des1, des2), key=lambda x: x.distance)
good_matches = matches[:int(len(matches)*0.15)]
# 计算单应性矩阵提取旋转角度
src_pts = np.float32([kp1[m.queryIdx].pt for m in good_matches]).reshape(-1,1,2)
dst_pts = np.float32([kp2[m.trainIdx].pt for m in good_matches]).reshape(-1,1,2)
M, _ = cv.findHomography(src_pts, dst_pts, cv.RANSAC, 5.0)
angle = np.arctan2(M[1,0], M[0,0]) * 180 / np.pi
print(f"匹配得到的旋转角度: {angle}°")

3. 相位相关法

将徽章与模板图像转换至频域,通过相位相关计算旋转参数,对光照和局部缺陷鲁棒性强:

  1. 裁剪定位后的徽章区域;
  2. 将徽章与模板归一化到相同尺寸;
  3. 利用极坐标变换结合cv.phaseCorrelate计算旋转角度。

4. 深度学习姿态估计

若徽章样本充足,训练轻量级CNN模型直接预测旋转角度,可适配复杂缺陷场景。

内容的提问来源于stack exchange,提问作者Andrey

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 20:29:51