基于图像内容进行裁切调试过程,未成功

330 阅读14分钟

有一个地方要调整:假如用户提交的原图片尺寸是7201028,第一种情况要求裁切的尺寸是5001200,因为图片的高度没有1200高,那么应当先把原图片等比放大高度至1200,宽度1200/1028720后再进行裁切500宽1200高。第二种情况要求裁切的尺寸是8001000,因为图片的宽度没有800宽,那么应当先把原图片等比放大宽度至800,高度800/7201028后再进行裁切800宽*1000高 ,这个逻辑才是对的。补充说明。等比例放大的因子来自是原图像的宽度或高度与用户要求的宽度或高度之间的差,如果宽度不足那么就取宽度来放大,如果高度不足那就取高度来放大

根据您的描述,确实需要调整逻辑以确保在裁剪之前先进行等比例放大。具体来说,我们需要检查用户要求的宽度和高度是否超过了原始图片的尺寸,如果超过了,则需要按照要求进行等比例放大,然后再进行裁剪。

以下是根据您提供的逻辑调整后的代码:

python
深色版本
from PIL import Image, ImageOps
import cv2
import numpy as np
from mtcnn.mtcnn import MTCNN

def smart_crop(image_path, output_path, target_width, target_height):
    try:
        # 加载图片
        image = Image.open(image_path)
        img_width, img_height = image.size

        # 检查是否需要放大
        scale_width = target_width / img_width
        scale_height = target_height / img_height

        # 确定放大的比例因子
        if img_width < target_width and img_height < target_height:
            # 如果宽度和高度都不够,选择较大的缩放比例
            scale = max(scale_width, scale_height)
        elif img_width < target_width:
            # 如果只有宽度不够
            scale = scale_width
        elif img_height < target_height:
            # 如果只有高度不够
            scale = scale_height
        else:
            # 如果不需要放大
            scale = 1.0

        # 等比例放大图片(如果需要)
        new_width = int(img_width * scale)
        new_height = int(img_height * scale)
        resized_image = image.resize((new_width, new_height), Image.Resampling.LANCZOS)

        # 初始化人脸检测器
        detector = MTCNN()

        # 转换为 OpenCV 格式
        resized_image_cv = cv2.cvtColor(np.array(resized_image), cv2.COLOR_RGB2BGR)

        # 检测所有人脸
        faces = detector.detect_faces(resized_image_cv)

        if not faces:
            print("No faces detected.")
            return

        # 取第一个检测到的人脸(假设只有一个主体物)
        face = faces[0]
        x, y, w, h = face['box']

        # 计算人脸的中心位置
        center_x = x + w / 2
        center_y = y + h / 2

        # 计算裁剪区域的左上角和右下角
        left = int(center_x - target_width / 2)
        top = int(center_y - target_height / 2)
        right = int(center_x + target_width / 2)
        bottom = int(center_y + target_height / 2)

        # 确保裁剪区域不会超出图像边界
        left = max(0, left)
        top = max(0, top)
        right = min(new_width, right)
        bottom = min(new_height, bottom)

        # 如果裁剪区域太小,返回原始图片
        if right <= left or bottom <= top:
            print("Cropping area is too small. Returning the original image.")
            resized_image.save(output_path)
        else:
            # 根据计算出的边界框裁剪图片
            cropped_image = resized_image.crop((left, top, right, bottom))

            # 保存裁剪后的图片
            cropped_image.save(output_path)
            print(f"Image saved to {output_path}")

    except Exception as e:
        print(f"An error occurred: {e}")

# 使用函数
if __name__ == "__main__":
    input_image_path = 'c.png'  # 输入图片路径
    output_image_path = 'output.jpg'  # 输出图片路径
    smart_crop(input_image_path, output_image_path, target_width=500, target_height=600)

