【STM32+CUBEMX+HAL库】RTC设备学习小结

什么是RTC设备?

RTC设备即real time clock的缩写,是一种掉电也能继续计时的计时器。虽然它只有简单的计时和触发中断的功能,但它掉电也能继续运行则让它的价值瞬间上升了无数倍。

CUBEmx配置

图一

RTC设备因为其独特的运行方式(即掉电依旧运行)导致它不能使用HSE或者HSI进行分频,否则资源消耗太大,小小的纽扣电池根本吃不消。所以我们可以选择单片机内部的LSI或者使用外部晶振LSE,推荐使用外部晶振LSE,因为单片机内部的LSI容易受到电压以及温度的影响导致精度不足。

在这里插入图片描述

在这里插入图片描述

以上为基本的一些设置。

在这里插入图片描述

在这里插入图片描述

以上分别是设置中断和设置系统日期。

在这里插入图片描述

在这里插入图片描述

因为接下来的例程会使用的串口和led灯所以要在cube中配置一下。

HAL库中有关RTC设备的API讲解

在编程之前我们需要先了解一下我们一会用到的api:

/*设置系统时间*/
HAL_StatusTypeDef HAL_RTC_SetTime(RTC_HandleTypeDef *hrtc, RTC_TimeTypeDef *sTime, uint32_t Format) 
/*读取系统时间*/
HAL_StatusTypeDef HAL_RTC_GetTime(RTC_HandleTypeDef *hrtc, RTC_TimeTypeDef *sTime, uint32_t Format)
/*设置系统日期*/
HAL_StatusTypeDef HAL_RTC_SetDate(RTC_HandleTypeDef *hrtc, RTC_DateTypeDef *sDate, uint32_t Format)
/*读取系统日期*/
HAL_StatusTypeDef HAL_RTC_GetDate(RTC_HandleTypeDef *hrtc, RTC_DateTypeDef *sDate, uint32_t Format)
/*启动报警功能*/
HAL_StatusTypeDef HAL_RTC_SetAlarm(RTC_HandleTypeDef *hrtc, RTC_AlarmTypeDef *sAlarm, uint32_t Format)
/*设置报警中断*/
HAL_StatusTypeDef HAL_RTC_SetAlarm_IT(RTC_HandleTypeDef *hrtc, RTC_AlarmTypeDef *sAlarm, uint32_t Format)
/*报警时间回调函数*/
__weak void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)
/*写入后备储存器*/
void HAL_RTCEx_BKUPWrite(RTC_HandleTypeDef *hrtc, uint32_t BackupRegister, uint32_t Data)
/*读取后备储存器*/
uint32_t HAL_RTCEx_BKUPRead(RTC_HandleTypeDef *hrtc, uint32_t BackupRegister	

代码实战

接下来的代码会演示如何实时显示时间,设置rtc报警中断并启用中断,以及解决rtc掉电重启时间会重置的问题。
cubemx会自动帮我们生成初始化代码:

#include "rtc.h"
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */
RTC_HandleTypeDef hrtc;
/* RTC 初始化函数 */
void MX_RTC_Init(void)
{
  RTC_TimeTypeDef sTime = {0};
  RTC_DateTypeDef DateToUpdate = {0};
  /** 初始化RTC */
  hrtc.Instance = RTC;
  hrtc.Init.AsynchPrediv = RTC_AUTO_1_SECOND;
  hrtc.Init.OutPut = RTC_OUTPUTSOURCE_NONE;
  if (HAL_RTC_Init(&hrtc) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN Check_RTC_BKUP */
 
  /* USER CODE END Check_RTC_BKUP */

  /** 设置RTC系统时间以及日期  */
  sTime.Hours = 19;
  sTime.Minutes = 29;
  sTime.Seconds = 0;

  if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN) != HAL_OK)
  {
    Error_Handler();
  }
  DateToUpdate.WeekDay = RTC_WEEKDAY_SUNDAY;
  DateToUpdate.Month = RTC_MONTH_APRIL;
  DateToUpdate.Date = 4;
  DateToUpdate.Year = 20;

  if (HAL_RTC_SetDate(&hrtc, &DateToUpdate, RTC_FORMAT_BIN) != HAL_OK)
  {
    Error_Handler();
  }
}

