(一)PS识别: Python 图像分析PS识别之道

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

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

          本文将详细介绍如何使用 Python 实现一个简单的图像 PS 处理检测程序,通过分析图像的边缘特征和直方图信息来判断图像是否经过 PS 处理,同时深入探讨数字参数大小对 PS 判断结果的影响。

实现思路

我们的检测程序主要基于图像的边缘特征和边缘梯度直方图进行判断。具体步骤如下:

  1. 边缘特征提取​:使用 Canny 边缘检测算法提取图像的边缘,计算边缘像素占比、边缘连续性和边缘复杂度。
  2. 边缘梯度直方图分析​:计算图像边缘梯度的直方图,分析直方图的峰值数量、峰值高度和峰值宽度。
  3. 综合判断​:根据预设的阈值,综合边缘特征和直方图信息判断图像是否经过 PS 处理。

代码实现

1. 导入必要的库

import cv2
import numpy as np
from skimage.morphology import erosion, dilation
from scipy.signal import find_peaks
import os
import logging

这些库分别用于图像处理、数值计算、形态学操作、峰值检测、文件操作和日志记录。

2. 配置日志记录

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('ps_detection.log'),
        logging.StreamHandler()
    ]
)

3. PSEdgeChecker 类

class PSEdgeChecker:
    def __init__(self,
                 edge_ratio_threshold=0.05,
                 avg_contour_length_threshold=20,
                 edge_complexity_threshold=0.02,
                 num_peaks_threshold=3,
                 peak_height_threshold=0.1,
                 peak_width_threshold=3):
        # 初始化各项阈值
        self.EDGE_RATIO_THRESHOLD = edge_ratio_threshold
        self.AVG_CONTOUR_LENGTH_THRESHOLD = avg_contour_length_threshold
        self.EDGE_COMPLEXITY_THRESHOLD = edge_complexity_threshold
        self.NUM_PEAKS_THRESHOLD = num_peaks_threshold
        self.PEAK_HEIGHT_THRESHOLD = peak_height_threshold
        self.PEAK_WIDTH_THRESHOLD = peak_width_threshold

    def analyze_edges(self, image_path):
        # 读取图片
        image = cv2.imread(image_path, 0)
        # Canny边缘检测
        edges = cv2.Canny(image, 100, 200)
        # 计算边缘像素占比
        total_pixels = image.size
        edge_pixels = np.count_nonzero(edges)
        edge_ratio = edge_pixels / total_pixels
        # 计算边缘的连续性
        contours, _ = cv2.findContours(edges.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        contour_lengths = [len(c) for c in contours]
        avg_contour_length = np.mean(contour_lengths) if contour_lengths else 0
        # 形态学操作,计算边缘复杂度
        eroded = erosion(edges)
        dilated = dilation(edges)
        edge_complexity = np.count_nonzero(dilated - eroded) / total_pixels
        # 边缘梯度分析
        sobel_x = cv2.Sobel(image, cv2.CV_64F, 1, 0, ksize=3)
        sobel_y = cv2.Sobel(image, cv2.CV_64F, 0, 1, ksize=3)
        gradient_magnitude = np.sqrt(sobel_x ** 2 + sobel_y ** 2)
        gradient_magnitude = cv2.normalize(gradient_magnitude, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)
        gradient_hist = np.histogram(gradient_magnitude.flatten(), bins=256, range=[0, 256])[0]
        gradient_hist = gradient_hist / np.sum(gradient_hist)
        # 寻找峰值
        peaks, properties = find_peaks(gradient_hist, height=self.PEAK_HEIGHT_THRESHOLD, width=self.PEAK_WIDTH_THRESHOLD)
        num_peaks = len(peaks)
        significant_peaks = len([h for h in properties['peak_heights'] if h > self.PEAK_HEIGHT_THRESHOLD])
        return edge_ratio, avg_contour_length, edge_complexity, num_peaks, significant_peaks, edges, contours

    def is_ps(self, image_path, save_suspicious_parts=False, save_dir='suspicious_parts', save_annotated_image=True,
              annotated_dir='annotated_images'):
        edge_ratio, avg_contour_length, edge_complexity, num_peaks, significant_peaks, edges, contours = self.analyze_edges(
            image_path)
        original_image = cv2.imread(image_path)
        annotated_image = original_image.copy()
        is_ps = False
        reasons = []
        suspicious_areas = []
        annotated_image_path = None
        if edge_ratio > self.EDGE_RATIO_THRESHOLD:
            is_ps = True
            reasons.append("边缘像素占比过高")
        if avg_contour_length < self.AVG_CONTOUR_LENGTH_THRESHOLD:
            is_ps = True
            reasons.append("边缘连续性较差")
        if edge_complexity > self.EDGE_COMPLEXITY_THRESHOLD:
            is_ps = True
            reasons.append("边缘复杂度较高")
        if num_peaks > self.NUM_PEAKS_THRESHOLD or significant_peaks > self.NUM_PEAKS_THRESHOLD:
            is_ps = True
            reasons.append("边缘梯度直方图峰值过多或有效峰值过多")
        if is_ps and save_suspicious_parts:
            if not os.path.exists(save_dir):
                os.makedirs(save_dir)
            for i, contour in enumerate(contours):
                x, y, w, h = cv2.boundingRect(contour)
                suspicious_part = original_image[y:y + h, x:x + w]
                save_path = os.path.join(save_dir, f"{os.path.basename(image_path).split('.')[0]}_suspicious_{i}.jpg")
                cv2.imwrite(save_path, suspicious_part)
                suspicious_areas.append(save_path)
        if is_ps and save_annotated_image:
            if not os.path.exists(annotated_dir):
                os.makedirs(annotated_dir)
            for contour in contours:
                x, y, w, h = cv2.boundingRect(contour)
                cv2.rectangle(annotated_image, (x, y), (x + w, y + h), (0, 0, 255), 2)
            annotated_image_path = os.path.join(annotated_dir, f"{os.path.basename(image_path).split('.')[0]}_annotated.jpg")
            cv2.imwrite(annotated_image_path, annotated_image)
        reason_str = ", ".join(reasons) if reasons else "未发现异常"
        feature_values = {
            "edge_ratio": edge_ratio,
            "avg_contour_length": avg_contour_length,
            "edge_complexity": edge_complexity,
            "num_peaks": num_peaks,
            "significant_peaks": significant_peaks
        }
        return is_ps, reason_str, suspicious_areas, annotated_image_path, feature_values

PSEdgeChecker 类是核心类,负责图像边缘特征提取、边缘梯度直方图分析和 PS 处理判断。其中:

  • __init__ 方法:初始化各项判断阈值。
  • analyze_edges 方法:分析图像的边缘特征和边缘梯度直方图。
  • is_ps 方法:根据分析结果判断图像是否经过 PS 处理,可选保存可疑区域和标注图片。

4. ImagePSProcessor 类

class ImagePSProcessor:
    def __init__(self,
                 edge_ratio_threshold=0.05,
                 avg_contour_length_threshold=20,
                 edge_complexity_threshold=0.02,
                 num_peaks_threshold=3,
                 peak_height_threshold=0.1,
                 peak_width_threshold=3):
        self.ps_checker = PSEdgeChecker(
            edge_ratio_threshold=edge_ratio_threshold,
            avg_contour_length_threshold=avg_contour_length_threshold,
            edge_complexity_threshold=edge_complexity_threshold,
            num_peaks_threshold=num_peaks_threshold,
            peak_height_threshold=peak_height_threshold,
            peak_width_threshold=peak_width_threshold
        )

    def process_image(self, image_path, save_suspicious_parts=False, save_dir='suspicious_parts',
                      save_annotated_image=True, annotated_dir='annotated_images'):
        result, reason, suspicious_areas, annotated_image_path, feature_values = self.ps_checker.is_ps(
            image_path,
            save_suspicious_parts=save_suspicious_parts,
            save_dir=save_dir,
            save_annotated_image=save_annotated_image,
            annotated_dir=annotated_dir
        )
        logging.info(f"处理图片: {image_path}")
        logging.info(f"是否经过PS处理: {result}")
        logging.info(f"判断原因: {reason}")
        logging.info(f"详细特征值: {feature_values}")
        if suspicious_areas:
            logging.info(f"怀疑PS处理的部位已保存至: {', '.join(suspicious_areas)}")
        if annotated_image_path:
            logging.info(f"标注图片已保存至: {annotated_image_path}")
        print(f"图像是否经过PS处理: {result}, 原因: {reason}")
        if suspicious_areas:
            print(f"怀疑PS处理的部位已保存至: {', '.join(suspicious_areas)}")
        if annotated_image_path:
            print(f"标注图片已保存至: {annotated_image_path}")
        return result, reason, suspicious_areas, annotated_image_path, feature_values

ImagePSProcessor 类封装了 PSEdgeChecker 类,提供了更方便的图像处理接口,并记录处理日志。

5. 使用示例

if __name__ == "__main__":
    processor = ImagePSProcessor(
        edge_ratio_threshold=0.06,
        avg_contour_length_threshold=25,
        edge_complexity_threshold=0.03,
        num_peaks_threshold=4,
        peak_height_threshold=0.15,
        peak_width_threshold=4
    )
    image_path = "your_image.jpg"
    processor.process_image(image_path, save_suspicious_parts=True, save_annotated_image=True)

通过创建 ImagePSProcessor 实例,调用 process_image 方法即可对指定图片进行 PS 处理检测。

数字参数大小对 PS 判断结果的影响

1. edge_ratio_threshold(边缘像素占比阈值)

  • 数值增大​:判断会变得更加严格。程序需要边缘像素占比达到更高的数值才会判定图像经过 PS 处理,这会减少将正常图像误判为经过 PS 处理的情况,但可能会漏判一些实际经过 PS 处理、不过边缘像素占比提升不明显的图像。
  • 数值减小​:判断会更宽松。较低的边缘像素占比就可能使程序判定图像经过 PS 处理,提高了对边缘变化的敏感度,但同时也容易将正常图像误判为经过 PS 处理。

2. avg_contour_length_threshold(平均轮廓长度阈值)

  • 数值增大​:对边缘连续性的要求更高。这有助于更准确地识别边缘被破坏(如 PS 处理导致边缘断裂)的图像,但也可能把一些正常的边缘简单图像误判为经过 PS 处理,因为这些图像本身的平均轮廓长度可能就较短。
  • 数值减小​:降低了对边缘连续性的要求。减少了将正常图像误判为经过 PS 处理的可能性,但可能会漏判一些边缘处理导致连续性降低的 PS 图像。

3. edge_complexity_threshold(边缘复杂度阈值)

  • 数值增大​:判断更加严格。减少了将复杂边缘的正常图像误判为经过 PS 处理的情况,但可能会漏判一些边缘复杂度增加的 PS 处理图像。
  • 数值减小​:判断更为宽松。容易将正常的边缘复杂图像误判为经过 PS 处理,但能更敏锐地捕捉到边缘复杂度有轻微变化的 PS 处理图像。

4. num_peaks_threshold(边缘梯度直方图峰值数量阈值)

  • 数值增大​:需要更多的峰值才会判定图像经过 PS 处理。减少了误判的可能性,但可能会漏判一些峰值数量增加不明显的 PS 处理图像。
  • 数值减小​:较少的峰值就会使程序判定图像经过 PS 处理,提高了判断的灵敏度,但可能会增加误判正常图像的概率。

5. peak_height_threshold(峰值高度阈值)

  • 数值增大​:只考虑更高的峰值,减少了对低幅度波动的敏感。降低了误判的可能性,但可能会漏判一些峰值高度较低的 PS 处理图像。
  • 数值减小​:会考虑更多较低的峰值,提高了对细微变化的敏感度,但可能会增加误判的概率。

6. peak_width_threshold(峰值宽度阈值)

  • 数值增大​:只考虑更宽的峰值,减少了对窄幅波动的敏感。降低了误判的可能性,但可能会漏判一些峰值宽度较窄的 PS 处理图像。
  • 数值减小​:会考虑更多较窄的峰值,提高了对细微变化的敏感度,但可能会增加误判的概率。

   

总结

   本文介绍了一种基于 Python 的图像 PS 处理检测方法,通过分析图像的边缘特征和边缘梯度直方图,结合预设的阈值进行综合判断。同时,详细探讨了数字参数大小对 PS 判断结果的影响,不同的参数设置会在误判率和漏判率之间取得不同的平衡。在实际应用中,需要根据具体的场景和需求,通过实验不断调整这些参数,以达到最佳的检测效果。

   需要注意的是,该方法并非绝对准确,因为不同的图像处理操作可能会产生不同的边缘特征和直方图信息。在实际应用中,可能需要结合更多的特征和方法来提高检测的准确性。

Logo

GitCode 天启AI是一款由 GitCode 团队打造的智能助手,基于先进的LLM(大语言模型)与多智能体 Agent 技术构建,致力于为用户提供高效、智能、多模态的创作与开发支持。它不仅支持自然语言对话,还具备处理文件、生成 PPT、撰写分析报告、开发 Web 应用等多项能力,真正做到“一句话,让 Al帮你完成复杂任务”。

更多推荐