(二)PS识别: 特征识别-直方图分析的从原理到实现

#王者杯·14天创作挑战营·第3期#

    (一)PS识别: Python 图像分析PS识别之道-CSDN博客

        (三)PS识别:基于噪声分析PS识别的技术实现-CSDN博客

       (四)PS识别:基于边缘纹理检测分析PS识别的技术实现

       (五)PS识别:压缩痕迹挖掘-压缩量化表与 DCT 系数分析

       

 

          在这个图像编辑技术日益发达的时代,PS(Adobe Photoshop)处理过的图片随处可见。无论是社交媒体上的精美自拍,还是广告宣传中的产品图片,都可能经过了精心的 PS 处理。这也引发了人们对于图片真实性的关注,PS 特征识别技术应运而生。本文将深入探讨 PS 特征识别的原理,并通过 Python 代码进行实战演示。

一 直方图分析

直方图是图像中像素强度分布的图形表示,它展示了不同亮度级别的像素数量。经过 PS 处理的图片,其直方图可能会出现一些异常特征,例如:

  • 尖锐峰值:在高像素值区域出现尖锐峰值,可能意味着图片经过了过度曝光调整。
  • 截断现象:直方图两端的像素数量突然增多,可能是图片经过了曝光调整,导致部分像素值被截断。
  • 多个显著峰值和谷值:如果直方图中出现多个显著的峰值和谷值,图片可能是拼接而成的。

二  实现逻辑分析

逻辑图解释

  1. 开始与图片读取:流程从开始节点出发,尝试读取指定路径的图片,判断图片是否成功读取。若读取失败,输出错误信息并结束流程。
  2. 灰度转换与直方图计算:图片读取成功后,将其转换为灰度图并计算直方图。
  3. 直方图分析:对计算得到的直方图进行分析,判断图片是否经过 PS 处理。如果判断为处理过,进入编辑区域检测与标注步骤。
  4. 边缘分析:若直方图分析未发现异常,进行边缘分析,判断边缘变化是否过小。若边缘变化过小,同样进入编辑区域检测与标注步骤。
  5. 结果输出与可视化:根据判断结果输出相应信息,绘制灰度直方图,并显示原始图和标注图的对比,最后流程结束。

通过这个逻辑图,能清晰地看到图片识别过程中各个步骤的先后顺序和判断逻辑。

三、Python 实战:PS 特征识别

1. 安装依赖库

在开始之前,我们需要安装一些必要的 Python 库:

pip install opencv-python numpy matplotlib scipy scikit-image

2.代码实现

import cv2
import numpy as np
import matplotlib.pyplot as plt
from scipy.signal import find_peaks
from skimage.filters import sobel
from skimage.segmentation import find_boundaries
from scipy.stats import skew, kurtosis
from typing import Tuple


