9.1 图片的分割处理(c++)

news2025/2/23 18:13:20

        本文的图片处理分为图片分割、图像的亚像素坐标处理。亚像素处理的原理可以看论文一种基于多项式插值改进的亚像素细分算法,该论文的详解及c++的代码实现可以看博文基于多项式插值的亚像素边缘定位算法_基于多项式插值的亚像素算法-CSDN博客。下面的内容很多来自以上博文的内容,我只是对部分代码做了稍微的修改。

一. 背景

        在测量或者定位的应用中会涉及到边缘检测, 但是像 OpenCV 这样的开源库中边缘检测算法的精度在像素级别, 比如 Canny, Sobel blablabla. 要想提高精度就需要用到 亚像素 的技术, 在不改变硬件成本的前提下提高检测精度。

二、代码实现(龟速版)

1.梯度幅值

梯度检测包含幅值和方向,用8个方向模板在检测图像上进行卷积运算(模板滑过的地方的像素值和模板中对应的值相乘, 最后全部加总), 得到 8 张梯度图像, 方向模板如下. 模板的方向就是和 X 轴的夹角。

以下代码生成 8 个方向模板

#include<opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>
#include<list>
#include<vector>
#include <typeinfo>


using namespace cv;
using namespace std;


#define KERNEL_SUM 8

Mat kernels[KERNEL_SUM];

int k = 0;
kernels[k++] = (Mat_<float>(3, 3) <<  1,  2,  1,  0,  0,  0, -1, -2, -1);	// 270°
kernels[k++] = (Mat_<float>(3, 3) <<  2,  1,  0,  1,  0, -1,  0, -1, -2);	// 315°
kernels[k++] = (Mat_<float>(3, 3) <<  1,  0, -1,  2,  0, -2,  1,  0, -1);	// 0°
kernels[k++] = (Mat_<float>(3, 3) <<  0, -1, -2,  1,  0, -1,  2,  1,  0);	// 45°

flip(kernels[0], kernels[k++], 0);											// 90°

kernels[k++] = (Mat_<float>(3, 3) << -2, -1,  0, -1,  0,  1,  0,  1,  2);	// 135°

flip(kernels[2], kernels[k++], 1);											// 180°

kernels[k++] = (Mat_<float>(3, 3) <<  0,  1,  2, -1,  0,  1, -2, -1,  0);	// 225°

        接下来利用模板卷积生成梯度图像, 假设要检测的图像长下面这个样, 边缘是模糊的

        利用 filter2D 函数计算梯度

// 梯度图像
Mat gradients[KERNEL_SUM];
// 检测图像, 路径自己更改, 注意要是单通道图像
Mat imgsrc = imread("E:\\vs\\daima\\opencvs\\OpencvPractice\\1.jpg");//IMREAD_ANYCOLOR

for (int k = 0; k < KERNEL_SUM; k++)
{
    filter2D(imgsrc, gradients[k], CV_16S, kernels[k]);// CV_64F    //

    Mat imgshowt;
    normalize(gradients[k], imgshowt, 0, 255, NORM_MINMAX);
    cv::Mat rlt;
    imgshowt.convertTo(rlt, CV_8UC1);//将imgshowt转换为无符号单通道的整型并赋值给rlt  CV_8UC1
    cvtColor(rlt, gradients[k], cv::COLOR_BGR2GRAY);//将rlt转换为灰度图并赋值给gradients[k]
    
    imshow("img", rlt);
    waitKey(0);
}

        注意,这里和前面博文的代码稍有不同,因为原图像是3通道的图像(我在前面博文截取了个圆的图片用来处理),在以上代码中用filter2D函数后将处理的图片修改为了单通道图片,并且显示了8个卷积核处理的不同图片,结果如下(截取了前博文的图片,但效果相同):

        从图中可以看出各模板可以检测出指定方向的边缘, 其中白色表示该方向最大梯度值, 黑色表示反向最大梯度值。

梯度幅值最大值处的点就是边缘的整数坐标。

2. 梯度方向

        梯度方向指向梯度幅值最大的方向, 现在已经有 8 张幅值图像, 只要比较其中最大的幅值即可得到其方向, 例如第 0 张梯度图像在 (i, j) 处的幅值比其他图像都大, 则 (i, j) 点的梯度方向是 270°. 下面我们用代码求出最大幅度值和方向。

//2. 梯度方向
  // (1. 角度列表
