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

如何用Python检测并对比两张图像中的点状目标?

问题描述

两名儿童在尺寸相近的白纸上绘制了类似点的物体,需要完成以下任务:

  • 检测图像中的点目标
  • 统计两张图中处于同一X、Y轴位置的目标数量
  • 度量两张图的一致性

尝试过SSIM等相似性方法,但针对这类小目标效果不佳;当前用OpenCV编写的代码会把多个点归为一个轮廓,无法实现每个点单独生成轮廓的需求。

输入图像(缩放版本)

image1
image2

当前使用的代码

from skimage.metrics import structural_similarity
import numpy as np
import cv2
image1=cv2.imread("E:/image1.png")
image2=cv2.imread("E:/image2.png")

image1gray=cv2.cvtColor(image1, cv2.COLOR_BGR2GRAY)
image2gray=cv2.cvtColor(image2, cv2.COLOR_BGR2GRAY)

# Compute SSIM between the two images
(score, diff) = structural_similarity(image1gray, image2gray, full=True)
print("Image Similarity: {:.4f}%".format(score * 100))

# The diff image contains the actual image differences between the two images
# and is represented as a floating point data type in the range [0,1] 
# so we must convert the array to 8-bit unsigned integers in the range
# [0,255] before we can use it with OpenCV
diff = (diff * 255).astype("uint8")
diff_box = cv2.merge([diff, diff, diff])

# Threshold the difference image, followed by finding contours to
# obtain the regions of the two input images that differ
thresh = cv2.threshold(diff, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
contours = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]

mask = np.zeros(image1.shape, dtype='uint8')
filled_image2 = image2.copy()
filled_image1=image1.copy()

for c in contours:
    area = cv2.contourArea(c)
    if area > 40:
        x,y,w,h = cv2.boundingRect(c)
        cv2.rectangle(image1, (x, y), (x + w, y + h), (36,255,12), 2)
        cv2.rectangle(image2, (x, y), (x + w, y + h), (36,255,12), 2)
        cv2.rectangle(diff_box, (x, y), (x + w, y + h), (36,255,12), 2)
        cv2.drawContours(mask, [c], 0, (255,255,255), -1)
        cv2.drawContours(filled_image2, [c], 0, (0,255,0), -1)
        cv2.drawContours(filled_image1, [c], 0, (0,255,0), -1)

cv2.imwrite('E:/Outout/image11.png', image1)
cv2.imwrite('E:/Outout/image22.png', image2)
cv2.imwrite('E:/Outout/diff_img.png', diff)
cv2.imwrite('E:/Outout/diff_box.png', diff_box)
cv2.imwrite('E:/Outout/mask.png', mask)
cv2.imwrite('E:/Outout/filled image2.png', filled_image2)

当前输出结果(不准确)

image11
image22
diff_img
diff_box
mask
filled image2


解决方案

不需要用机器学习,用OpenCV的基础图像处理就能解决,核心思路是先分别检测两张图里的每个点,再匹配坐标,而非先做全局差异对比。

步骤1:单独检测每张图的点目标

针对单张图,先做二值化,再用轮廓检测提取每个点,通过调整轮廓面积阈值确保每个点单独成轮廓:

import cv2
import numpy as np

def detect_points(image_path):
    img = cv2.imread(image_path)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    # 二值化:白底黑点,用反相阈值
    _, thresh = cv2.threshold(gray, 200, 255, cv2.THRESH_BINARY_INV)
    # 提取最外层轮廓,简化轮廓点
    contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    points = []
    for cnt in contours:
        area = cv2.contourArea(cnt)
        # 根据点的实际大小调整阈值(示例为10-50像素)
        if 10 < area < 50:
            # 计算轮廓中心作为点的坐标
            M = cv2.moments(cnt)
            if M["m00"] != 0:
                cx = int(M["m10"] / M["m00"])
                cy = int(M["m01"] / M["m00"])
                points.append((cx, cy))
                # 可视化标记点
                cv2.circle(img, (cx, cy), 3, (0, 255, 0), -1)
    return points, img

# 检测两张图的点
points1, img1_marked = detect_points("E:/image1.png")
points2, img2_marked = detect_points("E:/image2.png")

# 保存标记后的图像
cv2.imwrite("E:/Outout/marked_image1.png", img1_marked)
cv2.imwrite("E:/Outout/marked_image2.png", img2_marked)

步骤2:匹配同位置的点

考虑到纸张可能存在微小偏移,设置距离阈值(如5像素),通过欧氏距离判断点是否处于同一位置:

def match_points(points_a, points_b, threshold=5):
    matched_count = 0
    unmatched_a = []
    unmatched_b = []
    # 标记已匹配的点
    matched_b = [False]*len(points_b)
    
    for pa in points_a:
        found = False
        for idx, pb in enumerate(points_b):
            if not matched_b[idx]:
                distance = np.sqrt((pa[0]-pb[0])**2 + (pa[1]-pb[1])**2)
                if distance < threshold:
                    matched_count +=1
                    matched_b[idx] = True
                    found = True
                    break
        if not found:
            unmatched_a.append(pa)
    
    # 收集图2中未匹配的点
    for idx, pb in enumerate(points_b):
        if not matched_b[idx]:
            unmatched_b.append(pb)
    
    return matched_count, unmatched_a, unmatched_b

# 执行匹配
matched_num, unmatched1, unmatched2 = match_points(points1, points2)

步骤3:计算一致性指标

用以下指标量化两张图的一致性:

  • 匹配率:匹配点数量 / 两张图中较小的总点数
  • Jaccard系数:匹配点数量 / (图1总点数 + 图2总点数 - 匹配点数量)

示例计算代码:

match_rate = matched_num / min(len(points1), len(points2))
jaccard = matched_num / (len(points1) + len(points2) - matched_num)

print(f"匹配的点数量:{matched_num}")
print(f"图1未匹配点数量:{len(unmatched1)}")
print(f"图2未匹配点数量:{len(unmatched2)}")
print(f"匹配率:{match_rate:.2%}")
print(f"Jaccard系数:{jaccard:.2%}")

原方法失效原因

之前用SSIM生成差异图再找轮廓,会把邻近的差异区域合并成大轮廓——因为SSIM基于局部区域计算差异,邻近点的差异会连在一起。而先单独检测每张图的点再匹配坐标,能精准定位每个点,避免轮廓合并问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 14:01:42