Bubble detection;contour filter

import os
import tkinter as tk
from tkinter import simpledialog, messagebox, filedialog
import cv2
import numpy as np

import os
import tkinter as tk
from tkinter import simpledialog, messagebox, filedialog


def get_video_path():
    root = tk.Tk()
    root.withdraw()

    video_path = filedialog.askopenfilename(title='select video files', filetypes=[('video files', '*.mp4 *.avi')])
    if not video_path:
        print('no path input')
        return None
    return video_path


import cv2

def process_video_frames(video_path):
    paused = False
    cap = cv2.VideoCapture(video_path)

    if not cap.isOpened():
        print('Error: Unable to open video file')
        return

    frame_num = 0
    while True:
        ret, frame = cap.read()
        if not ret:
            break
        frame_num += 1

        # 转化为灰度
        gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

        # 高斯模糊
        gaussian_blur = cv2.GaussianBlur(gray_frame, (5, 5), 0)

        # CLAHE 自适应直方图均衡化
        clane = cv2.createCLAHE(clipLimit=3, tileGridSize=(9, 9))
        clane_frame = clane.apply(gray_frame)

        # 动态阈值 + Otsu 阈值
        dynamics_threshold, ostu_frame = cv2.threshold(clane_frame, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
        correct_threshold = dynamics_threshold
        thresh_num, thresh_frame = cv2.threshold(clane_frame, correct_threshold, 255, cv2.THRESH_TOZERO_INV)

        copy_frame = frame.copy()

        # 找到轮廓
        contours, _ = cv2.findContours(thresh_frame, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

        # 修改面积控制
        min_area = 100        # 最小面积,增加过滤条件
        max_area = 1000      # 最大面积,限制面积过大的噪声轮廓
        centroids = []

        for contour_index, contour in enumerate(contours):
            area_contour = cv2.contourArea(contour)

            if min_area < area_contour < max_area:
                # 获取轮廓的外接矩形 (x, y, w, h)
                x, y, w, h = cv2.boundingRect(contour)

                moments = cv2.moments(contour)
                cX = int(moments["m10"] / moments["m00"])
                cY = int(moments["m01"] / moments["m00"])
                centroids.append((cX,cY))
                cv2.circle(copy_frame, (cX,cY), 10,(0,255,0), -1)

                # 计算长宽比:取长边和短边的比值
                long_side = max(w, h)
                short_side = min(w, h)
                aspect_ratio = long_side / short_side if short_side != 0 else 0

                # 如果长宽比小于5,绘制轮廓
                if aspect_ratio < 5:
                    # 绘制符合条件的轮廓
                    cv2.drawContours(copy_frame, [contour], -1, (0, 255, 0), 2)
                    cv2.rectangle(copy_frame, (x -10, y - 10), (x + w + 10, y + h + 10), (255,255,255), 2)
                    #cv2.putText(copy_frame, f'bubble{contour_index}', (x,y-20), cv2.FONT_HERSHEY_PLAIN, 1.7, (255,255,255), 2)

        # 显示处理后的帧
        cv2.imshow('frame with contour', copy_frame)

        cv2.imshow('thresh frame', thresh_frame)

        key = cv2.waitKey(1) & 0xFF
        if key == ord('p'):
            paused = not paused

        if paused:
            while paused:
                key = cv2.waitKey(0) & 0xFF  # 等待用户按键
                if key == ord('p'):
                    paused = False
                if key == ord('q'):
                    break

    cap.release()
    cv2.destroyAllWindows()




video_path = get_video_path()
if video_path:
    process_video_frames(video_path)

how can i filter bubbles accurately.