固定轮廓内黑色异物检测

图像轮廓与黑色区域分析工具

概述

本工具用于分析图像中的大轮廓及其内部黑色区域,计算轮廓面积、白黑像素比值等关键参数,并将分析结果可视化标注在图像上。适用于需要对图像中特定区域进行定量分析的场景,如工业检测、材料分析等。

功能特点

  • 自动识别图像中的大轮廓区域
  • 分析轮廓内的黑色区域并计算面积
  • 计算轮廓的白黑像素比值
  • 可视化标注所有分析结果(轮廓、面积、比值等)
  • 批量处理图像并生成汇总报告

函数说明

核心函数

calculate_contour_ratios(img_path)

功能:分析单张图像的轮廓和黑色区域,返回分析结果并保存标注后的图像
参数

  • img_path:图像文件的路径(字符串)

返回值

  • 列表,每个元素为元组 (面积, 白像素/黑像素比值),对应每个符合条件的大轮廓
  • 若图像读取失败或未检测到有效轮廓,返回空列表

流程

  1. 调用预处理函数处理图像
  2. 提取并过滤大轮廓
  3. 对每个大轮廓计算面积和白黑比
  4. 分析轮廓内的黑色区域
  5. 绘制所有标注信息
  6. 保存结果图像并返回分析数据

辅助函数

_preprocess_image(img_path)

功能:图像预处理,包括读取图像、转灰度图、二值化和形态学操作
返回值

  • 原图、灰度图、处理后的二值图(三元组)
  • 若图像读取失败,返回 (None, None, None)
_extract_large_contours(binary_img, min_area=105000)

功能:从二值图像中提取轮廓并过滤出面积足够大的轮廓
参数

  • binary_img:二值化图像
  • min_area:轮廓面积阈值,默认105000

返回值:符合面积条件的轮廓列表

_process_black_regions(contour, binary_img)

功能:处理单个大轮廓内的黑色区域,返回符合条件的黑色区域信息
参数

  • contour:大轮廓坐标数组
  • binary_img:二值化图像

返回值:列表,每个元素为字典 {"contour": 轮廓坐标, "area": 面积}

_draw_large_contour_info(img, contour, area, ratio)

功能:在图像上绘制大轮廓的面积和白黑比标注
参数

  • img:待绘制的图像
  • contour:大轮廓坐标
  • area:轮廓面积
  • ratio:白黑像素比值
_draw_black_region_info(img, black_regions)

功能:在图像上绘制黑色区域的轮廓和面积标注
参数

  • img:待绘制的图像
  • black_regions:黑色区域信息列表(_process_black_regions 的返回值)
_save_result_image(img_path, img)

功能:保存处理后的图像并返回保存路径
参数

  • img_path:原始图像路径
  • img:带标注的图像

返回值:保存的图像路径

使用方法

单张图像分析

result = calculate_contour_ratios("path/to/your/image.jpg")
if result:
    print("分析结果:")
    for area, ratio in result:
        print(f"面积: {area:.0f}, 白黑比: {ratio:.2f}")
else:
    print("分析失败")

批量图像分析

image_paths = ["image1.jpg", "image2.jpg", "image3.jpg"]
all_results = []

for path in image_paths:
    print(f"处理图像: {path}")
    results = calculate_contour_ratios(path)
    if results:
        all_results.extend(results)

# 打印汇总结果
print(f"\n总分析轮廓数: {len(all_results)}")

