OpenCV实例(八)行人跟踪

news2024/9/21 3:33:02

OpenCV实例(八)行人跟踪

  • 1.目标跟踪概述
  • 2.基于背景差分检测运动物体
    • 2.1 实现基本背景差分器
    • 2.2 使用MOG背景差分器
    • 2.3 使用卡尔曼滤波器寻找运动趋势
  • 3.跟踪行人

作者:Xiou

1.目标跟踪概述

目标跟踪是对摄像头视频中的移动目标进行定位的过程,它有着广泛的应用,本章将介绍这一主题。实时目标跟踪是许多计算机视觉应用的重要任务,例如监控(surveillance)、基于感知的(perceptual)用户界面、增强现实、基于对象的视频压缩以及辅助驾驶等。

可用多种方式实现目标跟踪,而最优的跟踪技术在很大程度跟具体任务有关。

为了跟踪视频中的所有目标,首先要完成的任务是识别视频帧中那些可能包含移动目标的区域。有很多实现视频目标跟踪的方法,这些方法的目的稍微不同。例如,当跟踪所有移动目标时,帧之间的差异会变得有用;当跟踪视频中移动的手时,基于皮肤颜色的均值漂移方法是最好的解决方案;当知道跟踪对象的一方面时,模板匹配会是不错的技术。

2.基于背景差分检测运动物体

2.1 实现基本背景差分器

实现基本背景差分器为了实现基本背景差分器,我们采用以下几步:
(1)开始用摄像头捕捉帧。
(2)丢弃9帧,这样摄像头才有时间适当调整自动曝光,以适应场景中的光照条件。(3)取第10帧,将其转换为灰度图像,对其进行模糊,并把模糊图像作为背景的参考图像。
(4)对于每个后续帧,对其进行模糊,将其转换为灰度图像,再计算模糊帧和背景参考图像之间的绝对差值。对差值图像进行阈值化、平滑和轮廓检测处理。绘制并显示主要轮廓的边框。

将前面的几步展开为更小的步骤,我们可以考虑用8个连续的代码块来实现脚本:(1)首先,导入OpenCV,并定义blur、erode和dilate运算的核的大小:
在这里插入图片描述
(2)试着从摄像头捕捉10帧:

在这里插入图片描述

(3)如果无法采集到10帧,就退出。否则,将第10帧图像转换为灰度图像,并对其进行模糊:
在这里插入图片描述
(4)在这一阶段,我们有了背景的参考图像。现在,我们继续采集更多帧,这样就可以检测运动物体了。对每一帧的处理都从灰度转换和高斯模糊操作开始:

在这里插入图片描述
(5)现在,我们对当前帧的模糊、灰度版本,以及背景图像的模糊、灰度版本进行比较。具体来说,我们将使用OpenCV的cv2.absdiff函数求这两幅图像之间差值的绝对值(或大小)。然后,应用阈值来获得纯黑白图像,并通过形态学运算对阈值化图像进行平滑处理。以下是相关的代码:

在这里插入图片描述
(6)现在,如果技术运行良好,阈值图像应该在运动物体处包含白色斑点。我们想找到白色斑点的轮廓,并在其周围绘制边框。为了进一步过滤可能不是真实物体的微小变化,我们将应用一个基于轮廓面积的阈值。如果轮廓太小,就认为它不是真正的运动物体。(当然,“太小”的界定可能会因摄像头的分辨率和应用程序而有所不同,在某些情况下,你可能根本不希望应用此测试。)下面是检测轮廓和绘制边框的代码:

在这里插入图片描述

(7)显示差值图像、阈值化图像以及带有矩形边框的检测结果:

在这里插入图片描述

(8)继续读取帧,直到用户按下Esc键退出:

在这里插入图片描述

代码实例:

import cv2

OPENCV_MAJOR_VERSION = int(cv2.__version__.split('.')[0])

BLUR_RADIUS = 21
erode_kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
dilate_kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (9, 9))

cap = cv2.VideoCapture(0)

# Capture several frames to allow the camera's autoexposure to adjust.
for i in range(10):
    success, frame = cap.read()
if not success:
    exit(1)

gray_background = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
gray_background = cv2.GaussianBlur(gray_background,
                                   (BLUR_RADIUS, BLUR_RADIUS), 0)