void HAL_RTC_MspInit(RTC_HandleTypeDef* rtcHandle)
{
  if(rtcHandle->Instance==RTC)
  {
  /* USER CODE BEGIN RTC_MspInit 0 */

  /* USER CODE END RTC_MspInit 0 */
    HAL_PWR_EnableBkUpAccess();
    /* Enable BKP CLK enable for backup registers */
    __HAL_RCC_BKP_CLK_ENABLE();
    /* RTC clock enable */
    __HAL_RCC_RTC_ENABLE();

    /* RTC interrupt Init */
    HAL_NVIC_SetPriority(RTC_Alarm_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(RTC_Alarm_IRQn);
  /* USER CODE BEGIN RTC_MspInit 1 */

  /* USER CODE END RTC_MspInit 1 */
  }
}

void HAL_RTC_MspDeInit(RTC_HandleTypeDef* rtcHandle)
{
  if(rtcHandle->Instance==RTC)
  {
  /* USER CODE BEGIN RTC_MspDeInit 0 */

  /* USER CODE END RTC_MspDeInit 0 */
    /* Peripheral clock disable */
    __HAL_RCC_RTC_DISABLE();

    /* RTC interrupt Deinit */
    HAL_NVIC_DisableIRQ(RTC_IRQn);
    HAL_NVIC_DisableIRQ(RTC_Alarm_IRQn);
  /* USER CODE BEGIN RTC_MspDeInit 1 */

  /* USER CODE END RTC_MspDeInit 1 */
  }
} 
/* USER CODE BEGIN 1 */

/* USER CODE END 1 */

我已经把原本的英文注释翻译成了中文注释,简单的来说上面这些代码就是完成了RTC设备的初始化以及设置系统时间,并且初始化了中断。

接下来完成通过串口实时显示时间的功能:
首先为了方便接下来的使用我们需要把下面这个结构体变量转换为全局变量

RTC_TimeTypeDef sTime = {0};
#include "rtc.h"
/* USER CODE BEGIN 0 */
RTC_TimeTypeDef sTime = {0};
/* USER CODE END 0 */
RTC_HandleTypeDef hrtc;

然后点开我们的main函数,在while循环中读取当前时间,并且每秒通过串口传输到电脑上

#include "main.h"
#include "rtc.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "stdio.h"
/* USER CODE END Includes */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
int fputc(int ch,FILE *f){
     uint8_t temp[1]={ch};
     HAL_UART_Transmit(&huart1,temp,1,2);
     return ch;
}
/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */
extern RTC_TimeTypeDef sTime;
  /* USER CODE END 1 */
  
  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();
  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();
  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_RTC_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */

  /* USER CODE END 2 */
 
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
    printf("%02d:",sTime.Hours);
    printf("%02d:",sTime.Minutes);
    printf("%02d\n",sTime.Seconds);
    HAL_Delay(1000);
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

为了方便我们打印时间,我们改写了printf函数。

实现RTC报警中断功能:
首先在全局变量中定义结构体变量。

/* USER CODE BEGIN 0 */
RTC_TimeTypeDef sTime = {0};
RTC_AlarmTypeDef sAlarm ;
/* USER CODE END 0 */

让我们来看一下RTC_AlarmTypeDef这个结构体里面都有些什么?

在这里插入图片描述

在这里我们又看到了一个结构体名称,有没有觉得这个名称在哪见过?
没错!在最开始初始化RTC系统时间时我们有见到过它

在这里插入图片描述

继续查看这个结构体的内容发现:
在这里插入图片描述

原来设置报警的时间和设置系统的时间所用的数据结构是一样的。
搞清楚了报警结构体后设置报警中断就很容易了:

RTC_AlarmTypeDef sAlarm ;
void sAlarm_Config(int hours,int minutes,int seconds){
    /*填写报警结构体变量*/
  sAlarm.Alarm=RTC_ALARM_A; 
  sAlarm.AlarmTime.Hours=hours; 
  sAlarm.AlarmTime.Minutes=minutes;
  sAlarm.AlarmTime.Seconds=seconds;
  HAL_RTC_SetAlarm(&hrtc,&sAlarm, RTC_FORMAT_BIN);     //开启中断功能
  HAL_RTC_SetAlarm_IT(&hrtc,&sAlarm, RTC_FORMAT_BIN);  //设置中断
}

然后写一下回调函数:

void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc){
   HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);
}

接下来去main函数中调用一下:

#include "main.h"
#include "rtc.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "stdio.h"
/* USER CODE END Includes */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
int fputc(int ch,FILE *f){
     uint8_t temp[1]={ch};
     HAL_UART_Transmit(&huart1,temp,1,2);
     return ch;
}
void sAlarm_Config(int hours,int minutes,int seconds) //声明中断函数
/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */
extern RTC_TimeTypeDef sTime;
  /* USER CODE END 1 */
  
  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();
  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();
  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_RTC_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
void sAlarm_Config(19,30,0); //在1分钟后发送中断
  /* USER CODE END 2 */
 
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
    printf("%02d:",sTime.Hours);
    printf("%02d:",sTime.Minutes);
    printf("%02d\n",sTime.Seconds);
    HAL_Delay(1000);
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE 

当然,如果我们要完成的是在5小时候后发送中断我们可以把中断函数改写成这样:

void sAlarm_Config2(int hours,int minutes,int seconds){
  HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);   //获取设置中断时的时间
  sAlarm.Alarm=RTC_ALARM_A;
  sAlarm.AlarmTime.Hours=hours+sTime.Hours;
  sAlarm.AlarmTime.Minutes=minutes+sTime.Minutes;
  sAlarm.AlarmTime.Seconds=seconds+sTime.Seconds;
  HAL_RTC_SetAlarm(&hrtc,&sAlarm, RTC_FORMAT_BIN);
  HAL_RTC_SetAlarm_IT(&hrtc,&sAlarm, RTC_FORMAT_BIN);
}

