No. 30 - Median in Stream

No. 30 - Median in Stream


Question: How to get the median from a stream of numbers at any time? The median is middle value of numbers. If the count of numbers is even, the median is defined as the average value of the two numbers in middle.

Analysis: Since numbers come from a stream, the count of numbers is dynamic, and increases over time. If a data container is defined for the numbers from a stream, new numbers will be inserted into the container when they are deserialized. Let us find an appropriate data structure for such a data container.

An array is the simplest choice. The array should be sorted, because we are going to get its median. Even though it only costs O(lg n) time to find the position to be inserted with binary search algorithm, it costs O( n) time to insert a number into a sorted array, because O( n) numbers will be moved if there are  n numbers in the array. It is very efficient to get the median, since it only takes O(1) time to access to a number in an array with an index.

A sorted list is another choice. It takes O( n) time to find the appropriate position to insert a new number. Additionally, the time to get the median can be optimized to O(1) if we define two pointers which points to the central one or two elements.

A better choice available is a binary search tree, because it only costs O(lg n) on average to insert a new node. However, the time complexity is O( n) for the worst cases, when numbers are inserted in sorted (increasingly or decreasingly) order. To get the median number from a binary search tree, auxiliary data to record the number of nodes of its sub-tree is necessary for each node. It also requires O(lg n) time to get the median node on overage, but O( n) time for the worst cases.

We may utilize a balanced binary search tree, AVL, to avoid the worst cases. Usually the balance factor of a node in AVL trees is the height difference between its right sub-tree and left sub-tree. We may modify a little bit here: Define the balance factor as the difference of number of nodes between its right sub-tree and left sub-tree. It costs O(lg n) time to insert a new node into an AVL, and O(1) time to get the median for all cases.

An AVL is efficient, but it is not implemented unfortunately in libraries of the most common programming languages. It is also very difficult for candidates to implement the left/right rotation of AVL trees in dozens of minutes during interview. Let us looks for better solutions.

As shown in Figure 1, if all numbers are sorted, the numbers which are related to the median are indexed by P1 and P2. If the count of numbers is odd, P1 and P2 point to the same central number. If the count is even, P1 and P2 point to two numbers in middle.

Median can be get or calculated with the numbers pointed by P1 are P2. It is noticeable that all numbers are divided into two parts. The numbers in the first half are less than the numbers in the second half. Moreover, the number indexed by P1 is the greatest number in the first half, and the number indexed by P2 is the least one in the second half.

If numbers are divided into two parts, and all numbers in the first half is less than the numbers in the second half, we can get the median with the greatest number of the first part and the least number of the second part. How to get the greatest number efficiently? Utilizing a max heap. It is also efficient to get the least number with a min heap.

Therefore, numbers in the first half are inserted into a max heap, and numbers in the second half are inserted into a min heap. It costs O(lg n) time to insert a number into a heap. Since the median can be get or calculated with the root of a min heap and a max heap, it only takes O(1) time.

Table 1 compares the solutions above with a sorted array, a sorted list, a binary search tree, an AVL tree, as well as a min heap and a max heap.
Type for Data Container
Time to Insert
Time to Get Median
Sorted Array
O(n)
O(1)
Sorted List
O(n)
O(1)
Binary Search Tree
O(lgn) on average, O(n) for the worst cases
O(lgn) on average, O(n) for the worst cases
AVL
O(lgn)
O(1)
Max Heap and Min Heap
O(lgn)
O(1)
Table 1: Summary of solutions with a sorted array, a sorted list, a binary search tree, an AVL tree, as well as a min heap and a max heap.

Let us consider the implementation details. All numbers should be evenly divided into two parts, so the count of number in min heap and max heap should diff 1 at most. To achieve such a division, a new number is inserted into the min heap if the count of existing numbers is even; otherwise it is inserted into the max heap.

We also should make sure that the numbers in the max heap are less than the numbers in the min heap. Supposing the count of existing numbers is even, a new number will be inserted into the min heap. If the new number is less than some numbers in the max heap, it violates our rule that all numbers in the min heap should be greater than numbers in the min heap.

In such a case, we can insert the new number into the max heap first, and then pop the greatest number from the max heap, and push it into the min heap. Since the number pushed into the min heap is the former greatest number in the max heap, all numbers in the min heap are greater than numbers in the max heap with the newly inserted number.

The situation is similar when the count of existing numbers is odd and the new number to be inserted is greater than some numbers in the min heap. Please analyze the insertion process carefully by yourself.

The following is sample code in C++. Even though there are no types for heaps in STL, we can build heaps with vectors utilizing function push_heap and pop_heap. Comparing functor less and greaterare employed for max heaps and min heaps correspondingly.