输出说明

  1. 控制台输出

    • 处理进度信息
    • 每个图像的轮廓数量
    • 每个轮廓的面积和白黑比
    • 汇总统计信息
  2. 图像输出

    • 在原始图像路径下生成标注后的图像(文件名格式:原文件名_contour.扩展名
    • 红色线:大轮廓
    • 绿色线:黑色区域轮廓
    • 蓝色文本:大轮廓面积
    • 绿色文本:白黑像素比值
    • 黄色文本:黑色区域面积

参数调整

可根据实际需求调整以下参数:

  1. 大轮廓面积阈值:_extract_large_contours 函数中的 min_area 参数
  2. 黑色区域面积阈值:_process_black_regions 函数中的 min_black_area 参数
  3. 形态学操作核大小:_process_black_regions 函数中的 small_kernel 定义
  4. 腐蚀膨胀迭代次数:_process_black_regions 函数中的 iterations 参数
  5. 二值化阈值:_preprocess_image 函数中的阈值参数(当前为20)

调整这些参数可以优化不同类型图像的分析效果。

import cv2
import numpy as np
import os


def _preprocess_image(img_path):
    """预处理图像:读取、转灰度、二值化和形态学操作"""
    # 读取图像
    img = cv2.imread(img_path)
    if img is None:
        return None, None, None

    # 转为灰度图
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    # 阈值分割(提取白色区域)
    ret, binary = cv2.threshold(gray, 20, 255, cv2.THRESH_BINARY)

    # 形态学操作:增强白色区域连通度
    kernel = np.ones((7, 7), np.uint8)
    opening = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel, iterations=1)
    closing = cv2.morphologyEx(opening, cv2.MORPH_CLOSE, kernel, iterations=3)

    return img, gray, closing


def _extract_large_contours(binary_img, min_area=105000):
    """提取并过滤出面积足够大的轮廓"""
    contours, hierarchy = cv2.findContours(
        binary_img,
        cv2.RETR_EXTERNAL,
        cv2.CHAIN_APPROX_SIMPLE
    )
    return [cnt for cnt in contours if cv2.contourArea(cnt) >= min_area]