在这里要注意一下,因为我们设置的是多少时间后进行中断,那么我们就要把当前时间加上多少我们要求的时间,所以要先获取一下当前时间是多少,如果不获取一下,结构体里面的变量就都是0。具体请看以下代码:

在这里插入图片描述

在这里插入图片描述

我们从HAL_RTC_GetTime()这个函数内容可以看出sTime结构体是通过RTC_ReadTimeCounter()这个函数读取了当前RTC设备下的RTC_CNT寄存器中的值。如果不读取的话,这个结构体里面的值就为0。
另外,我在看操作手册时看到了以下这段话:在这里插入图片描述

我最开始以为就是操作手册中的这个原因,但是我分析一下后发现,应该不是这个原因,因为在这里应该是结构体中的变量没有被赋值导致的数据错误,而不是硬件上的问题,虽然结果都是0。如果有人发现我的分析是错误的话,非常欢迎在下面评论中指出我的错误。

好了言归正传,接下来我们还要解决掉电后,日期被重置的问题。
首先在创建如下函数:

void user_CheckRtcBkup(){
   HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR1, 1); 
}

然后在RTC初始化函数中加入这句:

/* USER CODE BEGIN Check_RTC_BKUP */
   if(HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR1)==1){
       return;
   }
  /* USER CODE END Check_RTC_BKUP */

然后在main函数中调用一下:

int main(void)
{
  /* USER CODE BEGIN 1 */
extern RTC_TimeTypeDef sTime;
  /* USER CODE END 1 */
  
  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_RTC_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
  user_CheckRtcBkup();
  sAlarm_Config2(0,0,10);
  user_sendmessage();
  /* USER CODE END 2 */
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
    printf("%02d:",sTime.Hours);
    printf("%02d:",sTime.Minutes);
    printf("%02d\n",sTime.Seconds);
    HAL_Delay(1000);
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

这样我们就在初始化好RTC设备后,在后备寄存器中写入一个值1,这个后备寄存器在断电重启后值依旧不会被改变,所以在断电重启后再一次初始化时,RTC初始化函数中就不会把最开始设置的系统时间赋给sTime这个结构体。

*额外功能

当然我们在真实的开发中回调函数不大可能就单单只亮个灯,就比如我要在12小时后开启电磁阀,首先我要输出一个高电平(假设),然后在几秒钟后我们还要让电磁阀关闭,那我们相对的是不是还要输出一个低电平。以下代码完成了这部分的功能:

void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *nhrtc){
  if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0)==GPIO_PIN_SET){  
   HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);  //翻转led灯的电平
   sAlarm.AlarmTime.Hours=sTime.Hours;
   sAlarm.AlarmTime.Minutes=sTime.Minutes;
   sAlarm.AlarmTime.Seconds=sTime.Seconds+5;  //设置报警时间为5秒后
   HAL_RTC_SetAlarm_IT(&hrtc,&sAlarm,RTC_FORMAT_BIN);
  return;  //这个return很关键,在设置完中断后要马上退出回调函数,否则led灯会在下个if中再次翻转
  }
  if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0)==GPIO_PIN_RESET){
  HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);
  return;
  }
}

当然我这里还是用led灯来演示了,毕竟C语言入门hello world,单片机入门点亮led灯。具体思路就是首先检测电平,如果为高(我这块开发板低电平点亮led灯),就代表led灯是暗着的,那我们就翻转电平,并且设置中断时间为5秒后。设置完后直接退出当前回调函数,5秒后中断触发,这时电平由于是低电平那么我们就再次翻转一下,但是不再设置下一次中断。

HAL库与stm32CUBEMX学习记录——UART设备(1)

由于自己忘性比较大,所以打算花点时间把近期学的一些知识点给整理顺便巩固一下,话不多说直接开始。

一、什么时UART?

uart就是通用异步收发传输设备的简称,从名字里我们可以捕捉到几个关键词:异步、收发。那么也就是说UART没有时钟线或者时钟校准这一说因为它是异步的,而且它不仅可以收信息也可以发信息。因为这一简单实用的特点,在一些数据传输时UART被大量使用。

二、用cubemx配置UART

首先还是选择芯片型号啥的,这里我就不截图了。

然后点开connectivity,所有和通讯相关的大部分都在这儿,

点开后我们会发现不仅有uart还有usart,这两个其实没啥区别,区别就在于uart是异步传输,而usart不仅支持异步传输还支持同步传输。结合我的开发板所以我选择了pa10和pa9两个引脚作为串口设备,也理所应当地选择了usart1。

点开Mode界面我们会发现一堆模式:

从上到下分别是:异步模式

同步模式

单线模式

多处理器通讯

红外线传感器

LIN(一种运用于汽车电子设备通讯协议)

智能卡

有计时功能的智能卡

我们在这里使用第一个异步模式,软件自动为我们选择了两个引脚pa10和pa9,其中pa10作为RX(收信息),pa9作为TX(发信息)。

设置完后再去设置一下系统时钟,因为待会我们会使用串口进行中断所以我们还要使能一下NVIC(中断),最后生成一下代码。

三、HAL库函数讲解

在HAL库中关于UART设备的函数有很多,我会挑选常用且一会使用到的函数进行讲解。