success, frame = cap.read()
while success:

    gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    gray_frame = cv2.GaussianBlur(gray_frame,
                                  (BLUR_RADIUS, BLUR_RADIUS), 0)

    diff = cv2.absdiff(gray_background, gray_frame)
    _, thresh = cv2.threshold(diff, 40, 255, cv2.THRESH_BINARY)
    cv2.erode(thresh, erode_kernel, thresh, iterations=2)
    cv2.dilate(thresh, dilate_kernel, thresh, iterations=2)

    if OPENCV_MAJOR_VERSION >= 4:
        # OpenCV 4 or a later version is being used.
        contours, hier = cv2.findContours(thresh, cv2.RETR_EXTERNAL,
                                          cv2.CHAIN_APPROX_SIMPLE)
    else:
        # OpenCV 3 or an earlier version is being used.
        # cv2.findContours has an extra return value.
        # The extra return value is the thresholded image, which is
        # unchanged, so we can ignore it.
        _, contours, hier = cv2.findContours(thresh, cv2.RETR_EXTERNAL,
                                             cv2.CHAIN_APPROX_SIMPLE)

    for c in contours:
        if cv2.contourArea(c) > 4000:
            x, y, w, h = cv2.boundingRect(c)
            cv2.rectangle(frame, (x, y), (x+w, y+h), (255, 255, 0), 2)

    cv2.imshow('diff', diff)
    cv2.imshow('thresh', thresh)
    cv2.imshow('detection', frame)

    k = cv2.waitKey(1)
    if k == 27:  # Escape
        break

    success, frame = cap.read()

输出结果:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.2 使用MOG背景差分器

(1)用MOG背景差分器替换基本背景差分模型。
(2)使用视频文件(而不是摄像头)作为输入。
(3)取消高斯模糊。
(4)调整阈值、形态学和轮廓分析步骤中使用的参数。

这些修改会影响位于整个脚本中几个不同地方的几行代码。靠近脚本的顶部,我们初始化MOG背景差分器并修改形态学核的大小,如下面代码块中的粗体所示:

在这里插入图片描述

注意OpenCV提供了一个函数cv2.createBackgroundSubtractorMOG2来创建cv2.BackgroundSubtractorMOG2实例。该函数接受一个参数detectShadows,将其设置为True,就会标记出阴影区域,而不会标记为前景的一部分。

输出结果:

在这里插入图片描述
由于抛光的地板和墙壁,这个场景不仅包含阴影,也包含反射内容。当启用阴影检测时,我们可以使用一个阈值来移除掩模上的阴影和反射部分,大厅里的人周围只留下一个准确的检测矩形。可是,当禁用阴影检测时,有两个检测结果,可以说,两个检测结果都是不准确的。其中一个覆盖了男子及其影子和地板上的倒影。第二个则覆盖了该男子在墙上的倒影。可以说,这些都是不准确的检测,因为即使这个人的影子和反射是运动物体的视觉产物,但是它们并不是真正的运动物体。

2.3 使用卡尔曼滤波器寻找运动趋势

卡尔曼滤波器主要(但不完全)是由鲁道夫·卡尔曼(Rudolf Kalman)在20世纪50年代后期开发出来的一种算法。卡尔曼滤波器在许多领域都有实际应用,尤其是从核潜艇到飞机的各种交通工具的导航系统。

卡尔曼滤波器递归地对有噪声的输入数据流进行操作,以产生底层系统状态的统计最优估计。在计算机视觉背景下,卡尔曼滤波器可以对跟踪物体的位置进行平滑估计。

我们来考虑一个简单的例子。想象桌子上有一个红色小球,并想象有一个摄像头对着此场景。将球作为要跟踪的主体,然后用手指轻弹小球。球将根据运动规律开始在桌子上滚动。如果球在特定的方向上以1米/秒的速度滚动,那么很容易估计出球在1秒后的位置:它将在1米远的地方。卡尔曼滤波器应用这样的规律,根据先前收集到的帧的跟踪结果来预测当前视频帧中物体的位置。

卡尔曼滤波器本身并没有收集这些跟踪结果,但是它会根据来自另一种算法(如MeanShift)的跟踪结果更新物体的运动模型。自然,卡尔曼滤波器无法预见作用在球上的力(例如与桌上的铅笔的碰撞),但是它可以在事后根据跟踪结果更新它的运动模型。通过使用卡尔曼滤波器,我们可以获得比单独跟踪的结果更稳定、更符合运动规律的估计结果。

