OPCV图像形状识别及面积计算
最近在忙着学习写微信小程序,然后学校又在举办篮球比赛,事情一多就没什么时间写东西。今天抽空把这个项目补完一下,主要是分享一下使用 OpenCV 来进行简单的形状检测和面积计算的思路。
前言
在做图像处理的过程中,我们常常需要计算图像中的目标面积。常规做法有以下两种思路:
- 像素点计数法
通过计算目标在图像中的像素数量来推算面积大小。- 缺点:目标与摄像头的距离会直接影响像素计数的结果,需要对距离或拍摄角度等因素进行校正。
- 固定距离对比法
在拍摄时固定好目标与摄像头之间的距离,然后借助一个已知尺寸的参照物(比如 A4 纸)来估算出目标的实际面积。- 缺点:依然需要保证拍摄过程中距离、角度等尽量保持一致,否则会带来误差。
这里,我采用了第二种思路:在图像中预留了一块 A4 纸区域,用它来换算像素面积与实际面积之间的比例。下面给出部分核心代码片段,演示如何在 Python 中利用 OpenCV 进行形状检测并计算面积。
代码思路简介
主要功能如下:
- 形状检测
- 通过
cv2.findContours
提取轮廓,再根据多边形逼近、角度特征等信息区分三角形、矩形、正方形和圆形。
- 通过
- 数字识别(可选)
- 先加载数字模板,再使用
cv2.matchTemplate
进行匹配,用以识别形状内部可能标注的数字。
- 先加载数字模板,再使用
- 面积计算
- 将像素面积转换为实际面积:
[
\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()