我们打开stm32f1xx_hal_uart.c文件,会大体看到st公司把这么多关于uart的函数分为了4个部分:

1、Initialization and Configuration functions(初始化配置功能)

2、IO operation functions(IO操作功能)

3、Peripheral Control functions(外围操作功能)

4、Peripheral State and Errors functions(外围状态和操作功能)

我们接下来会把重点放在1和2中,3和4都是关于外设的如单线模式、LIN和多处理器之间的通讯。

1是用作初始化功能的函数,在使用标准库时这些是我们自己要进行配置的,但是有了cubemx的帮助这部分我们几乎不用管。

2才是我们真正用来串口通讯的函数,在进行通讯前我们要选择以堵塞方式还是非堵塞方式进行通讯。关于阻塞和非阻塞我也并不能说是很懂,个人感觉阻塞就是传递信息这个过程没有结束那么接下来的任务都不会进行直到这个过程结束,非阻塞就是不管你信息有没有传递完毕都会返回一个值。

阻塞和非阻塞之间的区别给我最大的感受就是,非阻塞有中断功能而阻塞没有。。。。。。而且st官方也对阻塞和非阻塞进行了解释,但是这个解释感觉并没有把阻塞与非阻塞之间的区别给清晰的写出来,也有可能是我语文理解不大好。但是st官方把大篇幅都花在了非阻塞上,而且关于非阻塞的api比阻塞的api明显多了很多(阻塞的只有2个。。。),再根据我的实践得出能用非阻塞就用非阻塞,阻塞在处理信息时有很大的误差,而非阻塞由于有中断功能所以处理信息会高效的很多而且错误率也低,所以我在使用时遵循以下这个规则:由单片机发出的信息使用阻塞式,向单片机发送的信息使用非阻塞式。

在解决完阻塞与非阻塞的问题后让我们来看一下常用的api有哪些:

(+) HAL_UART_Transmit();//阻塞模式发送
(+) HAL_UART_Receive();//阻塞模式接收

(+) HAL_UART_Transmit_IT();//非阻塞中断模式下发送
(+) HAL_UART_Receive_IT();//非阻塞中断模式下接收
(+) HAL_UART_IRQHandler();//中断回调函数句柄

(+) HAL_UART_RxCpltCallback();//中断回调函数

以上这些api可以帮你完成大部分的数据收发功能,接下来我会编写一个实例,实例可以做到接收数据并进行判断,判断成功会发送数据。

uint8_t message_receive[50];

void User_transmite(){
  
  uint8_t message[]="hello,this is SAT8";
HAL_UART_Transmit(&huart1,message,sizeof(message),50);
}

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){
   if(huart == &huart1){
     	uint16_t num1=0,num2=0;
  
  uint8_t message1[]="hello,you are my wife,right?";
  uint8_t message2[]="who are you...";
  for(int i=0;i<=sizeof(message1);i++){
      if(message_receive[i]== message1[i]){
     				num1++;
  }
      else {
        break;
      }
  }
  for(int i=0;i<=14;i++){
     if(message_receive[i]==message2[i]){
      num2++;
  }
    else{
      break;
    }
  }
  if(num1==sizeof(message1)-1){
       uint8_t shy_word[]="yes...i am your wife";
     HAL_UART_Transmit(&huart1,shy_word,sizeof(shy_word),50);
     }
  if(num2==sizeof(message2)-1){
      uint8_t angry_word[]="you forget your wife?fxxk!";
    HAL_UART_Transmit(&huart1,angry_word,sizeof(angry_word),50);
  }
     
  }
}

void User_receive(){
  HAL_UART_Receive_IT(&huart1,message_receive,30);
  HAL_Delay(100);
}
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */
  

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  
  /* USER CODE BEGIN 2 */
  HAL_UART_IRQHandler(&huart1);
  User_transmite();
  /* USER CODE END 2 */
 
 

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    User_receive();
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

调试成功,有一点要说明一下,

void User_receive(){
  HAL_UART_Receive_IT(&huart1,message_receive,30);
  HAL_Delay(100);
}

在这个接收函数中我添加了一个延时,时间为100ms。具体为什么是因为如果不加,在两次发送讯息之间时间如果过短会导致数据接收不到,具体原因还不清楚,但是一加上去后在两次快速发送讯息时导致的数据接收问题就大大改善了,如果有人能够回答一下的话就太好了。

 

openCV:使用均值模糊和高斯模糊来对图像进行处理

模糊又称平滑,是一种去噪手段,接下来我们会介绍均值模糊和高斯模糊。

均值模糊

我们会先创建一个卷积核,这个卷积核可以是3*3或者任意的x*x,如下图:

在创建完一个3*3卷积核后我们会将黄色格子和红色格子的像素值给加起来然后除以9(也就是卷积核里有多少个像素点就除以多少),这样我们就得出了平均值,然后将平均值赋给红色格子,这样我们就对一个像素完成了模糊处理。

所谓模糊就是平滑,只要让所有的像素值都趋于一个平均值就能达到平滑的效果了。接着我们就对下一个像素点进行同样的处理直到整张图片都完成处理。