template< typename T>  class DynamicArray
{
public:
     void Insert(T num)
    {
         if(((minHeap.size() + maxHeap.size()) & 1) == 0)
        {
             if(maxHeap.size() > 0 && num < maxHeap[0])
            {
                maxHeap.push_back(num);
                push_heap(maxHeap.begin(), maxHeap.end(), less<T>());

                num = maxHeap[0];

                pop_heap(maxHeap.begin(), maxHeap.end(), less<T>());
                maxHeap.pop_back();
            }

            minHeap.push_back(num);
            push_heap(minHeap.begin(), minHeap.end(), greater<T>());
        }
         else
        {
             if(minHeap.size() > 0 && minHeap[0] < num)
            {
                minHeap.push_back(num);
                push_heap(minHeap.begin(), minHeap.end(), greater<T>());

                num = minHeap[0];

                pop_heap(minHeap.begin(), minHeap.end(), greater<T>());
                minHeap.pop_back();
            }

            maxHeap.push_back(num);
            push_heap(maxHeap.begin(), maxHeap.end(), less<T>());
        }
    }

     int GetMedian()
    {
         int size = minHeap.size() + maxHeap.size();
         if(size == 0)
             throw exception( "No numbers are available");

        T median = 0;
         if(size & 1 == 1)
            median = minHeap[0];
         else
            median = (minHeap[0] + maxHeap[0]) / 2;

         return median;
    }

private:
    vector<T> minHeap;
    vector<T> maxHeap;
};

In the code above, function Insert is used to insert a new number deserialized from a stream, andGetMedian is used to get the median of the existing numbers dynamically.

The author Harry He owns all the rights of this post. If you are going to use part of or the whole of this ariticle in your blog or webpages,  please add a reference to  http://codercareer.blogspot.com/. If you are going to use it in your books, please contact him via zhedahht@gmail.com . Thanks.