const short angle_list[] = { 270, 315, 0, 45, 90, 135, 180, 225 };
// (2. 总幅值矩阵
Mat amplitude(imgsrc.rows, imgsrc.cols, CV_8UC1, Scalar::all(0));

// (3. 角度矩阵, 后面初始化成 -64 只是为了归一化之后能显示角度 0
Mat angle(imgsrc.rows, imgsrc.cols, CV_16SC1, Scalar::all(-64));//CV_16SC1   -64

for (int r = 0; r < imgsrc.rows; r++)
{

    short* pAng = angle.ptr<short>(r);

    for (int c = 0; c < imgsrc.cols; c++)
    {
        // 找出最大值
        for (int i = 0; i < KERNEL_SUM; i++)
        {
            if (amplitude.ptr<unsigned char>(r)[c] < gradients[i].ptr<unsigned char>(r)[c])
            {
                amplitude.ptr<unsigned char>(r)[c] = gradients[i].ptr<unsigned char>(r)[c];
                pAng[c] = angle_list[i];
            }
        }
        
    }
}


Mat imgshow;

imshow("amplitude", amplitude);
imwrite("D:\Datas\\1.jpg",amplitude);
waitKey(0);

normalize(angle, imgshow, 0, 255, NORM_MINMAX);
imgshow.convertTo(imgshow, CV_8UC1);
imshow("amplitude", imgshow);
waitKey(0);

        完成之后显示幅值和角度图像如下:

 

3. 单像素边缘

        以上的方式处理的图像边缘是比较粗的,那么我们如何将边缘的粗细提取成单像素呢,就是找最亮的像素点作为边缘。这个也比较简单, 只要在 3 × 3 的邻域中根据梯度的方向比较中心点 “左右” 的两个点幅值就可以知道了. 左右我打了引号, 有可能是在对角方向和上下方向. 如果不能理解的话, 把幅值图放大如下, 仿佛看到了马赛克

        我们发现在梯度方向幅值从小到大, 再变小, 我们只需要判断中间是否大于两边(“左右”)的幅值,如果同时大于两边,则是边缘点, 如果不是同时大于两边, 则不是边缘点, 下面用代码实现此判断。

 // 3.单像素边缘,整数坐标边缘图像
 //cout << "===============> start 单像素边缘 <================" << endl;
 Mat edge(imgsrc.rows, imgsrc.cols, CV_8UC1, Scalar::all(0));

 for (int r = 1; r < imgsrc.rows - 1; r++)
 {
     // 3 * 3 邻域, 所以用 3 个指针, 一个指针指一行
     const unsigned char* pAmp1 = amplitude.ptr<unsigned char>(r - 1);
     const unsigned char* pAmp2 = amplitude.ptr<unsigned char>(r);
     const unsigned char* pAmp3 = amplitude.ptr<unsigned char>(r + 1);

     const short* pAng = angle.ptr<short>(r);
     unsigned char* pEdge = edge.ptr<unsigned char>(r);

     for (int c = 1; c < imgsrc.cols - 1; c++)
     {
         switch (pAng[c])
         {
         case 270:
             if (pAmp2[c] > pAmp1[c] && pAmp2[c] >= pAmp3[c])
             {
                 pEdge[c] = 255;
             }
             break;
         case 90:
             if (pAmp2[c] >= pAmp1[c] && pAmp2[c] > pAmp3[c])
             {
                 pEdge[c] = 255;
             }
             break;

         case 315:
             if (pAmp2[c] > pAmp1[c - 1] && pAmp2[c] >= pAmp3[c + 1])
             {
                 pEdge[c] = 255;
             }
             break;
         case 135:
             if (pAmp2[c] >= pAmp1[c - 1] && pAmp2[c] > pAmp3[c + 1])
             {
                 pEdge[c] = 255;
             }
             break;

         case 0:
             if (pAmp2[c] > pAmp2[c - 1] && pAmp2[c] >= pAmp2[c + 1])
             {
                 pEdge[c] = 255;
             }
             break;
         case 180:
             if (pAmp2[c] >= pAmp2[c - 1] && pAmp2[c] > pAmp2[c + 1])
             {
                 pEdge[c] = 255;
             }
             break;

         case 45:
             if (pAmp2[c] >= pAmp1[c + 1] && pAmp2[c] > pAmp3[c - 1])
             {
                 pEdge[c] = 255;
             }
             break;
         case 225:
             if (pAmp2[c] > pAmp1[c + 1] && pAmp2[c] >= pAmp3[c - 1])
             {
                 pEdge[c] = 255;
             }
             break;

         default:
             break;
         }
     }
 }
 imshow("edg", edge);//总共有462个点为255(白色)
 waitKey(0);

 //cout << "===============> end 单像素边缘 <================" << endl;

        该代码得到的图片显示结果如下:

        现在的边缘是不是只有一个像素宽了, 到这里可以算是完成了 50% 工作了, 还有两个问题需要解决, 一是如何求出亚像素坐标, 二是怎样表示亚像素坐标