openCV给出了均值模糊的API:

void blur( InputArray src, OutputArray dst,
 Size ksize, Point anchor = Point(-1,-1),
 int borderType = BORDER_DEFAULT );

src:输入图像

dst:输出图像

ksize:卷积核大小

Point anchor:锚点,最后得出的平均值所赋给的像素点坐标也就是上文所提到的红色格子,建议不要轻易改变这里的数值,默认数值为(-1,-1)

borderType: 用于推断图像外部边缘像素,有默认值BORDER_DEFAULT,这个参数是什么意思呢?就是我们在使用卷积核时是从第2行第2列开始的,那么问题来了第一行和第一列怎么办?难道我们就不管了?当然不可能,我们对边缘处理有以下几种办法:

  • ① 对称处理,就是把已有的点拷贝到另一面的对应位置,模拟出完整的矩阵。
  • ② 赋0,想象图像是无限长的图像的一部分,除了我们给定值的部分,其他部分的像素值都是0
  • ③ 赋边界值,想象图像是无限制长,但是默认赋值的不是0而是对应边界点的值

示例

#include<opencv2/opencv.hpp>
#include<stdc++.h>
using namespace cv;
using namespace std;

int main() {
  Mat a, b;
  a = imread("D:\\壁纸\\带带大师兄.jpg");
  if (!a.data) {
    cout << " 没有找到图片";
    waitKey(0);
    return -1;
  }
  imshow("清晰的孙笑川", a);
  
  blur(a, b, Size(9, 9));
  imshow("模糊的孙笑川", b);
  
  waitKey(0);
  return 0;
}

运行结果如下:

高斯模糊

均值模糊就是通过求平均值来进行模糊操作,这样的结果会使我们最后得到的图像不够平滑,而且更加容易受到噪声的干扰。

而高斯模糊则是通过线性滤波方式进行去噪,通过权重的方式来进行平均值的计算,什么意思呢?让我们看下图:

图中的数字代表每个像素点的像素值,这是让我们引入一个公式:

这是二维高斯函数以此来表明在正态分布上每个像素点的权重,

通俗点讲就是越靠近锚点的权重越大,越远离锚点的权重越小。通过加权平均数我们可以得到更平滑的模糊图像。

openCV给出了高斯模糊的api:

void GaussianBlur( InputArray src, OutputArray dst, Size ksize,
                                double sigmaX, double sigmaY = 0,
                                int borderType = BORDER_DEFAULT );

src:输入图像

dst:输出图像

ksize:卷积核大小,这里我们要注意一下在使用高斯模糊时卷积核大小只能是奇数*奇数

sigmax:在X方向的标准差,通常情况下sigmax与sigmay的值是一样的,而且openCV允许在这两个值中填写0,这样openCV会自动算出每个点的权重。

sigmay:在Y方向的标准差

borderTypr:用于推断图像外部边缘像素,有默认值BORDER_DEFAULT。

示例

#include<opencv2/opencv.hpp>
#include<stdc++.h>
using namespace cv;
using namespace std;

int main() {
  Mat a, b;
  int height,width;
  a = imread("D:\\壁纸\\带带大师兄.jpg");
  if (!a.data) {
    cout << " 没有找到图片";
    waitKey(0);
    return -1;
  }
  imshow("清晰的孙笑川", a);
  GaussianBlur(a, b, Size(9, 9), 0, 0);
  imshow("模糊的孙笑川", b);

  waitKey(0);
  return 0;
}

运行结果如下:

为了让各位对均值模糊和高斯模糊有个更直观的认识所以我展示一下均值模糊和高斯模糊的对比:

  影流之主

使用openCV来完成图像的叠加

在openCV中我们可以使用以下函数来进行图像的叠加:

addWeighted(InputArray src1, double alpha, InputArray src2, double beta, double gamma, OutputArray dst);

接下来让我们来好好分析一下这个函数:

src1:需要叠加的第一个图像

alpha:src1图像在叠加时所占的权重

src2:需要叠加的另一个图像

beta:src2图像在叠加式所占的权重

gamma:叠加完后加的常数,你可以增加gamma的值让图片更亮

dst:叠加完后输出的图像

示例

#include<opencv2/opencv.hpp>
#include<stdc++.h>
using namespace cv;
using namespace std;

int main() {

  Mat a,b,c;
  /*读取图片*/
  a = imread("D:\\壁纸\\带带大师兄.jpg");
  if (!a.data) {
    printf("没有找到图片");
    return -1;
  }
  b = imread("D:\\壁纸\\星空.jpg");
  if (!b.data) {
    printf("没有找到图片");
    return -1;
  }	
  /*当大小一致时直接进行叠加*/
  if (a.size() == b.size()) {
    addWeighted(a, 0.4, b, 0.6,0, c);
    imshow("jun", c);
  }
  /*当大小不一致时先改变大小至一致再进行叠加*/
  else 
  {
    Mat bnew;
    int row, col;
    row = a.rows;
    col = a.cols;
    resize(b, bnew, Size(col, row));			
    addWeighted(a, 0.2, bnew, 0.8, 0, c);
    imshow("jun", c);		
  }
  
  waitKey(0);
  return 0;
}

