考考你的眼力,看看你能从下面的图片中找出几处不同:很多时候看到这类图片,总想多看几眼,找出其中的差别,可是有时眼睛都瞅模糊了,也没有找到几处。是不是有点开挂的感觉,有多少个差异,分别在哪里,全部一目了然。下面就是实现的完整代码,即使文件名带汉字也能完美运行:import cv2from pathlib import Pathimport numpy as npdef mark_differences(image_path, output_path, min_area=100): # 1. 读取图片 # img = cv2.imread(image_path) # 路径中有汉字会出错 img = cv2.imdecode(np.fromfile(image_path, dtype=np.uint8), cv2.IMREAD_COLOR) if img is None: print("错误:无法读取图片,请检查路径。") return h, w = img.shape[:2] # 2. 将图片垂直切分为左右两半(假设左右宽度相等) mid = w // 2 left_img = img[:, :mid] right_img = img[:, mid:] # 3.将图片缩放到相同尺寸 right_img = cv2.resize(right_img,(mid,h)) # 4. 转换为灰度图 gray_left = cv2.cvtColor(left_img, cv2.COLOR_BGR2GRAY) gray_right = cv2.cvtColor(right_img, cv2.COLOR_BGR2GRAY) # 5. 计算绝对差 diff = cv2.absdiff(gray_left, gray_right) # 6. 二值化:差异大于阈值设为白色 _, thresh = cv2.threshold(diff, 30, 255, cv2.THRESH_BINARY) # 7. 形态学去噪 kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5)) morph = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel, iterations=2) morph = cv2.morphologyEx(morph, cv2.MORPH_OPEN, kernel, iterations=1) # 8. 查找轮廓 contours, _ = cv2.findContours(morph, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 9. 在原图的副本上绘制矩形框 result = img.copy() marked_count = 0 for cnt in contours: area = cv2.contourArea(cnt) if area < min_area: continue x, y, w_box, h_box = cv2.boundingRect(cnt) # 标注左侧区域 cv2.rectangle(result, (x-20, y-20), (x + w_box+20, y + h_box+20), (0, 255, 255), 4) # 标注右侧区域,矩形框向右平移mid个像素 cv2.rectangle(result, (x + mid-20, y-20), (x + mid + w_box+20, y + h_box+20), (0, 255, 255), 4) marked_count += 1 print(f"共发现 {len(contours)} 个差异区域,标注了 {marked_count} 个(面积 ≥ {min_area})") # 10. 保存结果 # cv2.imwrite(output_path, result) #中文路径会报错 ext = Path(output_path).suffix # 后缀名 _, img_encode = cv2.imencode(ext, result) #把图像编码成内存中的 buffer,如 jpg、png img_encode.tofile(out_path) # 写入文件 print(f"标注结果已保存到: {output_path}")if __name__ == "__main__": img_path = r"D:\找不同\111.jpeg" # 要查找图片路径 out_path = r"D:\找不同\111.jpg" # 保存查找后的路径 mark_differences(img_path, out_path, min_area=100)
img = cv2.imdecode(np.fromfile(image_path, dtype=np.uint8), cv2.IMREAD_COLOR)
cv2.imread不支持中文路径,如果路径中包含中文会出错,在这里用cv2.imdecode配合fromfile可以彻底解决中文报错的问题。 mid = w // 2 left_img = img[:, :mid] right_img = img[:, mid:]
w // 2 自动计算出图片的宽度,然后从中间切开,left_img = img[:, :mid]是左图,right_img = img[:, mid:]是右图。right_img = cv2.resize(right_img,(mid,h))
使用cv2.resize调整右图尺寸,和左图一样大小。左右图像尺寸不一致,就会差异识别失败。 gray_left = cv2.cvtColor(left_img, cv2.COLOR_BGR2GRAY) gray_right = cv2.cvtColor(right_img, cv2.COLOR_BGR2GRAY)
彩色图片干扰因素多,cv2.cvtColor(left_img, cv2.COLOR_BGR2GRAY)可以将彩色图片转为黑白灰度图,只保留亮度差异。
5、计算像素差异
diff = cv2.absdiff(gray_left, gray_right)
cv2.absdiff是计算两个数组绝对值的函数,用来检测两幅图像之间的像素差异。cv2.absdiff会逐像素对比两张图片,不一样的地方会高高度显示,找出不一样的点。
6、二值化
_, thresh = cv2.threshold(diff, 30, 255, cv2.THRESH_BINARY)
cv2.threshold是图像二值化工具。diff是一张灰度图,设定30的阈值,cv2.THRESH_BINARY的作用是大于阈值的变白,小于等于阈值的变黑。thresh是要输出的二值化图像,最终会得到一张黑白分明的差异图。
7、去噪点
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5)) morph = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel, iterations=2) morph = cv2.morphologyEx(morph, cv2.MORPH_OPEN, kernel, iterations=1)
cv2.getStructuringElement是做一个小模板,MORPH_RECT是矩形,(5,5)是这个矩形的大小。
用cv2.morphologyEx作“闭运算”和"开运算“,就是用这把刷子来干活。cv2.MORPH_CLOSE是做闭运算,也就是先膨胀再腐蚀;cv2.MORPH_OPEN是做开运算,就是先腐蚀再膨胀;iterations=2表示迭代次数。这一步的作用是把零散的差异点聚集到成块。
8、找出轮廓
contours, _ = cv2.findContours(morph, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cv2.findContours的作用是把这些白色区域的边界描出来,存成一个一个的轮廓对象。cv2.RETR_EXTERNAL表示只要最外层的轮廓,不要里面的内容。cv2.CHAIN_APPROX_SIMPLE只存直线的两个端点,把中间的点扔掉。
contour是一个列表,列表中每个元素就是个轮廓。图像中有几个白色块,列表里就有几个轮廓。
9、标注颜色框
result = img.copy() marked_count = 0 for cnt in contours: area = cv2.contourArea(cnt) if area < min_area: continue x, y, w_box, h_box = cv2.boundingRect(cnt) # 标注左侧区域 cv2.rectangle(result, (x-20, y-20), (x + w_box+20, y + h_box+20), (0, 255, 255), 4) # 标注右侧区域,矩形框向右平移mid个像素 cv2.rectangle(result, (x + mid-20, y-20), (x + mid + w_box+20, y + h_box+20), (0, 255, 255), 4) marked_count += 1
cv2.contourArea(cnt):计算当前轮廓cnt的面积,单位为像素个数。
if area < min_area:min_area是函数参数的设置,默认值为100,小于100个像素的差异区域不进行标注。
x, y, w_box, h_box = cv2.boundingRect(cnt):其中x为矩形左上角的X坐标,y为矩形左上角的Y坐标,w_box为矩形的宽度,h_box为矩形的高度。
cv2.rectangle:在图上画一个矩形框。(x-20, y-20)为矩形框的左上角坐标,减20表示往外扩20个像素,(x + w_box+20, y + h_box+20)为矩形右下角的坐标,(0,255,255)表示矩形框的颜色,4表示矩形框的线条粗细。
10、保存图片
# cv2.imwrite(output_path, result) ext = Path(out_path).suffix # 后缀名 _, img_encode = cv2.imencode(ext, result) #把图像编码成内存中的 buffer,如 jpg、png img_encode.tofile(out_path) # 写入文件
cv2.imwrite不支持中文路径,cv2.imencode和tofile配合可以完美解决这个问题。
if __name__ == "__main__": img_path = r"D:\找不同\111.jpeg" # 要查找图片路径 out_path = r"D:\找不同\111.jpg" # 保存查找后的路径 mark_differences(img_path, out_path, min_area=100)
这是代码的主程序,只需修改路径,然后运行即可。
费眼、费力的事,用这几十行代码就能轻松解决。你觉得有用的话,还请点赞+在看+转发!