def _process_black_regions(contour, binary_img):
    """处理单个大轮廓内的黑色区域,返回符合条件的黑色区域信息"""
    # 创建轮廓掩码
    mask = np.zeros_like(binary_img)
    cv2.drawContours(mask, [contour], -1, 255, -1)

    # 提取轮廓内的区域并反转以突出黑色区域
    contour_region = cv2.bitwise_and(binary_img, mask)
    black_regions = cv2.bitwise_not(contour_region)

    # 阈值处理确保纯黑白
    _, black_binary = cv2.threshold(black_regions, 127, 255, cv2.THRESH_BINARY)

    # 形态学操作:断开细小连接
    small_kernel = np.ones((7, 7), np.uint8)
    eroded = cv2.erode(black_binary, small_kernel, iterations=3)
    dilated = cv2.dilate(eroded, small_kernel, iterations=1)
    dilated = cv2.bitwise_and(dilated, mask)  # 确保在大轮廓内

    # 查找黑色区域轮廓
    black_contours, _ = cv2.findContours(dilated, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

    # 过滤并计算面积
    min_black_area = 8000
    return [
        {"contour": cnt, "area": cv2.contourArea(cnt)}
        for cnt in black_contours
        if cv2.contourArea(cnt) >= min_black_area
    ]


def _draw_large_contour_info(img, contour, area, ratio):
    """在图像上绘制大轮廓的面积和比值信息"""
    font = cv2.FONT_HERSHEY_SIMPLEX
    font_scale = 0.7
    ratio_color = (0, 255, 0)
    area_color = (255, 0, 0)
    thickness = 2

    x, y, w, h = cv2.boundingRect(contour)
    area_text = f"Area: {int(area)}"
    ratio_text = f"Ratio: {ratio:.2f}" if ratio != float('inf') else "Ratio: inf"

    # 计算文本尺寸
    ratio_size = cv2.getTextSize(ratio_text, font, font_scale, thickness)[0]
    area_size = cv2.getTextSize(area_text, font, font_scale, thickness)[0]
    max_width = max(ratio_size[0], area_size[0])
    total_height = ratio_size[1] + area_size[1] + 10

    # 确定文本位置
    if y > total_height + 10:  # 顶部放置
        ratio_pos = (x, y - 10)
        area_pos = (x, y - ratio_size[1] - 20)
        bg_top = area_pos[1] - 5
        bg_bottom = ratio_pos[1] + 5
    else:  # 底部放置
        ratio_pos = (x, y + h + 30)
        area_pos = (x, y + h + 30 + ratio_size[1] + 10)
        bg_top = ratio_pos[1] - ratio_size[1] - 5
        bg_bottom = area_pos[1] + 5

    # 绘制文本背景
    cv2.rectangle(img, (x, bg_top), (x + max_width + 5, bg_bottom), (0, 0, 0), -1)

    # 绘制文本
    cv2.putText(img, ratio_text, ratio_pos, font, font_scale, ratio_color, thickness, cv2.LINE_AA)
    cv2.putText(img, area_text, area_pos, font, font_scale, area_color, thickness, cv2.LINE_AA)


def _draw_black_region_info(img, black_regions):
    """在图像上绘制黑色区域的轮廓和面积信息"""
    font = cv2.FONT_HERSHEY_SIMPLEX
    font_scale = 0.7
    color = (0, 255, 255)  # 黄色
    contour_color = (0, 255, 0)  # 绿色
    thickness = 2

    for region in black_regions:
        cnt = region["contour"]
        area = region["area"]

        # 绘制黑色区域轮廓
        cv2.drawContours(img, [cnt], -1, contour_color, 2)

        # 准备面积文本
        x, y, w, h = cv2.boundingRect(cnt)
        text = f"Black: {int(area)}"
        text_size = cv2.getTextSize(text, font, font_scale, thickness)[0]

        # 确定文本位置
        if y > 20:  # 上方放置
            text_pos = (x, y - 10)
            bg_top = text_pos[1] - text_size[1] - 5
            bg_bottom = text_pos[1] + 5
        else:  # 下方放置
            text_pos = (x, y + h + 20)
            bg_top = text_pos[1] - text_size[1] - 5
            bg_bottom = text_pos[1] + 5

        # 绘制文本背景和文本
        cv2.rectangle(img, (x, bg_top), (x + text_size[0] + 5, bg_bottom), (0, 0, 0), -1)
        cv2.putText(img, text, text_pos, font, font_scale, color, thickness, cv2.LINE_AA)


def _save_result_image(img_path, img):
    """保存处理后的图像并返回保存路径"""
    file_dir, file_name = os.path.split(img_path)
    name, ext = os.path.splitext(file_name)
    save_path = os.path.join(file_dir, f"{name}_contour{ext}")
    cv2.imwrite(save_path, img)
    return save_path


def calculate_contour_ratios(img_path):
    """
    分析图像中的轮廓,计算每个轮廓的面积和白黑像素比值,绘制轮廓并标注后保存

    参数:
        img_path: 图像文件的路径

    返回:
        列表,包含每个符合条件的轮廓的(面积, 白像素/黑像素比值)元组
        若图像读取失败,返回空列表
    """
    # 预处理图像
    img, gray, binary = _preprocess_image(img_path)
    if img is None:
        print(f"无法读取图像,请检查路径是否正确:{img_path}")
        return []

    # 提取大轮廓
    filtered_contours = _extract_large_contours(binary)
    if not filtered_contours:
        print(f"图像 {img_path} 未检测到符合条件的大轮廓")
        return []

    # 准备绘制
    img_with_contours = img.copy()
    result_list = []

    # 处理每个大轮廓
    for cnt in filtered_contours:
        # 计算大轮廓面积和白黑比
        area = cv2.contourArea(cnt)
        mask = np.zeros_like(binary)
        cv2.drawContours(mask, [cnt], -1, 255, -1)
        white_pixels = cv2.countNonZero(cv2.bitwise_and(binary, mask))
        total_pixels = cv2.countNonZero(mask)
        black_pixels = total_pixels - white_pixels
        ratio = white_pixels / black_pixels if black_pixels != 0 else float('inf')

        # 存储结果
        result_list.append((area, ratio))

        # 绘制大轮廓信息
        _draw_large_contour_info(img_with_contours, cnt, area, ratio)

        # 处理并绘制黑色区域
        black_regions = _process_black_regions(cnt, binary)
        _draw_black_region_info(img_with_contours, black_regions)

    # 绘制所有大轮廓
    cv2.drawContours(img_with_contours, filtered_contours, -1, (0, 0, 255), 2)

    # 保存结果图像
    save_path = _save_result_image(img_path, img_with_contours)
    print(f"带轮廓、面积和比值的图像已保存至:{save_path}")

    return result_list


# 使用示例
if __name__ == "__main__":
    image_paths = ["D:\\img\\test3\\1.jpg",
                   "D:\\img\\test3\\2.jpg",
                   "D:\\img\\test3\\3.jpg",
                   "D:\\img\\test3\\4.jpg",
                   "D:\\img\\test3\\5.jpg",
                   "D:\\img\\test3\\6.jpg",
                   "D:\\img\\test3\\7.jpg",
                   "D:\\img\\test3\\8.jpg",
                   "D:\\img\\test3\\9.jpg",
                   "D:\\img\\test3\\10.jpg"]

    all_results = []
    for path in image_paths:
        print(f"处理图像: {path}")
        results = calculate_contour_ratios(path)

        if results:
            print(f"  检测到 {len(results)} 个轮廓")
            for i, (area, ratio) in enumerate(results, 1):
                print(f"  轮廓 {i}: 面积={area:.0f}, 对比度={ratio:.4f}")
            all_results.extend(results)
        else:
            print("  未检测到符合条件的轮廓或图像读取失败")

    print("\n所有图像的轮廓信息汇总:")
    print(f"总共有 {len(all_results)} 个轮廓")
    for i, (area, ratio) in enumerate(all_results, 1):
        print(f"  轮廓 {i}: 面积={area:.0f}, 对比度={ratio:.4f}")

代码优化

import cv2
import numpy as np
import os


def _preprocess_image(img_path , threshold):
    """预处理图像:读取、转灰度、二值化和形态学操作"""
    # 读取图像
    img = cv2.imread(img_path)
    if img is None:
        return None, None, None

    # 转为灰度图
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    # 阈值分割(提取白色区域)
    ret, binary = cv2.threshold(gray, threshold, 255, cv2.THRESH_BINARY)

    # 形态学操作:增强白色区域连通度
    kernel = np.ones((7, 7), np.uint8)
    opening = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel, iterations=1)
    closing = cv2.morphologyEx(opening, cv2.MORPH_CLOSE, kernel, iterations=3)

    return img, gray, closing