根据前面的描述,我们认为卡尔曼滤波器算法有两个阶段:
·预测(在第一阶段):卡尔曼滤波器使用计算到当前时间点的协方差估计物体的新位置。
·更新(在第二阶段):卡尔曼滤波器记录物体的位置,并调整协方差用于下一个计算周期。

代码实例:

import cv2
import numpy as np

# Create a black image.
img = np.zeros((800, 800, 3), np.uint8)

# Initialize the Kalman filter.
kalman = cv2.KalmanFilter(4, 2)
kalman.measurementMatrix = np.array(
    [[1, 0, 0, 0],
     [0, 1, 0, 0]], np.float32)
kalman.transitionMatrix = np.array(
    [[1, 0, 1, 0],
     [0, 1, 0, 1],
     [0, 0, 1, 0],
     [0, 0, 0, 1]], np.float32)
kalman.processNoiseCov = np.array(
    [[1, 0, 0, 0],
     [0, 1, 0, 0],
     [0, 0, 1, 0],
     [0, 0, 0, 1]], np.float32) * 0.03

last_measurement = None
last_prediction = None

def on_mouse_moved(event, x, y, flags, param):
    global img, kalman, last_measurement, last_prediction

    measurement = np.array([[x], [y]], np.float32)
    if last_measurement is None:
        # This is the first measurement.
        # Update the Kalman filter's state to match the measurement.
        kalman.statePre = np.array(
            [[x], [y], [0], [0]], np.float32)
        kalman.statePost = np.array(
            [[x], [y], [0], [0]], np.float32)
        prediction = measurement
    else:
        kalman.correct(measurement)
        prediction = kalman.predict()  # Gets a reference, not a copy

        # Trace the path of the measurement in green.
        cv2.line(img, (last_measurement[0], last_measurement[1]),
                 (measurement[0], measurement[1]), (0, 255, 0))

        # Trace the path of the prediction in red.
        cv2.line(img, (last_prediction[0], last_prediction[1]),
                 (prediction[0], prediction[1]), (0, 0, 255))

    last_prediction = prediction.copy()
    last_measurement = measurement

cv2.namedWindow('kalman_tracker')
cv2.setMouseCallback('kalman_tracker', on_mouse_moved)

while True:
    cv2.imshow('kalman_tracker', img)
    k = cv2.waitKey(1)
    if k == 27:  # Escape
        cv2.imwrite('kalman.png', img)
        break

输出结果:

在这里插入图片描述

运行程序并移动鼠标。如果鼠标在高速下突然转弯,你会注意到预测线(红色)会比测量线(绿色)更宽。这是因为预测跟随鼠标运动到那时的动量。

3.跟踪行人

素材:

在这里插入图片描述

应用程序将遵循以下逻辑:

(1)从视频文件中捕获帧。
(2)使用前20帧来填充背景差分器的历史记录。
(3)基于背景差分,使用第21帧识别运动的前景物体。我们将把这些当作行人对待。对于每个行人,分配一个ID和一个初始跟踪窗口,然后计算直方图。
(4)对于随后的每一帧,使用卡尔曼滤波器和MeanShift跟踪每个行人。如果这是一个实际的应用程序,可能需要存储每个行人在场景中的路径记录,以便用户稍后对其进行分析。然而,这种类型的记录保存不在本示例中探讨。此外,在实际的应用程序中,需要确保能够识别进入场景的新的行人,但是目前我们将只专注于跟踪场景中靠近视频开始处的那些物体。

代码实例:

import cv2
import numpy as np

OPENCV_MAJOR_VERSION = int(cv2.__version__.split('.')[0])