class ImagePSAnalyzer:
    def __init__(
            self,
            peak_height_threshold_multiplier: float = 2.0,
            peak_distance: int = 30,
            significant_peak_ratio: float = 0.3,
            valley_height_threshold_multiplier: float = 1.0,
            valley_distance: int = 20,
            smooth_diff_threshold: float = 0.3,
            left_right_range: int = 10,
            left_right_pixel_ratio_threshold: float = 0.05,
            middle_pixel_ratio_threshold: float = 0.8,
            truncation_threshold_multiplier: float = 1.5,
            high_pixel_peak_threshold_multiplier: float = 5.0,
            skewness_left_threshold: float = -1.0,
            skewness_right_threshold: float = 1.0,
            kurtosis_threshold: float = 3.0
    ):
        """
        初始化图像 PS 分析器,设置各种阈值参数
        :param peak_height_threshold_multiplier: 峰值检测高度阈值的标准差倍数
        :param peak_distance: 峰值检测时相邻峰值的最小距离
        :param significant_peak_ratio: 显著峰值高度与最大峰值高度的比例阈值
        :param valley_height_threshold_multiplier: 谷值检测高度阈值的标准差倍数
        :param valley_distance: 谷值检测时相邻谷值的最小距离
        :param smooth_diff_threshold: 直方图平滑度差异阈值
        :param left_right_range: 直方图左右截断检测范围
        :param left_right_pixel_ratio_threshold: 直方图左右两端像素比例阈值
        :param middle_pixel_ratio_threshold: 直方图中间部分像素比例阈值
        :param truncation_threshold_multiplier: 截断检测阈值的标准差倍数
        :param high_pixel_peak_threshold_multiplier: 高像素值区域尖锐峰值阈值的均值倍数
        :param skewness_left_threshold: 左截断时偏度阈值
        :param skewness_right_threshold: 右截断时偏度阈值
        :param kurtosis_threshold: 峰度阈值
        """
        self.peak_height_threshold_multiplier = peak_height_threshold_multiplier
        self.peak_distance = peak_distance
        self.significant_peak_ratio = significant_peak_ratio
        self.valley_height_threshold_multiplier = valley_height_threshold_multiplier
        self.valley_distance = valley_distance
        self.smooth_diff_threshold = smooth_diff_threshold
        self.left_right_range = left_right_range
        self.left_right_pixel_ratio_threshold = left_right_pixel_ratio_threshold
        self.middle_pixel_ratio_threshold = middle_pixel_ratio_threshold
        self.truncation_threshold_multiplier = truncation_threshold_multiplier
        self.high_pixel_peak_threshold_multiplier = high_pixel_peak_threshold_multiplier
        self.skewness_left_threshold = skewness_left_threshold
        self.skewness_right_threshold = skewness_right_threshold
        self.kurtosis_threshold = kurtosis_threshold

    def analyze_histogram(self, hist: np.ndarray) -> Tuple[bool, str]:
        """
        分析直方图,判断图片是否有经过 PS 处理的迹象
        :param hist: 图像的直方图
        :return: 是否有 PS 处理迹象,对应的提示信息
        """
        # 计算直方图的统计信息
        mean_value = np.mean(hist)
        std_dev = np.std(hist)
        total_pixels = np.sum(hist)
        # 计算偏度和峰度
        skewness = skew(hist.flatten())
        kurt = kurtosis(hist.flatten())

        # 改进的峰值检测
        peaks, peak_properties = find_peaks(
            hist.flatten(),
            height=mean_value + self.peak_height_threshold_multiplier * std_dev,
            distance=self.peak_distance
        )
        peak_heights = peak_properties['peak_heights']
        significant_peaks = []

        # 检查 peak_heights 是否为空
        if peak_heights.size > 0:
            max_peak_height = np.max(peak_heights)
            for i in range(len(peaks)):
                if peak_heights[i] / max_peak_height > self.significant_peak_ratio:
                    significant_peaks.append(peaks[i])
        else:
            max_peak_height = 0

        # 谷值检测
        valleys, _ = find_peaks(
            -hist.flatten(),
            height=-(mean_value + self.valley_height_threshold_multiplier * std_dev),
            distance=self.valley_distance
        )

        # 直方图平滑度分析
        smoothed_hist = np.convolve(hist.flatten(), np.ones(5) / 5, mode='same')
        diff = np.sum(np.abs(hist.flatten() - smoothed_hist)) / total_pixels
        is_rough = diff > self.smooth_diff_threshold

        # 优化截断检测
        # 扩大截断检测范围
        left_range = self.left_right_range
        right_range = self.left_right_range
        # 计算不同区间的像素比例
        left_pixel_ratio = np.sum(hist[:left_range]) / total_pixels
        right_pixel_ratio = np.sum(hist[-right_range:]) / total_pixels
        # 计算中间部分像素比例
        middle_pixel_ratio = np.sum(hist[left_range:-right_range]) / total_pixels

        # 动态调整截断阈值
        left_threshold = mean_value + self.truncation_threshold_multiplier * std_dev
        right_threshold = mean_value + self.truncation_threshold_multiplier * std_dev

        # 检查连续多个像素点是否超过阈值
        left_truncated = np.any(hist[:left_range] > left_threshold) and \
                         left_pixel_ratio > self.left_right_pixel_ratio_threshold and \
                         middle_pixel_ratio < self.middle_pixel_ratio_threshold
        right_truncated = np.any(hist[-right_range:] > right_threshold) and \
                          right_pixel_ratio > self.left_right_pixel_ratio_threshold and \
                          middle_pixel_ratio < self.middle_pixel_ratio_threshold

        # 结合偏度和峰度判断
        if left_truncated:
            left_truncated = left_truncated and (
                        skewness < self.skewness_left_threshold or kurt > self.kurtosis_threshold)
        if right_truncated:
            right_truncated = right_truncated and (
                        skewness > self.skewness_right_threshold or kurt > self.kurtosis_threshold)

        # 判断是否异常
        if len(significant_peaks) > 2 and len(valleys) > 1:  # 多个显著峰值和谷值,可能是拼接图片
            return True, f"直方图出现 {len(significant_peaks)} 个显著峰值和 {len(valleys)} 个谷值,图片可能是拼接图片"
        elif left_truncated or right_truncated:  # 直方图截断
            return True, "直方图出现截断,结合像素比例、偏度和峰度判断,图片可能经过曝光调整"
        elif any(hist[i] > self.high_pixel_peak_threshold_multiplier * mean_value for i in
                 range(200, 256)):  # 高像素值区域尖锐峰值,可能过度曝光
            return True, "直方图在高像素值区域出现尖锐峰值,图片可能经过过度曝光调整"
        elif is_rough:  # 直方图不平滑
            return True, "直方图分布粗糙,图片可能经过处理"
        else:
            return False, "直方图分布正常,未发现 PS 处理迹象"

    def detect_edited_regions(self, image: np.ndarray) -> np.ndarray:
        """
        检测图片中可能被编辑的区域
        :param image: 输入图像
        :return: 标注了可能编辑区域的图像
        """
        # 转换为灰度图
        gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) if len(image.shape) == 3 else image

        # 边缘检测
        edges = sobel(gray)
        edge_variation = np.std(edges)

        # 查找可能的边界
        boundaries = find_boundaries(edges > 0.1, mode='thick')

        # 复制原图用于标注
        marked_image = image.copy()

        # 标注边界
        marked_image[boundaries] = [0, 0, 255]  # 用红色标注可能的编辑区域

        return marked_image

    def analyze_image(self, image: np.ndarray) -> Tuple[bool, str, np.ndarray]:
        """
        综合分析图像,包括直方图和边缘信息
        :param image: 输入图像
        :return: 是否有 PS 处理迹象,对应的提示信息,标注后的图像
        """
        # 转换为灰度图
        gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) if len(image.shape) == 3 else image

        # 计算直方图
        hist = cv2.calcHist([gray], [0], None, [256], [0, 256])

        # 分析直方图
        is_processed_hist, message_hist = self.analyze_histogram(hist)
        if is_processed_hist:
            marked_image = self.detect_edited_regions(image)
            return True, message_hist, marked_image

        # 边缘分析
        edges = sobel(gray)
        edge_variation = np.std(edges)
        if edge_variation < 0.05:
            marked_image = self.detect_edited_regions(image)
            return True, "图像边缘变化过小,可能经过处理", marked_image

        return False, "图像正常,未发现 PS 处理迹象", image