主要改进:

  1. 确定放大的比例因子:根据用户要求的宽度和高度,检查是否需要放大图片。如果需要,选择适当的缩放比例因子。

    • 如果宽度和高度都不够,选择较大的缩放比例。
    • 如果只有宽度不够,选择基于宽度的缩放比例。
    • 如果只有高度不够,选择基于高度的缩放比例。
    • 如果不需要放大,保持原图大小。
  2. 等比例放大图片:根据确定的比例因子,对图片进行等比例放大。

  3. 裁剪逻辑不变:在放大之后,继续使用原有的裁剪逻辑,确保裁剪区域以人脸为中心,并且符合用户指定的目标宽度和高度。

使用方法:

  • 调用 smart_crop 函数,并提供输入图片路径、输出图片路径以及用户指定的目标宽度和高度。确保使用 target_width 和 target_height 作为关键字参数。

示例:

  • 对于一个 720x1028 的图片,如果用户要求裁剪 500x1200 的尺寸:

    • 首先,检查图片的高度是否足够(1028 < 1200),发现高度不足。
    • 计算放大比例:scale = 1200 / 1028 ≈ 1.167
    • 放大后的图片尺寸为:720 * 1.167 ≈ 840(宽度)和 1028 * 1.167 ≈ 1200(高度)。
    • 然后进行裁剪,确保裁剪区域为 500x1200。
  • 对于一个 720x1028 的图片,如果用户要求裁剪 800x1000 的尺寸:

    • 首先,检查图片的宽度是否足够(720 < 800),发现宽度不足。
    • 计算放大比例:scale = 800 / 720 ≈ 1.111
    • 放大后的图片尺寸为:720 * 1.111 ≈ 800(宽度)和 1028 * 1.111 ≈ 1142(高度)。
    • 然后进行裁剪,确保裁剪区域为 800x1000。

这样,您可以确保在裁剪之前先进行必要的等比例放大,从而满足用户的要求。同时,代码也更加健壮,能够处理各种可能的输入情况。

如果图片里没有人物,而是风景,或动物,或物品等图片呢。该如何基于内容主体为中心进行裁切

当图片中没有人物时,基于内容主体进行裁剪变得更加复杂,因为需要检测和定位图像中的主要视觉兴趣点(即主体)。对于这种情况,可以使用计算机视觉技术来自动识别和定位图像中的显著区域。以下是几种常见的方法:

1. 显著性检测 (Saliency Detection)

显著性检测是一种用于识别图像中最吸引人注意的区域的技术。它可以帮助我们找到图像中的主要视觉焦点,即使这些焦点不是人脸。

使用 OpenCV 的显著性检测:

OpenCV 提供了内置的显著性检测模块,可以用来识别图像中的显著区域。以下是使用 OpenCV 进行显著性检测并基于显著区域进行裁剪的示例代码:

python
深色版本
import cv2
import numpy as np
from PIL import Image

