OPCV图像形状识别及面积计算

OPCV图像形状识别及面积计算

最近在忙着学习写微信小程序,然后学校又在举办篮球比赛,事情一多就没什么时间写东西。今天抽空把这个项目补完一下,主要是分享一下使用 OpenCV 来进行简单的形状检测和面积计算的思路。


前言

在做图像处理的过程中,我们常常需要计算图像中的目标面积。常规做法有以下两种思路:

  1. 像素点计数法
    通过计算目标在图像中的像素数量来推算面积大小。
    • 缺点:目标与摄像头的距离会直接影响像素计数的结果,需要对距离或拍摄角度等因素进行校正。
  2. 固定距离对比法
    在拍摄时固定好目标与摄像头之间的距离,然后借助一个已知尺寸的参照物(比如 A4 纸)来估算出目标的实际面积。
    • 缺点:依然需要保证拍摄过程中距离、角度等尽量保持一致,否则会带来误差。

这里,我采用了第二种思路:在图像中预留了一块 A4 纸区域,用它来换算像素面积与实际面积之间的比例。下面给出部分核心代码片段,演示如何在 Python 中利用 OpenCV 进行形状检测并计算面积。


代码思路简介

主要功能如下:

  1. 形状检测
    • 通过 cv2.findContours 提取轮廓,再根据多边形逼近、角度特征等信息区分三角形、矩形、正方形和圆形。
  2. 数字识别(可选)
    • 先加载数字模板,再使用 cv2.matchTemplate 进行匹配,用以识别形状内部可能标注的数字。
  3. 面积计算
    • 将像素面积转换为实际面积:
      [
      \text{实际面积} = \text{轮廓像素面积} \times \text{单位像素面积}
      ]
    • 其中“单位像素面积”通过已知的 A4 纸面积和在图像中的像素占比计算得出。

看图
在这里插入图片描述


部分核心代码

import cv2
import numpy as np
import time
from collections import defaultdict
import os

# 数字模板匹配函数
def img_match(input_img, template_dict):
    """返回 (最佳匹配数字, 最大相似度)"""
    resized_img = cv2.resize(input_img, (32, 48), interpolation=cv2.INTER_LINEAR)
    max_val = 0.0
    best_num = -1
    for i in range(10):
        template = template_dict[i]
        result = cv2.matchTemplate(resized_img, template, cv2.TM_CCOEFF_NORMED)
        _, current_max, _, _ = cv2.minMaxLoc(result)
        if current_max > max_val:
            max_val = current_max
            best_num = i
    return best_num, max_val

# 形状检测函数
def ShapeDetection(imgContour, cnt):
    perimeter = cv2.arcLength(cnt, True)
    approx = cv2.approxPolyDP(cnt, 0.02 * perimeter, True)
    corners = len(approx)
    area = cv2.contourArea(cnt)
    if area < 100:  # 调整面积过滤条件
        return None

    x, y, w, h = cv2.boundingRect(approx)

    # 三角形
    if corners == 3:
        return "Triangle", (x, y, w, h)
    # 矩形 or 正方形
    elif corners == 4:
        return "Rectangle or Square", (x, y, w, h)
    # 圆形
    elif corners >= 8:
        return "Circle", (x, y, w, h)
    return None

# 计算实际面积
def calculate_area(cnt, area_per_pixel):
    pixel_area = cv2.contourArea(cnt)
    actual_area = pixel_area * area_per_pixel
    return actual_area

# 主函数示例
def main():
    cap = cv2.VideoCapture(0)  # 摄像头编号可调整
    # A4 纸实际面积(单位 mm²) = 210mm × 297mm
    a4_area = 210 * 297

    # 假设事先确定了在图像中 A4 纸对应的像素宽高
    # 这里用 x_start, y_start, x_end, y_end 表示 A4 纸所在区域
    x_start, y_start = 210, 60
    x_end, y_end = 390, 340
    crop_width = x_end - x_start
    crop_height = y_end - y_start
    crop_area_pixels = crop_width * crop_height

    # 单位像素面积(mm²/像素)
    area_per_pixel = a4_area / crop_area_pixels

    while True:
        ret, frame = cap.read()
        if not ret:
            break
        # 只截取 A4 区域,避免不必要的干扰
        frame = frame[y_start:y_end, x_start:x_end]

        # 灰度+模糊+二值化
        imgGray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        imgBlur = cv2.GaussianBlur(imgGray, (7, 7), 1)
        _, imgThresh = cv2.threshold(imgBlur, 95, 255, cv2.THRESH_BINARY_INV)

        # 边缘检测 + 形态学操作
        edges = cv2.Canny(imgBlur, 30, 150)
        imgCanny = cv2.bitwise_or(edges, imgThresh)
        kernel = np.ones((3, 3), np.uint8)
        imgCanny = cv2.morphologyEx(imgCanny, cv2.MORPH_CLOSE, kernel)

        # 查找轮廓
        contours, _ = cv2.findContours(imgCanny, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        imgContour = frame.copy()

        for cnt in contours:
            result = ShapeDetection(imgContour, cnt)
            if result:
                shape_type, (x, y, w, h) = result
                cv2.rectangle(imgContour, (x, y), (x + w, y + h), (255, 0, 0), 2)
                cv2.putText(imgContour, shape_type, (x, y - 10),
                            cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 255), 2)

                # 计算面积并显示
                area_mm2 = calculate_area(cnt, area_per_pixel)
                print(f"{
      
      shape_type} 的实际面积: {
      
      area_mm2:.2f} mm²")

        cv2.imshow("Result", imgContour)
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break

    cap.release()
    cv2.destroyAllWindows()

if __name__ == "__main__":
    main()