if __name__ == "__main__":
    # 初始化图像 PS 分析器
    analyzer = ImagePSAnalyzer()

    # 读取图片
    image = cv2.imread('2008-07-07_2022-09-21_JFZDPT-BJ-20230111-00197_2.jpg')

    if image is None:
        print("无法读取图片,请检查图片路径。")
    else:
        # 分析图像
        is_processed, message, marked_image = analyzer.analyze_image(image)

        # 计算并绘制灰度直方图
        gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) if len(image.shape) == 3 else image
        hist = cv2.calcHist([gray], [0], None, [256], [0, 256])
        plt.plot(hist)
        plt.title('Image Histogram')
        plt.xlabel('Pixel Value')
        plt.ylabel('Frequency')
        plt.show()

        print(message)
        print(f"is_processed 结果: {is_processed}, 信息: {message}")

        # 确保原图和标注后的图片尺寸一致
        if image.shape != marked_image.shape:
            marked_image = cv2.resize(marked_image, (image.shape[1], image.shape[0]))

        # 水平拼接原图和标注后的图片
        comparison_image = np.hstack((image, marked_image))

        # 创建一个可调整大小的窗口
        cv2.namedWindow('Original vs Marked Image', cv2.WINDOW_NORMAL)
        # 显示对比图像
        cv2.imshow('Original vs Marked Image', comparison_image)
        # 调整窗口初始大小(可选)
        cv2.resizeWindow('Original vs Marked Image', 1200, 800)
        cv2.waitKey(0)
        cv2.destroyAllWindows()

3. 代码解释

ImagePSAnalyzer 类​:封装了 PS 特征识别的主要功能,包括直方图分析、边缘检测和图像分析。

analyze_histogram 方法​:通过分析直方图的峰值、谷值、平滑度和截断现象,判断图片是否经过了 PS 处理。

detect_edited_regions 方法​:使用边缘检测算法,检测图片中可能被编辑的区域,并标注出来。 ​
analyze_image 方法​:综合分析图像的直方图和边缘信息,判断图片是否经过了 PS 处理,并返回判断结果、提示信息和标注后的图像。

四、总结

          PS 特征识别技术可以帮助我们判断图片是否经过了处理,从而提高图片的真实性和可信度。通过直方图分析、元数据检查和边缘检测等方法,我们可以有效地识别出 PS 处理的痕迹。本文通过 Python 代码实现了一个简单的 PS 特征识别程序,你可以根据实际需求调整阈值参数,以提高识别的准确性。

          需要注意的是,PS 特征识别技术并不是 100% 准确的,一些高级的 PS 处理技术可能会掩盖这些特征。因此,在实际应用中,我们需要结合多种方法进行综合判断。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值