4. 亚像素坐标

        根据以下公式可计算亚像素坐标, 其实这个公式是二次多项式拟合(剪切前面博文的内容)

        下面代码中, sin 前面的负号非常关键, 因为图像的 y 方向和直角坐标系的 y 方向相反

// 4. 亚像素坐标
cout << "===============> start 亚像素坐标 <================" << endl;

// 根号2
const double root2 = sqrt(2.0);
// 三角函数表
double tri_list[2][KERNEL_SUM] = { 0 };

for (int i = 0; i < KERNEL_SUM; i++)
{
    tri_list[0][i] = cos(angle_list[i] * CV_PI / 180.0);
    // sin前面的负号非常关键, 因为图像的y方向和直角坐标系的y方向相反
    tri_list[1][i] = -sin(angle_list[i] * CV_PI / 180.0);
    
}

// vector 方式记录小数坐标
vector<Point3f> vPts;
// Mat 方式记录小数坐标, 注意这里是双通道
Mat coordinate(imgsrc.rows, imgsrc.cols, CV_32FC2, Scalar::all(0));

for (int r = 1; r < imgsrc.rows - 1; r++)
{
    // 3 * 3 邻域, 所以用3个指针, 一个指针指一行
    const short* pAmp1 = amplitude.ptr<short>(r - 1);
    const short* pAmp2 = amplitude.ptr<short>(r);
    const short* pAmp3 = amplitude.ptr<short>(r + 1);

    const short* pAng = angle.ptr<short>(r);
    const short* pEdge = edge.ptr<short>(r);

    float* pCoordinate = coordinate.ptr<float>(r);

    for (int c = 1; c < imgsrc.cols - 1; c++)
    {
        if (pEdge[c])
        {
            int nAngTmp = 0;
            double dTmp = 0;

            switch (pAng[c])
            {
            case 270:
                nAngTmp = 0;
                dTmp = ((double)pAmp1[c] - pAmp3[c]) / (pAmp1[c] + pAmp3[c] - 2 * pAmp2[c] + 0.000001) * 0.5;
                break;

            case 90:
                nAngTmp = 4;
                dTmp = -((double)pAmp1[c] - pAmp3[c]) / (pAmp1[c] + pAmp3[c] - 2 * pAmp2[c] + 0.000001) * 0.5;
                break;

            case 315:
                nAngTmp = 1;
                dTmp = ((double)pAmp1[c - 1] - pAmp3[c + 1]) / (pAmp1[c - 1] + pAmp3[c + 1] - 2 * pAmp2[c] + 0.000001) * root2 * 0.5;
                break;

            case 135:
                nAngTmp = 5;
                dTmp = -((double)pAmp1[c - 1] - pAmp3[c + 1]) / (pAmp1[c - 1] + pAmp3[c + 1] - 2 * pAmp2[c] + 0.000001) * root2 * 0.5;
                break;

            case 0:
                nAngTmp = 2;
                dTmp = ((double)pAmp2[c - 1] - pAmp2[c + 1]) / (pAmp2[c - 1] + pAmp2[c + 1] - 2 * pAmp2[c] + 0.000001) * 0.5;
                break;

            case 180:
                nAngTmp = 6;
                dTmp = -((double)pAmp2[c - 1] - pAmp2[c + 1]) / (pAmp2[c - 1] + pAmp2[c + 1] - 2 * pAmp2[c] + 0.000001) * 0.5;
                break;

            case 45:
                nAngTmp = 3;
                dTmp = ((double)pAmp3[c - 1] - pAmp1[c + 1]) / (pAmp1[c + 1] + pAmp3[c - 1] - 2 * pAmp2[c] + 0.000001) * root2 * 0.5;
                break;

            case 225:
                nAngTmp = 7;
                dTmp = -((double)pAmp3[c - 1] - pAmp1[c + 1]) / (pAmp1[c + 1] + pAmp3[c - 1] - 2 * pAmp2[c] + 0.000001) * root2 * 0.5;
                break;

            default:
                break;
            }

            const double x = c + dTmp * tri_list[0][nAngTmp];
            const double y = r + dTmp * tri_list[1][nAngTmp];
            const short z = angle_list[nAngTmp];
            //cout << dTmp<< "    "<< tri_list[0][nAngTmp]<<"   "<< dTmp * tri_list[0][nAngTmp] << endl;

            // vector方式
            vPts.push_back(Point3f((float)x, (float)y, (short)z));

            // Mat 方式
            pCoordinate[c << 1] = (float)x;
            pCoordinate[(c << 1) + 1] = (float)y;
        }
    }
}

