python环境:python3.8
依赖模块
scikit-image==0.21.0
opencv-python==4.9.0.80
imutils==0.5.4
import os
import cv2
import imutils
from skimage.metrics import structural_similarity
def cmp_picture(base_pic_path, cmp_pic_path, diff_pic_storage_dir_path=None):
"""
对比图片
:param base_pic_path: 基准图片路径
:param cmp_pic_path: 对比图片路径
:param diff_pic_storage_dir_path: 指定存储矩形标记的差异图片目录路路径,不指定存放当前目录下
:return: 基于对比图片生成的对差异的地方用矩形标记的图片的绝对路径
"""
# 加载两张图片
base_pic_ndarray = cv2.imread(base_pic_path)
cmp_pic_ndarray = cv2.imread(cmp_pic_path)
# 对比图片与基准图片大小对齐
height, width, channels = base_pic_ndarray.shape
cmp_pic_ndarray = cv2.resize(cmp_pic_ndarray, (width, height))
# 灰度处理
base_pic_gray_ndarray = cv2.cvtColor(base_pic_ndarray, cv2.COLOR_BGR2GRAY)
cmp_pic_gray_ndarray = cv2.cvtColor(cmp_pic_ndarray, cv2.COLOR_BGR2GRAY)
# 计算两个灰度图像之间的结构相似度指数:score和差异矩阵
# 不过ssim多用于压缩图片后的失真度比较
score, diff = structural_similarity(base_pic_gray_ndarray, cmp_pic_gray_ndarray, full=True)
diff = (diff * 255).astype("uint8")
# 找到不同点的轮廓以致于我们可以在被标识为“不同”的区域周围放置矩形:
thresh = cv2.threshold(diff, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
# cv2.findContours()函数返回两个值,一个是轮廓本身,还有一个是每条轮廓对应的属性。
# 其首先返回一个list,list中每个元素都是图像中的一个轮廓
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 注意cv版本,下面这一行会出现下列问题:
# OpenCV 3 改为cv2.findContours(...)返回值为image, contours, hierarchy,
# OpenCV 2 cv2.findContours(...)和OpenCV 4 的cv2.findContours(...)返回值为contours, hierarchy。
# 把contour轮廓储存在cnts这个list列表里
cnts = cnts[1] if imutils.is_cv2() else cnts[0]
# 找到一系列区域,在区域周围放置矩形:
# cv2.rectangle(imageA,(x,y),(x+w,y+h),(0,0,255),2) 参数解释
# 第一个参数:img是原图
# 第二个参数:(x,y)是矩阵的左上点坐
# 第三个参数:(x+w,y+h)是矩阵的右下点坐标
# 第四个参数:(0,0,255)是画线对应的rgb颜色
# 第五个参数:1是所画的线的宽度
for c in cnts:
x, y, w, h = cv2.boundingRect(c)
cv2.rectangle(cmp_pic_ndarray, (x, y), (x + w, y + h), (0, 0, 255), 1)
# 保存差异图片
cmp_pic_name = "diff_" + cmp_pic_path.split(os.sep)[-1]
if diff_pic_storage_dir_path:
os.makedirs(diff_pic_storage_dir_path, exist_ok=True)
diff_pic_path = os.path.join(diff_pic_storage_dir_path, cmp_pic_name)
else:
diff_pic_path = os.path.join(".", cmp_pic_name)
cv2.imwrite(diff_pic_path, cmp_pic_ndarray)
return os.path.abspath(diff_pic_path)
if __name__ == '__main__':
print(cmp_picture(r"C:\Users\xxx\Pictures\Saved Pictures\1.jpg",
r"C:\Users\xxx\Pictures\Saved Pictures\2.jpg"))
对比图片并返回差异图片的数据流,方便网络传输并在前端显示
import os
import cv2
import base64
import imutils
from skimage.metrics import structural_similarity
def cmp_and_get_diff_picture_streams(base_pic_path, cmp_pic_path):
"""
对比图片并返回差异图片的数据流,方便网络传输
需要在HTML的`<img>`标签中直接显示由数据流生成的JPEG图片时,应当使用"data URI scheme",
src属性前缀应加上"data:image/jpeg;base64,"
具体示例如下: <img src="data:image/jpeg;base64,{{encodedImageData}}"> 其中
"{{encodedImageData}}" 是一个占位符,表示已编码为Base64格式的JPEG图片数据流
:param base_pic_path: 基准图片路径
:param cmp_pic_path: 对比图片路径
:return: (标记差异矩阵后的基础图片数据流, 标记差异矩阵后的对比图片数据流)
"""
# 加载两张图片
base_pic_ndarray = cv2.imread(base_pic_path)
cmp_pic_ndarray = cv2.imread(cmp_pic_path)
# 对比图片与基准图片大小对齐
height, width, channels = base_pic_ndarray.shape
cmp_pic_ndarray = cv2.resize(cmp_pic_ndarray, (width, height))
# 灰度处理
base_pic_gray_ndarray = cv2.cvtColor(base_pic_ndarray, cv2.COLOR_BGR2GRAY)
cmp_pic_gray_ndarray = cv2.cvtColor(cmp_pic_ndarray, cv2.COLOR_BGR2GRAY)
# 计算两个灰度图像之间的结构相似度指数:score和差异矩阵
# 不过ssim多用于压缩图片后的失真度比较
score, diff = structural_similarity(base_pic_gray_ndarray, cmp_pic_gray_ndarray, full=True)
diff = (diff * 255).astype("uint8")
# 找到不同点的轮廓以致于我们可以在被标识为“不同”的区域周围放置矩形:
thresh = cv2.threshold(diff, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
# cv2.findContours()函数返回两个值,一个是轮廓本身,还有一个是每条轮廓对应的属性。
# 其首先返回一个list,list中每个元素都是图像中的一个轮廓
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 注意cv版本,下面这一行会出现下列问题:
# OpenCV 3 改为cv2.findContours(...)返回值为image, contours, hierarchy,
# OpenCV 2 cv2.findContours(...)和OpenCV 4 的cv2.findContours(...)返回值为contours, hierarchy。
# 把contour轮廓储存在cnts这个list列表里
cnts = cnts[1] if imutils.is_cv2() else cnts[0]
# 找到一系列区域,在区域周围放置矩形:
# cv2.rectangle(imageA,(x,y),(x+w,y+h),(0,0,255),2) 参数解释
# 第一个参数:img是原图
# 第二个参数:(x,y)是矩阵的左上点坐
# 第三个参数:(x+w,y+h)是矩阵的右下点坐标
# 第四个参数:(0,0,255)是画线对应的rgb颜色
# 第五个参数:2是所画的线的宽度
for c in cnts:
x, y, w, h = cv2.boundingRect(c)
cv2.rectangle(base_pic_ndarray, (x, y), (x + w, y + h), (0, 0, 255), 2) # 给基准图片标记差异矩阵
cv2.rectangle(cmp_pic_ndarray, (x, y), (x + w, y + h), (0, 0, 255), 2) # 给对比图片标记差异矩阵
# 对图像进行编码,例如转换为JPEG格式
# 参数1是目标格式(扩展名),参数2是要编码的图像矩阵
# 这里的cv2.IMWRITE_JPEG_QUALITY参数用于设置JPEG编码的质量等级,范围通常是从0到100。尽管设置为100能够获得最好的质量,
# 但JPEG始终是一种有损压缩格式,这意味着即使在最高质量设置下仍会有一定程度的信息丢失。如果你需要真正的无损存储,则应选择PNG等支持无损压缩的格式。
_, base_pic_buffer = cv2.imencode('.jpg', base_pic_ndarray, [int(cv2.IMWRITE_JPEG_QUALITY), 100]) # 可以设置压缩质量等参数
_, cmp_pic_buffer = cv2.imencode('.jpg', cmp_pic_ndarray, [int(cv2.IMWRITE_JPEG_QUALITY), 100]) # 可以设置压缩质量等参数
# 将此数据流转换为base64字符串以便在网络传输中使用
# decode成可打印的字符串形式 现在你可以发送或保存 base64_str 或 data_stream
return base64.b64encode(base_pic_buffer.tobytes()).decode('utf-8'), \
base64.b64encode(cmp_pic_buffer.tobytes()).decode('utf-8')