def _extract_large_contours(binary_img, min_area=105000):
    """提取并过滤出面积足够大的轮廓"""
    contours, hierarchy = cv2.findContours(
        binary_img,
        cv2.RETR_EXTERNAL,
        cv2.CHAIN_APPROX_SIMPLE
    )
    return [cnt for cnt in contours if cv2.contourArea(cnt) >= min_area]


def _process_black_regions(contour, binary_img , area):
    """处理单个大轮廓内的黑色区域,返回符合条件的黑色区域信息"""
    # 创建轮廓掩码
    mask = np.zeros_like(binary_img)
    cv2.drawContours(mask, [contour], -1, 255, -1)

    # 提取轮廓内的区域并反转以突出黑色区域
    contour_region = cv2.bitwise_and(binary_img, mask)
    black_regions = cv2.bitwise_not(contour_region)

    # 阈值处理确保纯黑白
    _, black_binary = cv2.threshold(black_regions, 127, 255, cv2.THRESH_BINARY)

    # 形态学操作:断开细小连接
    small_kernel = np.ones((7, 7), np.uint8)
    eroded = cv2.erode(black_binary, small_kernel, iterations=3)
    dilated = cv2.dilate(eroded, small_kernel, iterations=1)
    dilated = cv2.bitwise_and(dilated, mask)  # 确保在大轮廓内

    # 查找黑色区域轮廓
    black_contours, _ = cv2.findContours(dilated, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

    # 过滤并计算面积
    min_black_area = area
    return [
        {"contour": cnt, "area": cv2.contourArea(cnt)}
        for cnt in black_contours
        if cv2.contourArea(cnt) >= min_black_area
    ]


def _draw_large_contour_info(img, contour, area, ratio):
    """在图像上绘制大轮廓的面积和比值信息"""
    font = cv2.FONT_HERSHEY_SIMPLEX
    font_scale = 0.7
    ratio_color = (0, 255, 0)
    area_color = (255, 0, 0)
    thickness = 2

    x, y, w, h = cv2.boundingRect(contour)
    area_text = f"Area: {int(area)}"
    ratio_text = f"Ratio: {ratio:.2f}" if ratio != float('inf') else "Ratio: inf"

    # 计算文本尺寸
    ratio_size = cv2.getTextSize(ratio_text, font, font_scale, thickness)[0]
    area_size = cv2.getTextSize(area_text, font, font_scale, thickness)[0]
    max_width = max(ratio_size[0], area_size[0])
    total_height = ratio_size[1] + area_size[1] + 10

    # 确定文本位置
    if y > total_height + 10:  # 顶部放置
        ratio_pos = (x, y - 10)
        area_pos = (x, y - ratio_size[1] - 20)
        bg_top = area_pos[1] - 5
        bg_bottom = ratio_pos[1] + 5
    else:  # 底部放置
        ratio_pos = (x, y + h + 30)
        area_pos = (x, y + h + 30 + ratio_size[1] + 10)
        bg_top = ratio_pos[1] - ratio_size[1] - 5
        bg_bottom = area_pos[1] + 5

    # 绘制文本背景
    cv2.rectangle(img, (x, bg_top), (x + max_width + 5, bg_bottom), (0, 0, 0), -1)

    # 绘制文本
    cv2.putText(img, ratio_text, ratio_pos, font, font_scale, ratio_color, thickness, cv2.LINE_AA)
    cv2.putText(img, area_text, area_pos, font, font_scale, area_color, thickness, cv2.LINE_AA)


def _draw_black_region_info(img, black_regions):
    """在图像上绘制黑色区域的轮廓和面积信息"""
    font = cv2.FONT_HERSHEY_SIMPLEX
    font_scale = 0.7
    color = (0, 255, 255)  # 黄色
    contour_color = (0, 255, 0)  # 绿色
    thickness = 2

    for region in black_regions:
        cnt = region["contour"]
        area = region["area"]

        # 绘制黑色区域轮廓
        cv2.drawContours(img, [cnt], -1, contour_color, 2)

        # 准备面积文本
        x, y, w, h = cv2.boundingRect(cnt)
        text = f"Black: {int(area)}"
        text_size = cv2.getTextSize(text, font, font_scale, thickness)[0]

        # 确定文本位置
        if y > 20:  # 上方放置
            text_pos = (x, y - 10)
            bg_top = text_pos[1] - text_size[1] - 5
            bg_bottom = text_pos[1] + 5
        else:  # 下方放置
            text_pos = (x, y + h + 20)
            bg_top = text_pos[1] - text_size[1] - 5
            bg_bottom = text_pos[1] + 5

        # 绘制文本背景和文本
        cv2.rectangle(img, (x, bg_top), (x + text_size[0] + 5, bg_bottom), (0, 0, 0), -1)
        cv2.putText(img, text, text_pos, font, font_scale, color, thickness, cv2.LINE_AA)


def _save_result_image(img_path, img):
    """保存处理后的图像并返回保存路径"""
    file_dir, file_name = os.path.split(img_path)
    name, ext = os.path.splitext(file_name)
    save_path = os.path.join(file_dir, f"{name}_contour{ext}")
    cv2.imwrite(save_path, img)
    return save_path


def calculate_contour_ratios(img_path , threshold , area1 , area2):
    """
    分析图像中的轮廓,计算每个轮廓的面积和白黑像素比值,绘制轮廓并标注后保存

    参数:
        img_path: 图像文件的路径

    返回:
        列表,包含每个符合条件的轮廓的(面积, 白像素/黑像素比值)元组
        若图像读取失败,返回空列表
    """
    # 预处理图像
    img, gray, binary = _preprocess_image(img_path , threshold)
    if img is None:
        print(f"无法读取图像,请检查路径是否正确:{img_path}")
        return []

    # 提取大轮廓
    filtered_contours = _extract_large_contours(binary ,area1 )
    if not filtered_contours:
        print(f"图像 {img_path} 未检测到符合条件的大轮廓")
        return []

    # 准备绘制
    img_with_contours = img.copy()
    result_list = []
    black_list =[]
    # 处理每个大轮廓
    for cnt in filtered_contours:
        # 计算大轮廓面积和白黑比
        area = cv2.contourArea(cnt)
        mask = np.zeros_like(binary)
        cv2.drawContours(mask, [cnt], -1, 255, -1)
        white_pixels = cv2.countNonZero(cv2.bitwise_and(binary, mask))
        total_pixels = cv2.countNonZero(mask)
        black_pixels = total_pixels - white_pixels
        ratio = white_pixels / black_pixels if black_pixels != 0 else float('inf')

        # 存储结果
        result_list.append((area, ratio))

        # 绘制大轮廓信息
        _draw_large_contour_info(img_with_contours, cnt, area, ratio)

        # 处理并绘制黑色区域
        black_regions = _process_black_regions(cnt, binary , area2)
        black_list.extend(black_regions)
        _draw_black_region_info(img_with_contours, black_regions)

    # 绘制所有大轮廓
    cv2.drawContours(img_with_contours, filtered_contours, -1, (0, 0, 255), 2)

    # 保存结果图像
    save_path = _save_result_image(img_path, img_with_contours)
    print(f"带轮廓、面积和比值的图像已保存至:{save_path}")

    return result_list , black_list


# 使用示例
if __name__ == "__main__":
    image_paths = ["D:\\img\\test3\\1.jpg",
                   "D:\\img\\test3\\2.jpg",
                   "D:\\img\\test3\\3.jpg",
                   "D:\\img\\test3\\4.jpg",
                   "D:\\img\\test3\\5.jpg",
                   "D:\\img\\test3\\6.jpg",
                   "D:\\img\\test3\\7.jpg",
                   "D:\\img\\test3\\8.jpg",
                   "D:\\img\\test3\\9.jpg",
                   "D:\\img\\test3\\10.jpg"]

    all_results = []
    all_results_black = []
    for path in image_paths:
        print(f"处理图像: {path}")
        results,result_black = calculate_contour_ratios(path,20,105000,3000)

        if results:
            print(f"  检测到 {len(results)} 个轮廓")
            for i, (area, ratio) in enumerate(results, 1):
                print(f"  轮廓 {i}: 面积={area:.0f}, 对比度={ratio:.4f}")
            all_results.extend(results)
        else:
            print("  未检测到符合条件的轮廓或图像读取失败")

        print(result_black)
        all_results_black.extend(result_black)
    print("\n所有图像的轮廓信息汇总:")
    print("找到黑色异物个数"+str(len(all_results_black)))
    print(f"总共有 {len(all_results)} 个轮廓")
    for i, (area, ratio) in enumerate(all_results, 1):
        print(f"  轮廓 {i}: 面积={area:.0f}, 对比度={ratio:.4f}")

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值