def get_salient_region(image_cv):
    # 创建显著性对象
    saliency = cv2.saliency.StaticSaliencyFineGrained_create()
    
    # 计算显著性图
    (success, saliencyMap) = saliency.computeSaliency(image_cv)
    if not success:
        print("Failed to compute saliency map.")
        return None
    
    # 将显著性图转换为二值图
    saliencyMap = (saliencyMap * 255).astype("uint8")
    _, binaryMap = cv2.threshold(saliencyMap, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
    
    # 查找显著区域的轮廓
    contours, _ = cv2.findContours(binaryMap, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    
    if not contours:
        print("No salient region detected.")
        return None
    
    # 找到最大的显著区域
    largest_contour = max(contours, key=cv2.contourArea)
    x, y, w, h = cv2.boundingRect(largest_contour)
    
    # 返回显著区域的中心点
    center_x = x + w // 2
    center_y = y + h // 2
    return center_x, center_y

def smart_crop_no_faces(image_path, output_path, target_width, target_height):
    try:
        # 加载图片
        image = Image.open(image_path)
        img_width, img_height = image.size

        # 检查是否需要放大
        scale_width = target_width / img_width
        scale_height = target_height / img_height

        # 确定放大的比例因子
        if img_width < target_width or img_height < target_height:
            scale = max(scale_width, scale_height)
        else:
            scale = 1.0

        # 等比例放大图片(如果需要)
        new_width = int(img_width * scale)
        new_height = int(img_height * scale)
        resized_image = image.resize((new_width, new_height), Image.Resampling.LANCZOS)

        # 转换为 OpenCV 格式
        resized_image_cv = cv2.cvtColor(np.array(resized_image), cv2.COLOR_RGB2BGR)

        # 获取显著区域的中心点
        salient_center = get_salient_region(resized_image_cv)
        if salient_center is None:
            print("No significant region found. Cropping from the center of the image.")
            center_x = new_width // 2
            center_y = new_height // 2
        else:
            center_x, center_y = salient_center

        # 计算裁剪区域的左上角和右下角
        left = int(center_x - target_width / 2)
        top = int(center_y - target_height / 2)
        right = int(center_x + target_width / 2)
        bottom = int(center_y + target_height / 2)

        # 确保裁剪区域不会超出图像边界
        left = max(0, left)
        top = max(0, top)
        right = min(new_width, right)
        bottom = min(new_height, bottom)

        # 如果裁剪区域太小,返回原始图片
        if right <= left or bottom <= top:
            print("Cropping area is too small. Returning the original image.")
            resized_image.save(output_path)
        else:
            # 根据计算出的边界框裁剪图片
            cropped_image = resized_image.crop((left, top, right, bottom))

            # 保存裁剪后的图片
            cropped_image.save(output_path)
            print(f"Image saved to {output_path}")

    except Exception as e:
        print(f"An error occurred: {e}")

# 使用函数
if __name__ == "__main__":
    input_image_path = 'c.png'  # 输入图片路径
    output_image_path = 'output.jpg'  # 输出图片路径
    smart_crop_no_faces(input_image_path, output_image_path, target_width=500, target_height=600)

2. 边缘检测 (Edge Detection)

边缘检测可以帮助我们找到图像中的物体轮廓,从而确定图像的主要内容区域。虽然边缘检测不如显著性检测那么精确,但它可以作为备用方案,尤其是在图像中没有明显的显著区域时。

使用 Canny 边缘检测:

Canny 边缘检测是常用的边缘检测算法之一。可以通过以下方式结合边缘检测来确定裁剪区域:

python
深色版本
def get_edge_center(image_cv):
    # 使用 Canny 边缘检测
    edges = cv2.Canny(image_cv, 100, 200)
    
    # 查找边缘的轮廓
    contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    
    if not contours:
        print("No edges detected.")
        return None
    
    # 找到最大的边缘区域
    largest_contour = max(contours, key=cv2.contourArea)
    x, y, w, h = cv2.boundingRect(largest_contour)
    
    # 返回边缘区域的中心点
    center_x = x + w // 2
    center_y = y + h // 2
    return center_x, center_y

3. 深度学习模型 (Deep Learning Models)

现代深度学习模型(如 Faster R-CNN、YOLO、Mask R-CNN 等)可以用于检测图像中的物体,并提供物体的边界框。这些模型不仅可以检测常见的物体类别(如汽车、动物、建筑物等),还可以提供更精确的定位信息。

使用预训练的物体检测模型:

可以使用 cv2.dnn 模块加载预训练的物体检测模型(如 YOLO 或 SSD),并根据检测到的物体进行裁剪。以下是一个简单的例子:

python
深色版本
def get_object_center(image_cv):
    # 加载预训练的物体检测模型(例如 YOLO)
    net = cv2.dnn.readNetFromDarknet('yolov3.cfg', 'yolov3.weights')
    layer_names = net.getLayerNames()
    output_layers = [layer_names[i[0] - 1] for i in net.getUnconnectedOutLayers()]

    # 准备输入图像
    blob = cv2.dnn.blobFromImage(image_cv, 1/255.0, (416, 416), swapRB=True, crop=False)
    net.setInput(blob)
    outs = net.forward(output_layers)

    # 解析检测结果
    class_ids = []
    confidences = []
    boxes = []

    for out in outs:
        for detection in out:
            scores = detection[5:]
            class_id = np.argmax(scores)
            confidence = scores[class_id]
            if confidence > 0.5:
                # 物体检测成功
                center_x = int(detection[0] * image_cv.shape[1])
                center_y = int(detection[1] * image_cv.shape[0])
                width = int(detection[2] * image_cv.shape[1])
                height = int(detection[3] * image_cv.shape[0])
                boxes.append([center_x, center_y, width, height])
                confidences.append(float(confidence))
                class_ids.append(class_id)

    # 应用非极大值抑制 (NMS) 去除冗余的检测框
    indices = cv2.dnn.NMSBoxes(boxes, confidences, 0.5, 0.4)

    if len(indices) > 0:
        # 取置信度最高的物体
        idx = indices[0]
        box = boxes[idx]
        center_x, center_y, width, height = box
        return center_x, center_y
    else:
        print("No objects detected.")
        return None

4. 综合策略

为了提高准确性,可以结合多种方法。例如,首先尝试使用显著性检测来找到显著区域,如果失败则使用边缘检测或物体检测。这样可以确保即使在复杂的图像中也能找到合适的内容主体。

5. 默认行为

如果所有方法都未能找到显著区域或物体,可以选择从图像的中心进行裁剪,或者提示用户手动选择裁剪区域。

总结

  • 显著性检测 是一种有效的方法,适用于大多数自然场景的图像。
  • 边缘检测 可以作为备用方案,尤其适用于结构化较强的图像。
  • 深度学习模型 提供了最精确的物体检测和定位能力,但需要更多的计算资源。
  • 综合策略 可以提高鲁棒性,确保在不同类型的图像中都能找到合适的内容主体。

通过这些方法,您可以实现更加智能的图像裁剪,确保裁剪后的图像始终以主要内容为中心,而不仅仅是基于固定的位置或比例。

我要的是这种功能

Preview

要实现类似于图片中展示的功能,即根据不同的尺寸要求对图片进行裁剪,同时保持图片中的主要内容(如人物)居中,可以采用以下步骤:

  1. 检测主要内容区域:使用显著性检测或物体检测技术来识别图片中的主要内容区域。
  2. 等比例放大:如果图片的尺寸不足以满足目标尺寸要求,则先进行等比例放大。
  3. 裁剪:根据主要内容区域的位置进行裁剪,确保主要内容居中。

以下是一个示例代码,使用OpenCV和显著性检测来实现这一功能:

python
深色版本
import cv2
import numpy as np
from PIL import Image

def get_salient_region(image_cv):
    # 创建显著性对象
    saliency = cv2.saliency.StaticSaliencyFineGrained_create()
    
    # 计算显著性图
    (success, saliencyMap) = saliency.computeSaliency(image_cv)
    if not success:
        print("Failed to compute saliency map.")
        return None
    
    # 将显著性图转换为二值图
    saliencyMap = (saliencyMap * 255).astype("uint8")
    _, binaryMap = cv2.threshold(saliencyMap, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
    
    # 查找显著区域的轮廓
    contours, _ = cv2.findContours(binaryMap, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    
    if not contours:
        print("No salient region detected.")
        return None
    
    # 找到最大的显著区域
    largest_contour = max(contours, key=cv2.contourArea)
    x, y, w, h = cv2.boundingRect(largest_contour)
    
    # 返回显著区域的中心点
    center_x = x + w // 2
    center_y = y + h // 2
    return center_x, center_y

def smart_crop(image_path, output_path, target_width, target_height):
    try:
        # 加载图片
        image = Image.open(image_path)
        img_width, img_height = image.size

        # 检查是否需要放大
        scale_width = target_width / img_width
        scale_height = target_height / img_height

        # 确定放大的比例因子
        if img_width < target_width or img_height < target_height:
            scale = max(scale_width, scale_height)
        else:
            scale = 1.0

        # 等比例放大图片(如果需要)
        new_width = int(img_width * scale)
        new_height = int(img_height * scale)
        resized_image = image.resize((new_width, new_height), Image.Resampling.LANCZOS)

        # 转换为 OpenCV 格式
        resized_image_cv = cv2.cvtColor(np.array(resized_image), cv2.COLOR_RGB2BGR)

        # 获取显著区域的中心点
        salient_center = get_salient_region(resized_image_cv)
        if salient_center is None:
            print("No significant region found. Cropping from the center of the image.")
            center_x = new_width // 2
            center_y = new_height // 2
        else:
            center_x, center_y = salient_center

        # 计算裁剪区域的左上角和右下角
        left = int(center_x - target_width / 2)
        top = int(center_y - target_height / 2)
        right = int(center_x + target_width / 2)
        bottom = int(center_y + target_height / 2)

        # 确保裁剪区域不会超出图像边界
        left = max(0, left)
        top = max(0, top)
        right = min(new_width, right)
        bottom = min(new_height, bottom)

        # 如果裁剪区域太小,返回原始图片
        if right <= left or bottom <= top:
            print("Cropping area is too small. Returning the original image.")
            resized_image.save(output_path)
        else:
            # 根据计算出的边界框裁剪图片
            cropped_image = resized_image.crop((left, top, right, bottom))

            # 保存裁剪后的图片
            cropped_image.save(output_path)
            print(f"Image saved to {output_path}")

    except Exception as e:
        print(f"An error occurred: {e}")

# 使用函数
if __name__ == "__main__":
    input_image_path = 'input_image.jpg'  # 输入图片路径
    output_image_path = 'output_image.jpg'  # 输出图片路径
    smart_crop(input_image_path, output_image_path, target_width=500, target_height=600)

代码解释:

  1. 显著性检测

    • 使用OpenCV的显著性检测模块来计算显著性图。
    • 将显著性图转换为二值图,并查找显著区域的轮廓。
    • 找到最大的显著区域,并计算其中心点。
  2. 等比例放大

    • 检查图片的宽度和高度是否满足目标尺寸要求。
    • 如果不满足,则计算放大比例,并对图片进行等比例放大。
  3. 裁剪

    • 根据显著区域的中心点计算裁剪区域的边界。
    • 确保裁剪区域不会超出图片边界。
    • 如果裁剪区域太小,则返回原始图片。

使用方法:

  • 调用 smart_crop 函数,并提供输入图片路径、输出图片路径以及目标宽度和高度。

示例:

  • 对于一个 720x1028 的图片,如果用户要求裁剪 500x1200 的尺寸:

    • 首先,检查图片的高度是否足够(1028 < 1200),发现高度不足。
    • 计算放大比例:scale = 1200 / 1028 ≈ 1.167
    • 放大后的图片尺寸为:720 * 1.167 ≈ 840(宽度)和 1028 * 1.167 ≈ 1200(高度)。
    • 然后进行裁剪,确保裁剪区域为 500x1200。
  • 对于一个 720x1028 的图片,如果用户要求裁剪 800x1000 的尺寸:

    • 首先,检查图片的宽度是否足够(720 < 800),发现宽度不足。
    • 计算放大比例:scale = 800 / 720 ≈ 1.111
    • 放大后的图片尺寸为:720 * 1.111 ≈ 800(宽度)和 1028 * 1.111 ≈ 1142(高度)。
    • 然后进行裁剪,确保裁剪区域为 800x1000。

通过这种方式,可以确保裁剪后的图片始终以主要内容为中心,并且满足指定的尺寸要求。