class Pedestrian():
    """A tracked pedestrian with a state including an ID, tracking
    window, histogram, and Kalman filter.
    """

    def __init__(self, id, hsv_frame, track_window):

        self.id = id

        self.track_window = track_window
        self.term_crit = \
            (cv2.TERM_CRITERIA_COUNT | cv2.TERM_CRITERIA_EPS, 10, 1)

        # Initialize the histogram.
        x, y, w, h = track_window
        roi = hsv_frame[y:y+h, x:x+w]
        roi_hist = cv2.calcHist([roi], [0], None, [16], [0, 180])
        self.roi_hist = cv2.normalize(roi_hist, roi_hist, 0, 255,
                                      cv2.NORM_MINMAX)

        # Initialize the Kalman filter.
        self.kalman = cv2.KalmanFilter(4, 2)
        self.kalman.measurementMatrix = np.array(
            [[1, 0, 0, 0],
             [0, 1, 0, 0]], np.float32)
        self.kalman.transitionMatrix = np.array(
            [[1, 0, 1, 0],
             [0, 1, 0, 1],
             [0, 0, 1, 0],
             [0, 0, 0, 1]], np.float32)
        self.kalman.processNoiseCov = np.array(
            [[1, 0, 0, 0],
             [0, 1, 0, 0],
             [0, 0, 1, 0],
             [0, 0, 0, 1]], np.float32) * 0.03
        cx = x+w/2
        cy = y+h/2
        self.kalman.statePre = np.array(
            [[cx], [cy], [0], [0]], np.float32)
        self.kalman.statePost = np.array(
            [[cx], [cy], [0], [0]], np.float32)

    def update(self, frame, hsv_frame):

        back_proj = cv2.calcBackProject(
            [hsv_frame], [0], self.roi_hist, [0, 180], 1)

        ret, self.track_window = cv2.meanShift(
            back_proj, self.track_window, self.term_crit)
        x, y, w, h = self.track_window
        center = np.array([x+w/2, y+h/2], np.float32)

        prediction = self.kalman.predict()
        estimate = self.kalman.correct(center)
        center_offset = estimate[:,0][:2] - center
        self.track_window = (x + int(center_offset[0]),
                             y + int(center_offset[1]), w, h)
        x, y, w, h = self.track_window

        # Draw the predicted center position as a blue circle.
        cv2.circle(frame, (int(prediction[0]), int(prediction[1])),
                   4, (255, 0, 0), -1)

        # Draw the corrected tracking window as a cyan rectangle.
        cv2.rectangle(frame, (x,y), (x+w, y+h), (255, 255, 0), 2)

        # Draw the ID above the rectangle in blue text.
        cv2.putText(frame, 'ID: %d' % self.id, (x, y-5),
                    cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 0, 0),
                    1, cv2.LINE_AA)