在使用addWeighted()函数时要注意两张图片的大小必须相等,所以我们在进行叠加图片前要判断两者大小是否相等。这里介绍下resize()函数:

resize( InputArray src, OutputArray dst,Size dsize, double fx = 0, double fy = 0, int interpolation = INTER_LINEAR )

我们来分析一下这个函数:

src:输入图像

dst:输出图像

dsize:改变后图像的大小,如果选择填写此参数那么我们就要填写精确的数值,如Size(30,30),这就代表改变后的图像大小为30*30。如果你不选择填写此参数你可以填写0,那么你就需要填写fx、fy参数。

fx、fy:如果你无法确定被改变大小的图像要改变的确切数值你可以填写此参数,此参数会根据你填写的数值进行放大或缩小,如:

resize(jun,bian,Size(),2,2,INTER_LINEAR);

这样我们就能把jun这个图像按x轴放到2倍,按y轴放大2倍,并得到新的图像bian。

interpolation:此参数是用来选择使用什么插值法来进行变换图片大小。不论是放大或是缩小图像都需要插值运算,缩小图像时,目标图像的像素会映射为源图像中的多个像素,放大图像时,目标图形上的像素可能无法在源图像中找到精确对应的像素,都需要进行插值运算。

当然了,你可以将此参数空着,这样就会选择默认的双线性插值法。

以下为你可以选择的插值法:

1)INTER_NEAREST – 最近邻插值法
2)INTER_LINEAR – 双线性插值法(默认)
3)INTER_AREA – 基于局部像素的重采样(resampling using pixel area relation)。对于图像抽取(image decimation)来说,这可能是一个更好的方法。但如果是放大图像时,它和最近邻法的效果类似。
4)INTER_CUBIC – 基于4×4像素邻域的3次插值法
5)INTER_LANCZOS4 – 基于8×8像素邻域的Lanczos插值

使用resize()函数我们就能让a图像和b图像大小一致了,接下来就是使用addWeighted()函数进行叠加操作了。

让我们来运行一下试试:

我们可以看到两幅图片叠加到一起了!是不是觉得很儒雅随和呢?

 

openCV:Mat对象详解和使用

Mat对象

Mat是OpenCV中用来存储图像信息的内存对象,可以理解为一个包含所有强度值的像素点矩阵,另外包含其他信息(宽,高,类型,纬度,大小,深度等)。

当你使用Mat对象时会自动为你分配内存空间,不用再像openCV2.x.x版本之前一样使用IplImage还要自己分配内存空间。

Mat对象使用

首先是最简单的使用:

Mat a;
a = imread("...");

定义一个Mat对象a后使用imread函数读取一张图片。

我们还能这样使用Mat:

Mat a,b;
a=imread("...");
b=Mat(a.size(),a.type());

首先定义一个Mat对象a并读取一张图片后定义一个Mat对象b,并且这个对象b的大小和格式与a是一样的。

Mat a,b;
a=imread("...");
b=Mat(a.size(),a.type());
b=Scalar(255,255,255);

因为我们刚才只是定义了一个Mat对象b的框架所以我们可以使用Scalar函数来让b这个对象生成一张图片,图片的颜色为白色(因为三原色的值都为255所以是白色)。

Mat a,b;
a=imread("...");
b=a.clon();

如果我们想要完全复制一张照片要怎么办呢?这时我们就可以使用clon()这个函数了,clon()是全复制你不需要担心因为改变了对象a的数值而会导致对象b的数值随着a改变而改变。

Mat a,b;
a = imread("..");
a.copyTo(b);

copyTo()函数和clon()函数也是一样的效果,你可以根据自己的喜好去使用。

Mat a,b;
a = imread("..");
cvtColor(a,b,COLOR_BGR2GRAY);

cvtColor()函数可以将a复制到b并且以COLOR_BGR2GRAY的形式展现给你,在这里COLOR_BGR2GRAY是参数名称灰度图的意思。你可以根据需要使用其它参数。