//cout << "" << vPts.size() << endl;//总共有462个点为255(白色)
//for (size_t i = 0; i < vPts.size(); i++)
//{
//    cout << vPts[i].z << ":    " << vPts[i].x << ",    " << vPts[i].y <<endl;
//}

cout << "===============> end 亚像素坐标 <================" << endl;
return 0;

        注意,以上代码大家要了解为什么不同角度下加减不同,了解这个很重要。

三、龟速测试

至此, 龟速版本的代码已经完成了, 找一张其他图像试试? 找 lena 来试试。

        看到边缘图像有的同学可能要伤心了, 女神怎么变成这样了, 那么多边缘被检测出来了, 我们不需要那么多边缘啊. 同学别急, 检测出来那么多边缘是因为我们没有对梯度幅度进行筛选, 你想一下, 我们在计算单像素边缘的时候只要满足中间大于两边就算边缘, 女神图像中有一些中间只比两边大了一点点, 所以这种边缘可以去除,  我们想要的是比较强烈的边缘,解决办法就是设定一个阈值, 当梯度值大于阈值是才算真正的边缘。

将单像素边缘检测修改如下:

// 3.单像素边缘,整数坐标边缘图像
//cout << "===============> start 单像素边缘 <================" << endl;

// 阈值
double thres = 128;	// 此处为增加

Mat edge(imgsrc.rows, imgsrc.cols, CV_8UC1, Scalar::all(0));

for (int r = 1; r < imgsrc.rows - 1; r++)
{
    // 3 * 3 邻域, 所以用 3 个指针, 一个指针指一行
    const unsigned char* pAmp1 = amplitude.ptr<unsigned char>(r - 1);
    const unsigned char* pAmp2 = amplitude.ptr<unsigned char>(r);
    const unsigned char* pAmp3 = amplitude.ptr<unsigned char>(r + 1);

    const short* pAng = angle.ptr<short>(r);
    unsigned char* pEdge = edge.ptr<unsigned char>(r);

    for (int c = 1; c < imgsrc.cols - 1; c++)
    {
        
        // 以下判断为增加部分
		if (pAmp2[c] < thres)
		{
			continue;
		}
		//

        switch (pAng[c])
        {
        case 270:
            if (pAmp2[c] > pAmp1[c] && pAmp2[c] >= pAmp3[c])
            {
                pEdge[c] = 255;
            }
            break;
        case 90:
            if (pAmp2[c] >= pAmp1[c] && pAmp2[c] > pAmp3[c])
            {
                pEdge[c] = 255;
            }
            break;

        case 315:
            if (pAmp2[c] > pAmp1[c - 1] && pAmp2[c] >= pAmp3[c + 1])
            {
                pEdge[c] = 255;
            }
            break;
        case 135:
            if (pAmp2[c] >= pAmp1[c - 1] && pAmp2[c] > pAmp3[c + 1])
            {
                pEdge[c] = 255;
            }
            break;

        case 0:
            if (pAmp2[c] > pAmp2[c - 1] && pAmp2[c] >= pAmp2[c + 1])
            {
                pEdge[c] = 255;
            }
            break;
        case 180:
            if (pAmp2[c] >= pAmp2[c - 1] && pAmp2[c] > pAmp2[c + 1])
            {
                pEdge[c] = 255;
            }
            break;

        case 45:
            if (pAmp2[c] >= pAmp1[c + 1] && pAmp2[c] > pAmp3[c - 1])
            {
                pEdge[c] = 255;
            }
            break;
        case 225:
            if (pAmp2[c] > pAmp1[c + 1] && pAmp2[c] >= pAmp3[c - 1])
            {
                pEdge[c] = 255;
            }
            break;

        default:
            break;
        }
    }
}
imshow("edg", edge);//总共有462个点为255(白色)
imwrite("D:\Datas\\2.jpg", edge);
waitKey(0);