python3 glasswindow.py 玻璃窗检测系统启动 相机初始化失败: &#39;pyrealsense2.video_stream_profile&#39; object has no attribute &#39;get_device&#39; 程序执行完成 请根据上述问题修改程序代码,提供一份完整的可运行的程序代码 import cv2 import numpy as np import matplotlib.pyplot as plt import pyrealsense2 as rs import os import sys from sklearn.cluster import DBSCAN from sklearn.linear_model import RANSACRegressor from collections import defaultdict class RealSenseCamera: """RealSense D455相机控制类""" def __init__(self, enable_ir_filter=True): """ 初始化RealSense相机 参数: enable_ir_filter: 是否启用红外滤光片功能 """ self.pipeline = rs.pipeline() self.config = rs.config() self.align = rs.align(rs.stream.color) self.enable_ir_filter = enable_ir_filter # 配置深度和彩色流 self.config.enable_stream(rs.stream.depth, 640, 480, rs.format.z16, 30) self.config.enable_stream(rs.stream.color, 640, 480, rs.format.bgr8, 30) def start(self): """启动相机流""" profile = self.pipeline.start(self.config) # 获取深度传感器并配置红外滤光片 if self.enable_ir_filter: depth_sensor = profile.get_device().first_depth_sensor() depth_sensor.set_option(rs.option.emitter_enabled, 0) # 关闭红外发射器 depth_sensor.set_option(rs.option.laser_power, 0) # 关闭激光器 # 设置自动曝光 color_sensor = profile.get_device().query_sensors()[1] color_sensor.set_option(rs.option.enable_auto_exposure, True) # 获取深度比例 depth_profile = rs.video_stream_profile(profile.get_stream(rs.stream.depth)) self.depth_scale = depth_profile.get_device().first_depth_sensor().get_depth_scale() def capture_frame(self): """捕获一帧RGB和深度图像""" frames = self.pipeline.wait_for_frames() aligned_frames = self.align.process(frames) depth_frame = aligned_frames.get_depth_frame() color_frame = aligned_frames.get_color_frame() if not depth_frame or not color_frame: return None, None # 转换为numpy数组 depth_image = np.asanyarray(depth_frame.get_data()) color_image = np.asanyarray(color_frame.get_data()) return color_image, depth_image def stop(self): """停止相机流""" self.pipeline.stop() def cluster_line_segments(lines, eps=20, min_samples=2): """ 对线段进行聚类处理 参数: lines: 检测到的线段列表 [(x1,y1,x2,y2), ...] eps: 聚类半径 min_samples: 最小聚类样本数 返回: clusters: 聚类后的线段列表 [[line1, line2, ...], ...] """ if lines is None or len(lines) == 0: return [] # 计算线段的中点和角度 segments = [] for line in lines: x1, y1, x2, y2 = line mid_x, mid_y = (x1+x2)/2, (y1+y2)/2 angle = np.arctan2(y2-y1, x2-x1) * 180 / np.pi segments.append([mid_x, mid_y, angle]) segments = np.array(segments) # 使用DBSCAN聚类算法 db = DBSCAN(eps=eps, min_samples=min_samples).fit(segments) labels = db.labels_ # 按聚类分组 clusters = defaultdict(list) for i, label in enumerate(labels): if label >= 0: # 忽略噪声点 clusters[label].append(lines[i]) return list(clusters.values()) def extend_lines(lines, img_shape): """ 延长线段以形成完整框架 参数: lines: 同一簇的线段列表 img_shape: 图像尺寸 (h, w) 返回: extended_lines: 延长后的线段列表 """ if not lines: return [] # 收集所有端点 all_points = [] for line in lines: x1, y1, x2, y2 = line all_points.extend([(x1, y1), (x2, y2)]) # 使用RANSAC拟合直线 points = np.array(all_points) ransac = RANSACRegressor() # 水平线处理 horizontal_lines = [] for line in lines: x1, y1, x2, y2 = line if abs(y1 - y2) < 10: # 水平线 horizontal_lines.append(line) # 垂直线处理 vertical_lines = [] for line in lines: x1, y1, x2, y2 = line if abs(x1 - x2) < 10: # 垂直线 vertical_lines.append(line) # 延长水平线 extended_lines = [] for cluster in [horizontal_lines, vertical_lines]: if not cluster: continue # 收集端点 points = [] for line in cluster: x1, y1, x2, y2 = line points.extend([(x1, y1), (x2, y2)]) if len(points) < 2: continue points = np.array(points) if cluster is horizontal_lines: # 水平线:固定y值,延长x到图像边界 y_avg = np.mean(points[:, 1]) extended_lines.append((0, int(y_avg), img_shape[1], int(y_avg))) else: # 垂直线:固定x值,延长y到图像边界 x_avg = np.mean(points[:, 0]) extended_lines.append((int(x_avg), 0, int(x_avg), img_shape[0])) return extended_lines def detect_glass_and_frame(rgb_img, depth_map): """ 改进的玻璃及窗框检测函数 参数: rgb_img: RGB彩色图像 depth_map: 深度图 返回: glass_mask: 玻璃区域掩膜 frame_lines: 检测到的窗框线段 result_img: 可视化结果图像 """ # ===== 1. 深度图预处理 ===== # 创建玻璃候选区域掩膜(深度值为0的区域) glass_mask = np.where(depth_map == 0, 255, 0).astype(np.uint8) # 形态学操作优化掩膜 kernel = np.ones((5, 5), np.uint8) glass_mask = cv2.morphologyEx(glass_mask, cv2.MORPH_CLOSE, kernel) glass_mask = cv2.dilate(glass_mask, kernel, iterations=2) glass_mask = cv2.medianBlur(glass_mask, 5) # ===== 2. 玻璃区域分割 ===== contours, _ = cv2.findContours(glass_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) min_area = 1000 glass_roi = np.zeros_like(glass_mask) for cnt in contours: area = cv2.contourArea(cnt) if area > min_area: cv2.drawContours(glass_roi, [cnt], -1, 255, -1) # ===== 3. 窗框检测 ===== # RGB图像预处理 gray = cv2.cvtColor(rgb_img, cv2.COLOR_BGR2GRAY) clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8, 8)) enhanced = clahe.apply(gray) # 边缘检测 edges = cv2.Canny(enhanced, 50, 150) # 霍夫变换检测直线 lines = cv2.HoughLinesP(edges, rho=1, theta=np.pi/180, threshold=50, minLineLength=100, maxLineGap=10) # 几何约束筛选窗框线 frame_lines = [] if lines is not None: for line in lines: x1, y1, x2, y2 = line[0] # 筛选水平/垂直线(角度容差±10度) if abs(x1 - x2) < 10 or abs(y1 - y2) < 10: frame_lines.append(line[0]) # ===== 4. 线段聚类和延长 ===== clustered_lines = cluster_line_segments(frame_lines) extended_lines = [] for cluster in clustered_lines: extended = extend_lines(cluster, rgb_img.shape[:2]) extended_lines.extend(extended) # ===== 5. 结果可视化 ===== result_img = rgb_img.copy() # 绘制玻璃区域(半透明红色) glass_color = np.zeros_like(rgb_img) glass_color[glass_roi == 255] = [0, 0, 255] # 红色 cv2.addWeighted(glass_color, 0.3, result_img, 1, 0, result_img) # 绘制原始线段(黄色) for line in frame_lines: x1, y1, x2, y2 = line cv2.line(result_img, (x1, y1), (x2, y2), (0, 255, 255), 2) # 黄色 # 绘制延长后的线段(绿色) for line in extended_lines: x1, y1, x2, y2 = line cv2.line(result_img, (x1, y1), (x2, y2), (0, 255, 0), 3) # 绿色 return glass_roi, extended_lines, result_img def main(): """主函数""" print("玻璃窗检测系统启动") # 初始化RealSense相机 try: camera = RealSenseCamera(enable_ir_filter=True) camera.start() print("RealSense D455相机已连接") except Exception as e: print(f"相机初始化失败: {str(e)}") return # 创建结果保存目录 output_dir = "detection_results" os.makedirs(output_dir, exist_ok=True) frame_count = 0 plt.ion() # 启用交互模式 try: while True: # 捕获帧 color_img, depth_img = camera.capture_frame() if color_img is None or depth_img is None: continue # 执行检测 glass_mask, frame_lines, result_img = detect_glass_and_frame(color_img, depth_img) # 显示结果 plt.figure(figsize=(15, 10)) plt.subplot(221) plt.title("RGB Image") plt.imshow(cv2.cvtColor(color_img, cv2.COLOR_BGR2RGB)) plt.subplot(222) plt.title("Depth Map") plt.imshow(depth_img, cmap=&#39;jet&#39;) plt.colorbar() plt.subplot(223) plt.title("Glass Detection") plt.imshow(glass_mask, cmap=&#39;gray&#39;) plt.subplot(224) plt.title("Detection Result") plt.imshow(cv2.cvtColor(result_img, cv2.COLOR_BGR2RGB)) plt.tight_layout() plt.draw() plt.pause(0.001) # 保存结果 if frame_count % 10 == 0: cv2.imwrite(os.path.join(output_dir, f"frame_{frame_count:04d}_rgb.png"), color_img) cv2.imwrite(os.path.join(output_dir, f"frame_{frame_count:04d}_depth.png"), depth_img) cv2.imwrite(os.path.join(output_dir, f"frame_{frame_count:04d}_result.png"), result_img) print(f"已保存帧 {frame_count}") frame_count += 1 # 检测退出键 if cv2.waitKey(1) & 0xFF == ord(&#39;q&#39;): break except KeyboardInterrupt: print("用户中断") finally: camera.stop() plt.ioff() plt.close() print("相机已关闭") if __name__ == "__main__": main() print("程序执行完成")
07-31
jerrt@jerry:~/ORB-SLAM3-STEREO-FIXED/Examples$ ./Monocular/mono_euroc ../Vocabulary/ORBvoc.txt ./Monocular/EuRoC.yaml /home/jerry/dataset/MH01 ./Monocular/EuRoC_TimeStamps/MH01.txt num_seq = 1 Loading images for sequence 0...LOADED! ------- ORB-SLAM3 Copyright (C) 2017-2020 Carlos Campos, Richard Elvira, Juan J. Gómez, José M.M. Montiel and Juan D. Tardós, University of Zaragoza. ORB-SLAM2 Copyright (C) 2014-2016 Raúl Mur-Artal, José M.M. Montiel and Juan D. Tardós, University of Zaragoza. This program comes with ABSOLUTELY NO WARRANTY; This is free software, and you are welcome to redistribute it under certain conditions. See LICENSE.txt. Input sensor was set to: Monocular Loading settings from ./Monocular/EuRoC.yaml Camera1.k3 optional parameter does not exist... -Loaded camera 1 -Loaded image info -Loaded ORB settings Viewer.imageViewScale optional parameter does not exist... -Loaded viewer settings System.LoadAtlasFromFile optional parameter does not exist... System.SaveAtlasToFile optional parameter does not exist... -Loaded Atlas settings System.thFarPoints optional parameter does not exist... -Loaded misc parameters ---------------------------------- SLAM settings: -Camera 1 parameters (Pinhole): [ 458.65399169921875 457.29598999023438 367.21499633789062 248.375 ] -Camera 1 distortion parameters: [ -0.28340810537338257 0.073959067463874817 0.00019359000725671649 1.7618711353861727e-05 ] -Original image size: [ 752 , 480 ] -Current image size: [ 600 , 350 ] -Camera 1 parameters after resize: [ 365.94735717773438 333.44500732421875 292.99069213867188 181.10678100585938 ] -Sequence FPS: 20 -Features per image: 1000 -ORB scale factor: 1.2000000476837158 -ORB number of scales: 8 -Initial FAST threshold: 20 -Min FAST threshold: 7 Loading ORB Vocabulary. This could take a while... Vocabulary loaded! Initialization of Atlas from scratch Creation of new map with id: 0 Creation of new map with last KF id: 0 Seq. Name: There are 1 cameras in
08-10
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值