def main():

    cap = cv2.VideoCapture('pedestrians.avi')

    # Create the KNN background subtractor.
    bg_subtractor = cv2.createBackgroundSubtractorKNN()
    history_length = 20
    bg_subtractor.setHistory(history_length)

    erode_kernel = cv2.getStructuringElement(
        cv2.MORPH_ELLIPSE, (3, 3))
    dilate_kernel = cv2.getStructuringElement(
        cv2.MORPH_ELLIPSE, (8, 3))

    pedestrians = []
    num_history_frames_populated = 0
    while True:
        grabbed, frame = cap.read()
        if (grabbed is False):
            break

        # Apply the KNN background subtractor.
        fg_mask = bg_subtractor.apply(frame)

        # Let the background subtractor build up a history.
        if num_history_frames_populated < history_length:
            num_history_frames_populated += 1
            continue

        # Create the thresholded image.
        _, thresh = cv2.threshold(fg_mask, 127, 255,
                                  cv2.THRESH_BINARY)
        cv2.erode(thresh, erode_kernel, thresh, iterations=2)
        cv2.dilate(thresh, dilate_kernel, thresh, iterations=2)

        # Detect contours in the thresholded image.
        if OPENCV_MAJOR_VERSION >= 4:
            # OpenCV 4 or a later version is being used.
            contours, hier = cv2.findContours(
                thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        else:
            # OpenCV 3 or an earlier version is being used.
            # cv2.findContours has an extra return value.
            # The extra return value is the thresholded image, which
            # is unchanged, so we can ignore it.
            _, contours, hier = cv2.findContours(
                thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

        hsv_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)

        # Draw green rectangles around large contours.
        # Also, if no pedestrians are being tracked yet, create some.
        should_initialize_pedestrians = len(pedestrians) == 0
        id = 0
        for c in contours:
            if cv2.contourArea(c) > 500:
                (x, y, w, h) = cv2.boundingRect(c)
                cv2.rectangle(frame, (x, y), (x+w, y+h),
                              (0, 255, 0), 1)
                if should_initialize_pedestrians:
                    pedestrians.append(
                        Pedestrian(id, hsv_frame,
                                   (x, y, w, h)))
            id += 1

        # Update the tracking of each pedestrian.
        for pedestrian in pedestrians:
            pedestrian.update(frame, hsv_frame)

        cv2.imshow('Pedestrians Tracked', frame)

        k = cv2.waitKey(110)
        if k == 27:  # Escape
            break

if __name__ == "__main__":
    main()

输出结果:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/448723.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据结构与算法基础-学习-20-查找之散列表(HASH TABLE)

目录 目录 一、基本思想 二、术语 1、散列方法 2、散列函数 3、散列表 4、冲突 5、同义词 三、如何减少哈希冲突 四、构造散列函数需考虑的情况 五、散列函数的构造方法 1、直接定址法 2、除留余数法 六、如何处理哈希冲突 1、开地址法 2、拉链法 七、散列表查…

【微服务笔记16】微服务组件之Gateway服务网关基础环境搭建、高可用网关环境搭建

这篇文章&#xff0c;主要介绍微服务组件之Gateway服务网关基础环境搭建、高可用网关环境搭建。 目录 一、Gateway服务网关 1.1、什么是Gateway 1.2、Gateway基础环境搭建 &#xff08;1&#xff09;基础环境介绍 &#xff08;2&#xff09;引入依赖 &#xff08;3&#…

快速上手Navicat~

众所周知&#xff0c; Navicat是一款轻量级的用于MySQL连接和管理的工具&#xff0c;非常好用&#xff0c;使用起来方便快捷&#xff0c;简洁。下面我会简单的讲一下其安装以及使用的方法。并且会附带相关的永久安装教程。 简介 一般我们在开发过程中是离不开数据库的&#xf…

【Unity VR开发】结合VRTK4.0:添加对象追随器

语录&#xff1a; 我已经准备好了足够挡雨的伞&#xff0c;可是却迟迟没有等到雨的到来&#xff0c;这样的尴尬只是我漫长人生中的小插曲罢了。 前言&#xff1a; 对象追随器的目的是让一个或多个游戏对象跟随场景中的另一个对象&#xff0c;而无需将游戏对象嵌套在彼此之下。 …

『pyqt5 从0基础开始项目实战』13. 打包生成exe(保姆级图文)

目录 项目源码打包exe打开闪退需要db文件夹总结 欢迎关注 『pyqt5 从0基础开始项目实战』 专栏&#xff0c;持续更新中 欢迎关注 『pyqt5 从0基础开始项目实战』 专栏&#xff0c;持续更新中 项目源码 请查阅专栏上文获取源码 ## 安装库包 python pip install pyinstaller ![…

Stable Diffusion的原理

CSDN-markdown语法之怎样使用LaTeX语法编写数学公式 参考视频&#xff1a;【diffusion】扩散模型详解&#xff01;原理代码&#xff01; 用一颗桃树为你讲清楚 知识点&#xff1a;AI绘图原理 Diffusion扩散模型 Windows深度学习环境搭建&#xff1a;Windows深度学习环境搭建 …

FFmpeg开发笔记(三)FFmpeg的可执行程序介绍

外界对于FFmpeg主要有两种使用途径&#xff0c;一种是在命令行运行FFmpeg的可执行程序&#xff0c;该方式适合没什么特殊要求的普通场景&#xff1b;另一种是通过代码调用FFmpeg的动态链接库&#xff0c;由于开发者可以在C代码中编排个性化的逻辑&#xff0c;因此该方式适合厂商…

一篇文章介绍分布式事务

1、事务的基本概念 事务 事务指的就是一个操作单元&#xff0c;在这个操作单元中的所有操作最终要保持一致的行为&#xff0c;要么所有操作都成功&#xff0c;要么所有的操作都被撤销。简单地说&#xff0c;事务提供一种“要么什么都不做&#xff0c;要么做全套”机制。 本地…

【越早知道越好】的道理——能够大大提升效率的【快捷键】

文章目录 1️⃣虚拟桌面第一步&#xff1a;打开任务视图第二步&#xff1a;创建桌面第三步&#xff1a;桌面切换第四步&#xff1a;桌面删除 2️⃣窗口切换3️⃣桌面分屏如何分屏 前言&#x1f9d1;‍&#x1f3a4;&#xff1a;作为程序员&#x1f468;‍&#x1f4bb;&#xf…

scratch足球射门练习 中国电子学会图形化编程 少儿编程 scratch编程等级考试一级真题和答案解析2023年3月

目录 scratch足球射门练习 一、题目要求 1、准备工作 2、功能实现 二、案例分析

基于Java+SpringBoot+Vue前后端分离仓库管理系统设计实现

基于JavaSpringBootVue前后端分离仓库管理系统设计实现 博主介绍&#xff1a;5年java开发经验&#xff0c;专注Java开发、定制、远程、指导等,csdn特邀作者、专注于Java技术领域 作者主页 超级帅帅吴 Java项目精品实战案例《500套》 欢迎点赞 收藏 ⭐留言 文末获取源码联系方式…

RocketMQ 5.0 时代,6 张图带你理解 Proxy!

大家好&#xff0c;我是君哥。今天来聊一聊 RocketMQ 5.0 中的 Proxy。 RocketMQ 5.0 为了更好地拥抱云原生&#xff0c;引入了无状态的 Proxy 模块&#xff0c;新的架构图如下&#xff1a; 引入 Proxy 模块后&#xff0c;Proxy 承担了协议适配、权限管理、消息管理等计算功能…

JVM垃圾回收GC 详解(java1.8)

目录 垃圾判断算法&#xff08;你是不是垃圾&#xff1f;&#xff09; 引用计数法 可达性算法 对象的引用 强引用 软引用 弱引用 虚引用 对象的自我救赎 垃圾回收算法--分代 标记清除算法 复制算法 标记整理法 垃圾处理器 垃圾判断算法&#xff08;你是不是垃圾&…

Anaconda+PyTorch环境搭建

AnacondaPyTorch环境搭建 环境Anaconda安装配置下载镜像 cuda和cudnncudacudnn pytorch参考文章 环境 win10 22hx nvidia driver 528.89 Anaconda安装 在清华镜像中选择合适的版本及对应系统 https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/ 下载好之后一路next即可…

vscode笔记

vscode怎么中止运行 控制台—输出&#xff0c;结束运行&#xff1a; 控制台右击&#xff0c;然后点击Stop Code Run或者CtrlAltM快捷键 停止正在运行的Python脚本 在VS Code的右下角&#xff0c;选择正在运行的终端&#xff0c; 点右键&#xff0c;终止终端 VS code怎么终止…

简单的小型C++项目怎么用CMAKE进行管理

项目目录&#xff1a; 根目录下共有两个文件夹&#xff0c;分别为include、src&#xff0c;有两个文件&#xff0c;分别为CMakeLists.txt和main.cpp main函数 可以看出&#xff0c;include了func.h&#xff0c;且func.h的声明在include文件夹下&#xff0c;定义在src文件夹下的…

全网唯一!Matlab世界顶尖艺术品配色包Rmetbrewer

想要绘制一幅颜色搭配合理、好看又不花哨的论文插图&#xff0c;该如何操作呢&#xff1f; 正所谓求其上者得其中&#xff0c;求其中者得其下。 那么&#xff0c;向高手借鉴思路&#xff0c;无疑是一种不落下乘的好策略。 而在色彩搭配领域&#xff0c;像莫奈、梵高这些世界…

WPS表格数据出现绿色小三角,单引号,E+的原因说明和完美解决方案,终结版。

复盘问题的原因&#xff0c;了解原因的原因&#xff0c;预测事件的结果&#xff0c;推测结果产生的结果。 问题描述&#xff1a;好好的数据&#xff0c;复制进wps&#xff0c;左上角就会出现绿色三角。怎么去掉呢&#xff1f; 迷惑问题2&#xff1a;点击之后&#xff0c;绿色三…

代码优化- 前端优化

常量折叠 基本思想&#xff1a;在编译期间计算表达式的值&#xff08;编译时静态计算&#xff09; 例如&#xff1a;a 3 5 > a 8&#xff0c;if (true && false) ... > if (false) 好处是&#xff1a;语法树的节点数量减少了&#xff0c;意味着编译器要维护…

STM32—0.96寸OLED液晶显示

本文主要介绍基于STM32F103的0.96寸的OLED液晶显示&#xff0c;详细关于0.96寸OLED液晶屏幕的介绍可参考这篇博客&#xff1a;https://blog.csdn.net/u011816009/article/details/130119426 一、简介 OLED被称为有机激光二极管&#xff0c;也被称为有机激光显示&#xff0c;O…