//cout << "===============> end 单像素边缘 <================" << endl;

内容没完,快速版的后续继续更新!!!

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

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

相关文章

Sylar C++高性能服务器学习记录22 【ByteArray模块-代码分析篇】

早在19年5月就在某站上看到sylar的视频了&#xff0c;一直认为这是一个非常不错的视频。 由于本人一直是自学编程&#xff0c;基础不扎实&#xff0c;也没有任何人的督促&#xff0c;没能坚持下去。 每每想起倍感惋惜&#xff0c;遂提笔再续前缘。 为了能更好的看懂sylar&…

LLMs的基本组成:向量、Tokens和嵌入

编者按&#xff1a;随着人工智能技术的不断发展&#xff0c;大模型&#xff08;语言、视觉&#xff0c;或多模态模型&#xff09;已成为当今AI应用的核心组成部分。这些模型具有处理和理解自然语言等模态输入的能力&#xff0c;推动了诸如聊天机器人、智能助手、自动文本生成等…

好像也没那么失望!SD3玩起来,Stable Diffusion 3工作流商业及广告设计(附安装包)

今天基于SD3 base 工作流来尝试进行下广告设计&#xff0c;这要是一配上设计文案&#xff0c;视觉感就出来了。下面来看看一些效果展示~ SD3 Medium模型及ComfyUI工作流下载地址&#xff1a;文末领取&#xff01; 1.清凉夏日——西瓜音乐会 提示词&#xff1a; a guitar wi…

数字孪生火电厂:传统能源的数字化转型

通过图扑自主研发的产品 HT for Web &#xff0c;采用可视化与数字孪生技术&#xff0c;打造多样化设计风格和业务视角下的火电厂数字孪生方案。为智慧电厂综合“一张图”管理提供了上层展示技术支撑&#xff0c;助力企业增强对火电厂的信息化和数字化管理水平。

11.docker镜像分层dockerfile优化

docker镜像的分层&#xff08;kvm 链接克隆&#xff0c;写时复制的特性&#xff09; 镜像分层的好处&#xff1a;复用,节省磁盘空间&#xff0c;相同的内容只需加载一份到内存。 修改dockerfile之后&#xff0c;再次构建速度快 分层&#xff1a;就是在原有的基础镜像上新增了服…

Netflix 机器学习科学家的提示词优化经验分享

编者按&#xff1a; 如何充分发挥大模型的潜能&#xff0c;用好大模型&#xff0c;关键在于如何优化向它们发送的提示词&#xff08;prompt&#xff09;&#xff0c;是为提示词工程&#xff08;prompt engineering&#xff09;。 本文Netflix 机器学习科学家Cameron R. Wolfe的…

抖音视频素材在哪找无版权?免版权可以剪辑视频素材网站分享

在抖音视频制作中&#xff0c;素材的选择至关重要。今天&#xff0c;我就为大家推荐几个宝藏网站&#xff0c;帮你找到既好用又无版权纠纷的视频素材。无论你是新手还是老手&#xff0c;这些网站都能满足你的需求。 蛙学府 首先推荐的是蛙学府。这个网站提供丰富的视频素材&am…

STM32硬件接口I2C应用(基于HMC5883L)

目录 概述 1 STM32Cube控制配置I2C 1.1 I2C参数配置 1.2 使用STM32Cube产生工程 2 HAL库函数介绍 2.1 初始化函数 2.2 写数据函数 2.3 读数据函数 3 认识HMC5883L 3.1 HMC5883L功能介绍 3.2 HMC5883L的寄存器 4 HMC5883L驱动程序实现 4.1 驱动函数实现 4.2 完整驱…

xgo 原理探索

Go 单测 mock 方案 Mock 方法原理依赖优点缺点接口 Mock为依赖项定义接口&#xff0c;并提供接口的 Mock 实现。需要定义接口和 Mock 实现。灵活&#xff0c;遵循 Go 的类型系统&#xff1b;易于替换实现。需要更多的样板代码来定义接口和 Mock 实现。Monkey Patching&#xf…

