opencv复习

目录

1.core

1.图像变换

[1.1 affine仿射变换](#1.1 affine仿射变换)

[1.2 透视变换](#1.2 透视变换)

2.四元数(旋转)

[2.1 轴角转四元数](#2.1 轴角转四元数)

[2.2 旋转矩阵转四元数](#2.2 旋转矩阵转四元数)

[2.3 欧拉角转旋转矩阵](#2.3 欧拉角转旋转矩阵)

[2.4 四元数转旋转矩阵](#2.4 四元数转旋转矩阵)

[2.5 四元数用eigen用的比较多](#2.5 四元数用eigen用的比较多)

[2. imgproc. Image Processing](#2. imgproc. Image Processing)

[2.1 bilateralFilter](#2.1 bilateralFilter)

[2.2 blur均值滤波](#2.2 blur均值滤波)

[2.3 boxFilter()](#2.3 boxFilter())

[2.4 buildPyramid()](#2.4 buildPyramid())

[2.5 dilate,erode](#2.5 dilate,erode)

[2.6 GaussianBlur()](#2.6 GaussianBlur())

[2.7 medianBlur()](#2.7 medianBlur())

[2.8 拉普拉斯算子(Laplacian)](#2.8 拉普拉斯算子(Laplacian))

[2.9 morphologyDefaultBorderValue](#2.9 morphologyDefaultBorderValue)

[2.10 morphologyEx()](#2.10 morphologyEx())

[2.11 pyrMeanShiftFiltering](#2.11 pyrMeanShiftFiltering)

[2.12 floodfill](#2.12 floodfill)

[2.13 scharr](#2.13 scharr)

[2.14 stackBlur()](#2.14 stackBlur())

[2.15 adaptiveThreshold()](#2.15 adaptiveThreshold())

[2.16 threshold、THRESH_OTSU](#2.16 threshold、THRESH_OTSU)

3.opencv图像分割

3.1自适应阈值

3.2固定阈值

3.3floodfill

3.4grabcut

4.一些其余的

4.1直方图

4.2霍夫变换

4.3canny边缘检测


1.core

#include <opencv2/core/affine.hpp>

1.图像变换

1.1 affine仿射变换

该变换能保证图像的平直性和平行性,原来的直线仿射变换后还是直线,原来的平行线经过仿射变换之后还是平行线。

mat m = cv::getAffineTransforms(const point2f* src, const point* 2f dst);
mat m = cv::getRotationMatrix2D((cols / 2, rows / 2), 45, 1)

图像真正的旋转指的是:

cv::warpAffine(src, dst, m, dst_sz);
C++ void warpAffine(InputArray src, OutputArray dst, InputArray M, Size dsize, int flags=INTER_LINEAR, int borderMode=BORDER_CONSTANT, const Scalar& borderValue=Scalar())

//参数InputArray src:输入变换前图像

//参数OutputArray dst:输出变换后图像,需要初始化一个空矩阵用来保存结果,不用设定矩阵尺寸

//参数InputArray M:变换矩阵,用另一个函数getAffineTransform()计算

//参数Size dsize:设置输出图像大小

//参数int flags = INTER_LINEAR:设置插值方式,默认方式为线性插值

//参数int borderMode=BORDER_CONSTANT:边界像素模式,默认值BORDER_CONSTANT

//参数const Scalar& borderValue=Scalar(),在恒定边界情况下取的值,默认值为Scalar(),即0
仿射变换是一种二维坐标到二维坐标之间的线性变换,它保持了二维图形的平直性(直线经过变换之后依然是直线)和平行性(二维图形之间的相对位置关系保持不变,平行线依然是平行线,且直线上点的位置顺序不变)。

任意的仿射变换都能表示为乘一个矩阵(线性变换),再加上一个向量(平移)的形式。

//demo
//仿射变换---平移,旋转,缩放,翻转,错切
 
#include "stdafx.h"
#include<opencv2/opencv.hpp>
#include<iostream>
#include<math.h>
 
using namespace cv;
using namespace std;
 
int main(int argc, char* argv) {
	Mat src, dst;
	src = imread("C:/Users/59235/Desktop/image/girl5.jpg");
	if (!src.data) {
		printf("could not load image...\n");
		return -1;
	}
	namedWindow("original image", CV_WINDOW_AUTOSIZE);
	imshow("original image", src);
 
	Mat dst_warp, dst_warpRotateScale, dst_warpTransformation, dst_warpFlip;
	Point2f srcPoints[3];//原图中的三点 ,一个包含三维点(x,y)的数组,其中x、y是浮点型数
	Point2f dstPoints[3];//目标图中的三点  
 
    //第一种仿射变换的调用方式:三点法
	//三个点对的值,上面也说了,只要知道你想要变换后图的三个点的坐标,就可以实现仿射变换  
	srcPoints[0] = Point2f(0, 0);
	srcPoints[1] = Point2f(0, src.rows);
	srcPoints[2] = Point2f(src.cols, 0);
	//映射后的三个坐标值
	dstPoints[0] = Point2f(0, src.rows*0.3);
	dstPoints[1] = Point2f(src.cols*0.25, src.rows*0.75);
	dstPoints[2] = Point2f(src.cols*0.75, src.rows*0.25);
 
	Mat M1 = getAffineTransform(srcPoints, dstPoints);//由三个点对计算变换矩阵  
													  
 
	warpAffine(src, dst_warp, M1, src.size());//仿射变换  
											 
   //第二种仿射变换的调用方式:直接指定角度和比例                                          
	//旋转加缩放  
	Point2f center(src.cols / 2, src.rows / 2);//旋转中心  
	double angle = 45;//逆时针旋转45度  
	double scale = 0.5;//缩放比例  
 
	Mat M2 = getRotationMatrix2D(center, angle, scale);//计算旋转加缩放的变换矩阵  
	warpAffine(src, dst_warpRotateScale, M2, Size(src.cols, src.rows), INTER_LINEAR);//仿射变换
 
																					       //仿射变换---平移
	Point2f srcPoints1[3];
	Point2f dstPoints1[3];
 
	srcPoints1[0] = Point2i(0, 0);
	srcPoints1[1] = Point2i(0, src.rows);
	srcPoints1[2] = Point2i(src.cols, 0);
 
	dstPoints1[0] = Point2i(src.cols / 3, 0);
	dstPoints1[1] = Point2i(src.cols / 3, src.rows);
	dstPoints1[2] = Point2i(src.cols + src.cols / 3, 0);
 
	Mat M3 = getAffineTransform(srcPoints1, dstPoints1);
	warpAffine(src, dst_warpTransformation, M3, Size(src.cols + src.cols / 3, src.rows));
 
	//仿射变换---翻转、镜像
	Point2f srcPoints2[3];
	Point2f dstPoints2[3];
 
	srcPoints2[0] = Point2i(0, 0);
	srcPoints2[1] = Point2i(0, src.rows);
	srcPoints2[2] = Point2i(src.cols, 0);
 
	dstPoints2[0] = Point2i(src.cols, 0);
	dstPoints2[1] = Point2i(src.cols, src.rows);
	dstPoints2[2] = Point2i(0, 0);
 
	Mat M4 = getAffineTransform(srcPoints2, dstPoints2);
	warpAffine(src, dst_warpFlip, M4, Size(src.cols, src.rows));
	//flip(src, dst_warpFlip, 1);//  flipCode:= 0 图像向下翻转
	//> 0 图像向右翻转
	//< 0 图像同时向下向右翻转
 
	imshow("affine transformation1(三点法)", dst_warp);
	imshow("affine transfoemation2(指定比例和角度)", dst_warpRotateScale);
	imshow("affine transfoemation3(仿射变换平移)", dst_warpTransformation);
	imshow("affine transformation4(仿射变换镜像)", dst_warpFlip);
 
	waitKey(0);
	return 0;
}

1.2 透视变换

设取原图上的四个点组成矩阵points1,变换后的四个点组成的矩阵points2

mat_perspective = cv.getPerspectiveTransform(points1, points2)

image_perspective = cv.warpPerspective(image_original, mat_perspective, (image_original.shape[1], image_original.shape[0]))

cv.imshow("image_perspective", image_perspective)

平面的单应性被定义为从一个平面到另一个平面的投影映射。 二维平面上的点映射到摄像机成像画面上的映射,就是平面单应性的典型例子。

  • cv::findHomography:计算单应性矩阵
    findHomography的method参数用于选择计算单应性矩阵的算法。

cv::RANSAC:随机抽样方法( random sampling with consensus),随机选择所提供点的子集,并计算一个同源矩阵。RANSAC算法计算许多这样的随机抽样,并保留具有最大部分的抽样。该方法在实际应用中在拒绝噪声离群数据和寻找正确答案方面非常有效

cv::LMEDS:最小二乘中位数(least median of squares method),顾名思义,LMeDS背后的想法是最小化中值误差,而不是用默认方法基本上最小化的均方误差。这种方法的缺点是,只有当插入器至少构成数据点的大多数时,它才能表现良好。相比之下,RANSAC可以正常工作,并在给出几乎任何信噪比时给出令人满意的答案。

cv::RHO:RHO算法,在OpenCV3中使用,它基于一种被称为PROSAC的"加权"RANSAC修改,在许多异常值的情况下运行得更快。

在没有相机标定参数的情况下,可以利用物理平面和像平面中的四个对应点

需要一个3x3的变换矩阵。
为了得到视角变换(透视变换)矩阵,需要在输入图像上找四个点,以及在输出图像上对应的位置。这四个点中的任意三个都不能共线。
然后函数cv2.getPerspectiveTransform()创建一个3x3的矩阵,即为透视变换矩阵。
最后将矩阵传入函数cv2.warpPerspective对图像进行透视变换。
透视变换是二维到三维,再到另一个二维视平面的映射。

2.四元数(旋转)

旋转的表示转换与实现 - Hyaline-w - 博客园

#include <opencv2/calib3d.hpp>

2.1 轴角转四元数

2.2 旋转矩阵转四元数

2.3 欧拉角转旋转矩阵

2.4 四元数转旋转矩阵

2.5 四元数用eigen用的比较多

Eigen::Quaterniond q1(w, x, y, z);// 第一种方式

Eigen::Quaterniond q2(Vector4d(x, y, z, w));// 第二种方式

Eigen::Quaterniond q2(Matrix3d(R));// 第三种方式

2.5.2 左乘右乘

对于矩阵表示,在左乘时候,实际上是对坐标(或者说对偶空间上的元素)进行变换,表现为原空间基向量不变,变换本身发生了变换。

右乘是对原空间基向量进行变换,表现为坐标(对偶空间上的元素)不变,原空间基向量发生改变。

++基于固定坐标系的旋转变换左乘旋转矩阵,基于自身坐标系的旋转变换右乘旋转矩阵。++

2. imgproc. Image Processing

OpenCV: Image Filtering

RGB就是3通道,颜色表示最大为255,所以可以用CV_8UC3这个数据类型来表示;灰度图就是C1,只有一个通道;而带alph通道的PNG图像就是C4,是4通道图片。

2.1 bilateralFilter

void cv::bilateralFilter ( InputArray src,

OutputArray dst,

int d,

double sigmaColor,

double sigmaSpace,

int borderType = BORDER_DEFAULT

)

  1. d是在滤波时选取的空间距离参数,这里表示以当前像素点为中心点的直径。如果该值为非正数,则会自动从参数 sigmaSpace 计算得到。如果滤波空间较大(d>5),则速度较慢。因此,在实时应用中,推荐d=5。对于较大噪声的离线滤波,可以选择d=9。

  2. sigmaColor是滤波处理时选取的颜色差值范围,该值决定了周围哪些像素点能够参与到滤波中来。与当前像素点的像素值差值小于 sigmaColor 的像素点,能够参与到当前的滤波中。该值越大,就说明周围有越多的像素点可以参与到运算中。该值为0时,滤波失去意义;该值为255时,指定直径内的所有点都能够参与运算。

  3. sigmaSpace是坐标空间中的sigma值。它的值越大,说明有越多的点能够参与到滤波计算中来。当d>0时,无论sigmaSpace的值如何,d都指定邻域大小;否则,d与 sigmaSpace的值成比例。

  4. borderType是边界样式,该值决定了以何种方式处理边界。一般情况下,不需要考虑该值,直接采用默认值即可。

2.2 blur均值滤波

void blur( InputArray src, OutputArray dst,

Size ksize, Point anchor = Point(-1,-1),

int borderType = BORDER_DEFAULT );
1.ksize是滤波核的大小。滤波核大小是指在均值处理过程中,其邻域图像的高度和宽度。

2.anchor 是锚点,其默认值是(-1,-1),表示当前计算均值的点位于核的中心点位置。该值使用默认值即可,在特殊情况下可以指定不同的点作为锚点。

3..borderType是边界样式,该值决定了以何种方式处理边界。一般情况下不需要考虑该值的取值,直接采用默认值即可。

通常情况下,使用均值滤波函数时,对于锚点anchor和边界样式borderType,直接采用其默认值即可。因此,函数cv2.blur()的一般形式为:

2.3 boxFilter()

OpenCV还提供了方框滤波方式,与均值滤波的不同在于,方框滤波不会计算像素均 值。在均值滤波中,滤波结果的像素值是任意一个点的邻域平均值,等于各邻域像素值之 和除以邻域面积。而在方框滤波中,可以自由选择是否对均值滤波的结果进行归一化,即 可以自由选择滤波结果是邻域像素值之和的平均值,还是邻域像素值之和。

2.4 buildPyramid()

void cv::buildPyramid

(

InputArray src,

OutputArrayOfArrays dst,//目的地向量,包含 maxlevel + 1 个与 src 同类型的图像。dst[0] 将与 src 相同。dst[1] 是金字塔的下一层,它是 src 的平滑和缩小版,依此类推。

int maxlevel,//最后一层(最小的一层)金字塔的 0 基索引。必须是非负数。

int borderType = BORDER_DEFAULT

)

复制代码
pyrUp( tmp, dst, Size( tmp.cols*2, tmp.rows*2 )
复制代码
pyrDown( tmp, dst, Size( tmp.cols/2, tmp.rows/2 )

2.5 dilate,erode

膨胀就是求局部最大值的操作,从图像直观看来,就是将图像光亮部分放大,黑暗部分缩小。

OpenCV 图像处理之膨胀与腐蚀 - 知乎

//demo

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

int main()
{
    // 读取图像
    Mat image = imread("your_image.jpg", IMREAD_GRAYSCALE);

    if (image.empty())
    {
        cout << "Could not open or find the image" << endl;
        return -1;
    }

    // 定义核
    Mat kernel = getStructuringElement(MORPH_RECT, Size(5, 5));

    // 膨胀操作,有1全1
    Mat dilatedImage;
    dilate(image, dilatedImage, kernel);

    // 腐蚀操作,有0全0
    Mat erodedImage;
    erode(image, erodedImage, kernel);

    // 显示结果
    imshow("Original Image", image);
    imshow("Dilated Image", dilatedImage);
    imshow("Eroded Image", erodedImage);

    waitKey(0);

    return 0;
}

2.6 GaussianBlur()

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

int main() {
    // 读取图像
    Mat image = imread("your_image.jpg");

    if (image.empty()) {
        cout << "Could not open or find the image" << endl;
        return -1;
    }

    // 进行高斯滤波
    Mat blurredImage;
    GaussianBlur(image, blurredImage, Size(5, 5), 1.5);

    // 显示原始图像和滤波后的图像
    imshow("Original Image", image);
    imshow("Blurred Image", blurredImage);

    waitKey(0);

    return 0;
}

2.7 medianBlur()

void medianBlur(InputArray src, OutputArray dst, int ksize)//像素点邻域灰度值的中值来代替该像素点的灰度值

2.8 拉普拉斯算子(Laplacian)

通过模板可以发现,当邻域内像素灰度相同时,模板的卷积运算结果为0;当中心像素灰度高于邻域内其他像素的平均灰度时,模板的卷积运算结果为正数;当中心像素的灰度低于邻域内其他像素的平均灰度时,模板的卷积的负数。对卷积运算的结果用适当的衰弱因子处理并加在原中心像素上,就可以实现图像的锐化处理。

//demo
#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

int main() {
    // 读取图像
    Mat image = imread("your_image.jpg");

    if (image.empty()) {
        cout << "Could not open or find the image" << endl;
        return -1;
    }

    // 转换为灰度图像
    Mat grayImage;
    cvtColor(image, grayImage, COLOR_BGR2GRAY);

    // 应用拉普拉斯算子
    Mat laplacianImage;
    Laplacian(grayImage, laplacianImage, CV_16S, 3);

    // 对结果进行绝对值处理并转换为 8 位图像
    convertScaleAbs(laplacianImage, laplacianImage);

    // 显示结果
    imshow("Original Image", image);
    imshow("Laplacian Image", laplacianImage);

    waitKey(0);

    return 0;
}

2.9 morphologyDefaultBorderValue

morphologyDefaultBorderValue()

int main()
{
	Mat image, image_gray, image_bw, image_bw2, image_bw3;   //定义输入图像,灰度图像,二值图像
	image = imread("开运算闭运算.png");  //读取图像;
	if (image.empty())
	{
		cout << "读取错误" << endl;
		return -1;
	}


	//转换为灰度图像
	cvtColor(image, image_gray, COLOR_BGR2GRAY);

	//转换为二值图
	threshold(image_gray, image_bw, 120, 255, 1); //通过0,1调节二值图像背景颜色
	cv::imshow("image_bw", image_bw);

	//构造结构元
	Mat kernel = getStructuringElement(0, Size(5, 5));

	//腐蚀
	morphologyEx(image_bw, image_bw2, 0, kernel);
	cv::imshow("image_bw2", image_bw2);

	//膨胀
	morphologyEx(image_bw, image_bw3, 1, kernel);
	cv::imshow("image_bw3", image_bw3);

	cv::waitKey(0);  //暂停,保持图像显示,等待按键结束
	return 0;
}

2.10 morphologyEx()

cv2.morphologyEx()实现开运算,闭运算,礼帽与黑帽操作以及梯度运算

2.11 pyrMeanShiftFiltering

Opencv均值漂移pyrMeanShiftFiltering彩色图像分割流程剖析_opencv pyrmeanshiftfiltering-CSDN博客

2.12 floodfill

图像分割中的漫水填充(Flood Fill)算法是一种基于区域增长的像素分类方法。其原理是在图像中从种子点开始,逐渐向周围扩展,并根据一定的条件决定是否将相邻的像素归属于同一区域。

漫水填充的基本原理如下:

  • 选择种子点。
  • 以种子点为中心,判断4邻域或者8邻域的像素值与种子点像素值的差值,将差值小于阈值的像素点添加进区域内。
  • 将新加入的像素点作为新的种子点,反复执行Step2,直到没有新的像素点被添加进该区域。
void floodFill_f(Mat mat){
    // 如果是四通道图像,则要把四通道图像转换成三通道
    Mat image;
    cv::cvtColor(mat, image, cv::COLOR_BGRA2BGR);
    RNG rng(10086);//随机数,用于随机生成像素
    //设置操作标志flags
    int connectivity=4;//连接领域方式
    int  maskVal=255;//掩码图像的数值
    int  flags=connectivity|(maskVal<<8)|FLOODFILL_FIXED_RANGE;//漫水填充操作方式标志
    Rect rect;                     // 输出的填充区域
    //设置与选中像素点的差值
    Scalar loDiff=Scalar (20,20,20);
    Scalar upDiff=Scalar (20,20,20);
    //声明掩摸矩阵变量
    Mat mask=Mat::zeros(image.rows+2,image.cols+2,CV_8UC1);
    ostringstream ss;
    for(int i=0;i<20;i++){
        //随机产生图像中某一像素值
        int py=rng.uniform(0,image.rows-1);
        int px=rng.uniform(0,image.cols-1);
        Point point=Point (px,py);
        //彩色图像中填充的像素值
        Scalar newVal=Scalar (rng.uniform(0,255),rng.uniform(0,255),rng.uniform(0,255));
        //浸水填充函数
        int area= floodFill(image,mask,point,newVal,&rect,loDiff,upDiff,flags);
        //输出像素点和填充的像素数目
        ss<<"像素点x:"<<point.x<<"   y:"<<point.y<<"   填充像素数目"<<area<<endl;
    }
    LOGD("%s",ss.str().c_str());
    //输出填充的图像结果
    imwrite("/sdcard/DCIM/img.png",image);
    imwrite("/sdcard/DCIM/mask.png",mask);
}

2.13 scharr

Sobel 算子和 Scharr 算子都是用于图像处理中边缘检测的梯度算子,但它们有以下一些区别:

  1. 模板系数:Sobel 算子的模板系数相对较小,而 Scharr 算子的模板系数更大,这使得 Scharr 算子对边缘的检测更敏感,能够检测到更细微的边缘变化。

  2. 精度:Scharr 算子在计算梯度时具有更高的精度,尤其在检测水平和垂直方向的边缘时。

  3. 噪声敏感度:由于 Scharr 算子的敏感性更高,它对噪声也可能更敏感。相比之下,Sobel 算子在一定程度上对噪声更具鲁棒性。

  4. 计算复杂度:Scharr 算子由于模板系数较大,计算复杂度相对较高。

2.14 stackBlur()

高斯模糊的耗时会随着kernel size的增大而增大,对于StackBlur和BoxBlur,其并不会随着kernel size增大而耗时增加。这是为什么?

2.15 adaptiveThreshold()

2.16 threshold、THRESH_OTSU

3.opencv图像分割

3.1自适应阈值

3.2固定阈值

3.3floodfill

3.4grabcut

OpenCV提供了一种流行的图像分割算法------GrabCut算法的实现。GrabCut是一种复杂且计算量大的算法,但它通常会得到非常准确的结果。该算法特别适合提取图像中的前景对象,例如,将目标对象从一张图片剪切并粘贴到另一张图片中。

2.图像分割实战

cv::grabCut函数的使用方法非常简单,只需要输入一个图像并将其中的一些像素标记为属于背景或前景。基于这些标记,算法可以分割图像的前景/背景。

4.一些其余的

4.1直方图

直方图均衡化特别适用于背景和前景都被良好照亮的图像。但是,如果图像包含高对比度区域,则结果可能不理想,因为它会增加背景噪声的对比度并减少有用信号的对比度。

4.2霍夫变换

霍夫变换常用来提取图像中的直线和圆等几何形状。它通过一种投票算法检测具有特定形状的物体。该过程在一个参数空间中通过计算累计结果的局部最大值得到一个符合该特定形状的集合作为霍夫变换结果。

针对每个像素点,使得theta从-90度到180度,使用极坐标p = xcos(theta) + ysin(theta) 计算得到共270组(p,theta)代表着霍夫空间的270条直线。将这270组值存储到H中。

如果一组点共线,则这组点中的每个值,都会使得H(p,theta)加1。

因此找到H(p,theta)值最大的直线,就是共线的点最多的直线,H(p,theta)值次大的,是共线点次多的直线。可以根据一定的阈值,将比较明显的线全部找出来

4.3canny边缘检测

图像降噪。我们知道梯度算子可以用于增强图像,本质上是通过增强边缘轮廓来实现的,也就是说是可以检测到边缘的。但是,它们受噪声的影响都很大。那么,我们第一步就是想到要先去除噪声,因为噪声就是灰度变化很大的地方,所以容易被识别为伪边缘。

计算图像梯度,得到可能边缘。计算图像梯度能够得到图像的边缘,因为梯度是灰度变化明显的地方,而边缘也是灰度变化明显的地方。当然这一步只能得到可能的边缘。因为灰度变化的地方可能是边缘,也可能不是边缘。这一步就有了所有可能是边缘的集合。

非极大值抑制。通常灰度变化的地方都比较集中,将局部范围内的梯度方向上,灰度变化最大的保留下来,其它的不保留,这样可以剔除掉一大部分的点。将有多个像素宽的边缘变成一个单像素宽的边缘。即"胖边缘"变成"瘦边缘"。

双阈值筛选。通过非极大值抑制后,仍然有很多的可能边缘点,进一步的设置一个双阈值,即低阈值(low),高阈值(high)。灰度变化大于high的,设置为强边缘像素,低于low的,剔除。在low和high之间的设置为弱边缘。进一步判断,如果其领域内有强边缘像素,保留,如果没有,剔除。

相关推荐
迷雾漫步者1 小时前
Flutter组件————FloatingActionButton
前端·flutter·dart
向前看-2 小时前
验证码机制
前端·后端
燃先生._.3 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
高山我梦口香糖4 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
m0_748235244 小时前
前端实现获取后端返回的文件流并下载
前端·状态模式
m0_748240255 小时前
前端如何检测用户登录状态是否过期
前端
black^sugar5 小时前
纯前端实现更新检测
开发语言·前端·javascript
寻找沙漠的人5 小时前
前端知识补充—CSS
前端·css
GISer_Jing6 小时前
2025前端面试热门题目——计算机网络篇
前端·计算机网络·面试