Mat a(3,3,CV_8UC3,Scalar(50,100,200);
cout<<a<<endl;
namedWindow("test",CV_WINDOW_AUTOSIZE);
imshow("test",a);

我们同样可以使用Mat对象创建一个x*x的矩阵,并根据参数CV_8UC3来为它每个像素点来设置数值为(50,100,200)。需要解释一下的是C3代表的是Channel 3,意味着我们这张图片每个像素点有3个通道分别对应的是三原色(RGB),而后面的数值(50,100,200)就是三原色的数值(当然你可以根据需要自己变)。

除了三通道还有一通道,也就是灰度图(还记得上文哪有提到过🐎?)

Mat a = Mat::eye(5, 5, CV_8UC1);

我们可以通过上面这种形式来对对象a进行初始化,这里的eye代表最后出现的矩阵是以对角线形式出现的

你可以修改参数为zeros,这样它就会把矩阵通通初始化为0:

 

Mat对象差不多就讲到这儿了,光看没有用建议打开电脑自己也来操作一遍这样印象才深刻嗷!

如何在openCV中使用掩膜来完成对图像的处理

什么是掩膜?

在本篇文章开始前我们需要先来了解一下什么是掩膜,掩膜就是用选定的图像、图形或物体,对处理的图像(全部或局部)进行遮挡,来控制图像处理的区域或处理过程。用于覆盖的特定图像或物体称为掩模或模板。

掩膜通常是个n*n的矩阵,我们可以通过掩膜来更好地处理图像。

代码解析

首先贴上源码,关于代码的解析我已经写好了注释:

#include<opencv2/opencv.hpp>
#include<stdc++.h>

using namespace std;
using namespace cv;
int main() {
  Mat jun = imread("D:\\壁纸\\jun.png");//读取图片
  namedWindow("junbian", CV_WINDOW_AUTOSIZE);//创建窗口
  imshow("junbian", jun);//在窗口上显示图片

  Mat bian;
/* 创建一个掩膜 */
  Mat kernel = (Mat_<char>(3,3)<<0, -1,  0,
                              -1, 5, -1, 
                               0,-1, 0);
/* 使用filter2D来对图像进行处理 */
  filter2D(jun, bian, -1, kernel);

  namedWindow("cool jun", CV_WINDOW_AUTOSIZE);
  imshow("cool jun", bian);

  waitKey(0);
  return 0;
}

让我们来看看结果:

我们会发现处理过后的图像相较于原图对比度增强了。

接下来让我们来换一个掩膜试试看:

#include<opencv2/opencv.hpp>
#include<stdc++.h>

using namespace std;
using namespace cv;
int main() {
  Mat jun = imread("D:\\壁纸\\jun.png");//读取图片
  namedWindow("junbian", CV_WINDOW_AUTOSIZE);//创建窗口
  imshow("junbian", jun);//在窗口上显示图片

  Mat bian;
/* 创建一个掩膜 */
  Mat kernel = (Mat_<char>(3,3)<<-1, 0, -1,
                                  0, 4, 0, 
                                  -1,0,-1);
/* 使用filter2D来对图像进行处理 */
  filter2D(jun, bian, -1, kernel);

  namedWindow("cool jun", CV_WINDOW_AUTOSIZE);
  imshow("cool jun", bian);

  waitKey(0);
  return 0;
}

运行后的结果:

我们可以看到图像的轮廓被描绘了出来,在这里我们使用的掩膜是拉普拉斯滤波器,它可以将画面轮廓清晰地描绘出来。

如何在vs2015中配置openCV编译环境

(一)

从官网上下载openCV的exe文件,点击窗口按钮会自动进行下载。目前最新的版本有4.1.1和3.4.7,在本教程中将以3.4.7的版本作为例子。

(二)

下载完后我们会看到以下安装包,打开它选择安装路径后进行安装。

安装完毕后应该是这样的:

(三)

接下来我们来配置环境变量,好让vs2015更好地运行进程。

首先打开以下路径,然后复制。

接着打开控制面板——>高级系统设置——>环境变量。

点开环境变量——>Path——>编辑,然后把刚才复制的路径拷贝进去。

最后别忘记点确定。

(四)

然后我们要把我们所需要的头文件给包含进vs2015。

打开vs2015,新建一个控制台程序。

点击创建——>下一步——>勾选空项目——>完成。

接着打开属性管理器——>点击Microsoft.Cpp.x64.user的属性页——>包含目录——>编辑

找到我们头文件的路径然后点击“选择文件夹”,将所有路径都包含进去。

和刚才一样,我们点开库目录,并将路径包含进去。

同样的,我们点开链接器——>输入——>附加依赖项——>编辑

输入我们的路径,每个版本的名字都不一样大家可以在\openCV\build\x64\vc15\lib这个路径中查看你的是什么名字。

(五)

最后一步,我们将管理器配置成x64。

 

至此我们的开发环境就都配置成功啦!!!我们可以新建一个项目来检测我们是否配置成功。

新建一个项目,我们这里给新项目取名为jun,然后我们来读取一张照片。

说明我们的编译环境已经配置成功了。

【上海电机学院】易班熊和电机有个约会

易班熊与电机有个约会

迎上海电机学院审核评估

晴时花开满树,雨天月湖涟漪;阳光席卷楼阁,书香溢满校园。入夜每条马路温暖铺开的灯光,出行时寝室阿姨亲切的叮嘱。这里,是上海电机学院,易班熊陪你走进。

 

 

电机与易班

 

上海电机学院于我们,如徽章别在岁月的衣襟上,如飞鸟掠过长山湖畔,滞留下和风中最绵长的身影,她走过的时光动听宛如不老的少年。
上海电机学院易班发展中心与上海电机学院一起进步、共同发展,坚持引领、服务、传承的初心,在上海电机学院的六十五周年校庆之际也送上来自易班的祝愿。

 

65年来全体电机人一直秉承着“自强不息,追求卓越。”的电机精神做事做人;将“明德至善,博学笃行。”的校训铭记于心;办学指导一直以“技术主校,应用为本”为方针。“随风潜入夜,润物细无声。”上海电机学院无声中用行动感染了改变了我们每一位电机学子,并将上海电机学院教会我们的东西牢记在心。

 

电机全体师生都以一颗感恩而积极的心努力辛勤的筹备,为母校校庆奉献属于自己的一份心意。

 

春雪剧社以生动而富有情感的话剧重现了上海电机学院六十五年历经的风风雨雨!

校运动会更是展现了电机学子积极向上、健康乐观的态度与精神面貌!

10月16日电机校友开放日也迎来了从母校毕业的学生,让校友们时隔多年再一次感受到母校的温度!

 

 各个校区各个学院都为母校校庆献出自己的节目,或感动热烈,抑或是激情澎湃,不论是什么样的表演形式,对学校的感情却都是始终一致的。学校的六十五周年校庆不仅仅是一次回首与纪念,更是学校对于自身发展的历史新起点,回顾过去,总结经验,展望未来!

 

跟随易班熊的脚步,我们走在上海电机学院的每一帧风景里

图书馆满载着电机学子的理想与辛勤,不论是风雨交加的黄昏,还是阳光明媚的下午,图书馆的台阶上都有着电机学子辛勤而坚定走向知识与书籍的身影。

 

 体育馆中,电机学子正挥洒着汗水,朝气蓬勃,散发着生命的力量,象征着上海电机学院欣欣向荣的明天。

 

落日的余晖洒在月河上,为电机的校园又添了一帧无法忽视的画面… …

 

怀抱热情迎接审核评估

我们坚信机遇总是与挑战并存共生,并且我们期待每一个机遇的来临,并怀着饱满的自信与热情迎接每一个挑战。学校迎来了审核评估,审核评估的结果对每一位电机人都非常重要,它肯定我们的努力,带来前进的动力,明确我们的目标。因此电机学院全体师生都非常积极的应对审核评估,怀着激动的心情以最好的面貌迎接一切挑战与机遇,展现上海电机学院的良好风貌与校容,推动学校的发展与进步!

凝心聚力,迎接评估!

电机有迷人的四季:桃红柳绿的春,花繁叶茂的夏,枫红菊香的秋,松青雪白的冬。而最美的风景,是勤奋刻苦的每一位电机人。遮雨的伞,挡风的墙,这里是温暖而又厚重的上海电机学院。

 

 学校,注定是每一位学子日后心心念念的地方,无论何时无论何地,电机易班等你,上海电机学院等你。

 你与上海电机学院有个约会!

浅谈tcp/ip协议

什么是 TCP/IP?

TCP/IP 是供已连接因特网的计算机进行通信的通信协议。

TCP/IP 指传输控制协议/网际协议 (Transmission Control Protocol / Internet Protocol)。

TCP/IP 定义了电子设备(比如计算机)如何连入因特网,以及数据如何在它们之间传输的标准。

这张表就是tcp/ip模型和OSI模型的模型图。我们可以看到tcp/ip模型比osi模型更加精简一些。

tcp/ip协议中各层的作用

在我们刚刚贴出的表中可以看到,tcp/ip协议分为四层,分别是:应用层,传输层,网络层和数据链路层。

应用层

应用层是TCP/IP协议的第一层,是直接为应用进程提供服务的。对不同种类的应用程序它们会根据自己的需要来使用应用层的不同协议。应用层还能加密、解密、格式化数据。应用层可以建立或解除与其他节点的联系。常见的应用层协议有:HTTP,MQTT,SMTP等等。

大家平时在登陆网站的时候经常会以https作为开头,https是http协议的变种,区别在于http使用明文传输容易被破解,安全性不高,而https协议会对传输的内容进行加密,使得器安全性大大增加,所以https协议已经几乎取代了http协议(当然有些网站用的还是http……)。

运输层

作为TCP/IP协议的第二层,运输层在整个TCP/IP协议中起到了中流砥柱的作用。

它为两台主机上的应用程序提供端到端的通信。常见的传输层协议有:tcp,udp协议。

网络层

网络层是OSI参考模型中的第三层,介于传输层和数据链路层之间,它在数据链路层提供的两个相邻端点之间的数据帧的传送功能上,进一步管理网络中的数据通信,将数据设法从源端经过若干个中间节点传送到目的端,从而向运输层提供最基本的端到端的数据传送服务。

主要的协议有ip,ipx等协议。

数据链路层

数据链路层最基本的服务是将源计算机网络层来的数据可靠的传输到相邻节点的目标计算机的网络层。

主要协议有点对点协议(Point-to-Point Protocol);以太网(Ethernet);高级数据链路协议(High-Level Data Link Protocol);帧中继(Frame Relay);异步传输模式(Asynchronous Transfer Mode)。

数据通过一层层的流动最后会发送出去,接受的服务端也会按照tcp/ip协议来进行读取信息,从数据链路层开始直到应用层最后到我们手上的就是经过处理好后的信息了。

军事理论满分秘籍

1.打开mdrfb.com   拖到最下方,搜索

2.进入

3.进入之后,CTRL+F同时按住,会出现搜索栏

4.之后浏览器新开一页,输入172.20.8.10:8080,记得更改DNS为172.20.68.5或者链接学校的VPN再进入,即可开始做题