AIGC绘画设计—揭秘Midjourney关键词魔法:让你的AI绘画瞬间起飞

在这个数字化飞速发展的时代&#xff0c;AI技术正以前所未有的速度改变着我们的生活和创作方式。在艺术创作领域&#xff0c;Midjourney作为一款强大的AI绘画工具&#xff0c;正逐渐受到越来越多创作者和爱好者的青睐。今天&#xff0c;我就来为大家揭秘Midjourney背后的关键词…

11.3 Go 标准库的使用技巧

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…

Kafka消息能正常发送,但是无法消费问题排查

这里是小奏,觉得文章不错可以关注公众号小奏技术 kafka version kafka_2.13-3.5.0 背景 线上的kafka集群要进行扩容&#xff0c;原先的2broker&#xff0c;扩容之后变成了新增3个broker&#xff0c;然后下掉了原先老的broker。 新集群看着没问题&#xff0c;但是出现了一个…

Java面向对象-抽象类和抽象方法

Java面向对象-抽象类和抽象方法 1、代码案例展示2、抽象类和抽象方法的关系&#xff1a; 1、代码案例展示 1、在一个类中会有一类方法&#xff0c;无需重写&#xff0c;直接使用 2、在一个类中会有一类方法&#xff0c;会对这个方法进行重写 3、一个方法的方法体去掉&#xff…

蚓链数字化营销教你寻找快准直达市场路径小绝招

在当今数字化的商业世界中&#xff0c;蚓链数字化营销成为了企业开拓市场、实现增长的有力工具。它犹如一盏明灯&#xff0c;为您照亮寻找快速直达市场路径的方向。 绝招一&#xff1a;深入的市场调研。利用蚓链数字化营销的大数据分析能力&#xff0c;全面了解目标市场的规模、…

Permute 3 mac:一键转换,格式无忧

Permute 3是一款强大而灵活的多媒体格式转换工具&#xff0c;它以其高效、易用和广泛兼容的特性&#xff0c;成为了众多用户处理媒体文件的得力助手。 Permute 3 Mac版获取 首先&#xff0c;Permute 3支持广泛的媒体格式&#xff0c;无论是视频、音频还是图片&#xff0c;都能…

Python酷库之旅-比翼双飞情侣库(08)

目录 一、xlrd库的由来 二、xlrd库优缺点 1、优点 1-1、支持多种Excel文件格式 1-2、高效性 1-3、开源性 1-4、简单易用 1-5、良好的兼容性 2、缺点 2-1、对.xlsx格式支持有限 2-2、功能相对单一 2-3、更新和维护频率低 2-4、依赖外部资源 三、xlrd库的版本说明 …

2024年心理学研究、现代化教育与社会发展国际学术会议(PRMESD 2024)

2024年心理学研究、现代化教育与社会发展国际学术会议(PRMESD 2024) 2024 International Conference on Psychological Research, Modern Education and Social Development 会议地点&#xff1a;南京&#xff0c;中国 网址&#xff1a;www.prmesd.com 邮箱: prmesdsub-con…

【嵌入式】一种优雅的 bootloader 跳转APP 的方式

【嵌入式】一种优雅的 bootloader 跳转APP 的方式 0. 个人简介 && 授权须知1. 前言2. 干净的跳转3.程序的 noinit 段4. 利用noinit段实现优雅的跳转4.1 检查栈顶地址是否合法4.2 栈顶地址 44.3 __set_MSP 5.OTA 过后的运行逻辑 0. 个人简介 && 授权须知 &#…

花卉识别-python-pytorch-CNN深度学习含数据集+pyqt界面

代码下载地址&#xff1a; https://download.csdn.net/download/qq_34904125/89383063 本代码是基于python pytorch环境安装的。 下载本代码后&#xff0c;有个requirement.txt文本&#xff0c;里面介绍了如何安装环境&#xff0c;环境需要自行配置。 或可直接参考下面博文…

利器放送丨如何在PS里使用stable diffusion插件?

各位设计界的领军人物们&#xff0c;你们一定对PS&#xff08;也就是大家熟知的Photoshop&#xff09;不陌生吧。同样&#xff0c;对于AI领域的精英们&#xff0c;SD&#xff08;stablediffusion&#xff09;这款软件也应该是如雷贯耳。这两款软件&#xff0c;各自独立且功能强…