注释最详细、代码最简单的STM32+摄像头+显示屏的颜色跟踪电路软硬件方案

STM32库函数开发系列文章目录

第一篇:STM32F103ZET6单片机双串口互发程序设计与实现
第二篇:最简单DIY基于STM32单片机的蓝牙智能小车设计方案
第三篇:最简单DIY基于STM32F407探索者开发板的MPU6050陀螺仪姿态控制舵机程序
第四篇:最简单DIY基于STM32F407探索者开发板和PCA9685舵机控制模块的红外遥控机械臂控制程序
第五篇:注释最详细、代码最简单的STM32+摄像头+显示屏的颜色跟踪电路软硬件方案



前言

    daodanjishui物联网核心原创技术之注释最详细、代码最简单的STM32+摄像头+显示屏的颜色跟踪电路软硬件方案。
    市面上有各种开源STM32+摄像头+显示屏构成颜色跟踪系统,但是有复杂的有简单的,如果想快速入门STM32带显示屏和摄像头的颜色跟踪,这个方案会给你一个快捷高效的方案。


一、注释最详细、代码最简单的STM32+摄像头+显示屏的颜色跟踪电路软硬件方案是什么?

    我记得本栏的第三篇和第四篇博文的设计中大量使用了库函数和别人的开源代码,鲁迅先生的“拿来主义”表现的淋漓尽致,这也是STM32库函数开发的魅力所在,为了展示结合网络资源和各种资源呈现一个曾经热门的设计,所以诞生第五篇博文。
    这次的方案主要是:最简单的单片机摄像头颜色跟踪方案。下面请看全家福:
在这里插入图片描述

优酷视频演示地址:https://v.youku.com/v_show/id_XNTEzOTQxMTA5Mg==.html

直接观看

正点原子探索者开发板摄像头颜色跟踪

这个方案历史的起源是2012年阿莫论坛一个博主发布了一个颜色跟踪的代码,链接是:https://www.amobbs.com/thread-5499408-1-1.html

截图如下(铭记历史):
在这里插入图片描述
    差不多国内的所有基于单片机的颜色识别方案都用了这个内容,包括电路城里面很多卖家的电路。这个曾经火热的开源代码网站逐步走向了没落,这个网站现在有些服务好像是要花钱的了,取而代之的是正点原子开源论坛和野火开源论坛,这些论坛真的开源力度够,我也不是他们的托,事实说话。为了学习ESP8266,我曾经把正点原子关于ESP8266的帖子全部看完了,呵呵,有意思。这个颜色开源的代码也被拿来作为买卖商品,不过这也是无可厚非,想把这套代码在固定的硬件跑起来确实要花费不少功夫的,正点原子赠送的源码是没有F407驱动OV7725摄像头的,只有F1驱动OV7670的代码,我从F103的OV7670移植过去成为现在的F407驱动OV7725,正点原子配套的F407手册也只有OV2640摄像头的教程而已,OV2640摄像头没有FIFO意义不大,OV7725的性能要比OV7670的要好,所以我全选最好的。我使用的硬件平台是正点原子全家桶:探索者F407开发板,OV7725摄像头模块,ALIENTEK 4.3 TFTLCD,全部模块都是插上就用,原理图都是用的他们家的省事省力,这就是项目开头所说的:“注释最详细、代码最简单”的效果了,高校的学生只要有这个开发板,买了我的方案保证能快速掌握颜色跟踪,我的代码绝对让你物有所值。

二、使用步骤

1.准备硬件

1.1 正点原子探索者开发板一个,如果没有的话用STM32F407ZGT6核心板也可以的,现在美国ST公司禁售该芯片,估计以后这个芯片就被淘汰了。
1.2 OV7755带FIFO摄像头模块一个(正点原子出品)。
1.3 ALIENTEK 4.3 TFTLCD一个。

2.准备正点原子开源摄像头的代码

我采用库函数开发,所以代码直接使用该开发板配套的免费开源源码进行二次开发。源码来源:探索者光盘资料\A盘\4,程序源码\2,标准例程-库函数版本\实验35 摄像头实验

代码如下(示例):

#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "key.h"
#include "lcd.h"
#include "usmart.h"  
#include "usart2.h"  
#include "timer.h" 
#include "ov2640.h" 
#include "dcmi.h" 
//ALIENTEK 探索者STM32F407开发板 实验35
//摄像头 实验 -库函数版本
//技术支持:www.openedv.com
//淘宝店铺:http://eboard.taobao.com  
//广州市星翼电子科技有限公司  
//作者:正点原子 @ALIENTEK

u8 ov2640_mode=0;						//工作模式:0,RGB565模式;1,JPEG模式

#define jpeg_buf_size 31*1024  			//定义JPEG数据缓存jpeg_buf的大小(*4字节)
__align(4) u32 jpeg_buf[jpeg_buf_size];	//JPEG数据缓存buf
volatile u32 jpeg_data_len=0; 			//buf中的JPEG有效数据长度 
volatile u8 jpeg_data_ok=0;				//JPEG数据采集完成标志 
										//0,数据没有采集完;
										//1,数据采集完了,但是还没处理;
										//2,数据已经处理完成了,可以开始下一帧接收
//JPEG尺寸支持列表
const u16 jpeg_img_size_tbl[][2]=
{
	176,144,	//QCIF
	160,120,	//QQVGA
	352,288,	//CIF
	320,240,	//QVGA
	640,480,	//VGA
	800,600,	//SVGA
	1024,768,	//XGA
	1280,1024,	//SXGA
	1600,1200,	//UXGA
}; 
const u8*EFFECTS_TBL[7]={"Normal","Negative","B&W","Redish","Greenish","Bluish","Antique"};	//7种特效 
const u8*JPEG_SIZE_TBL[9]={"QCIF","QQVGA","CIF","QVGA","VGA","SVGA","XGA","SXGA","UXGA"};	//JPEG图片 9种尺寸 


//处理JPEG数据
//当采集完一帧JPEG数据后,调用此函数,切换JPEG BUF.开始下一帧采集.
void jpeg_data_process(void)
{
	if(ov2640_mode)//只有在JPEG格式下,才需要做处理.
	{
		if(jpeg_data_ok==0)	//jpeg数据还未采集完?
		{	
			DMA_Cmd(DMA2_Stream1, DISABLE);//停止当前传输 
			while (DMA_GetCmdStatus(DMA2_Stream1) != DISABLE){}//等待DMA2_Stream1可配置  
			jpeg_data_len=jpeg_buf_size-DMA_GetCurrDataCounter(DMA2_Stream1);//得到此次数据传输的长度
				
			jpeg_data_ok=1; 				//标记JPEG数据采集完按成,等待其他函数处理
		}
		if(jpeg_data_ok==2)	//上一次的jpeg数据已经被处理了
		{
			DMA2_Stream1->NDTR=jpeg_buf_size;	
			DMA_SetCurrDataCounter(DMA2_Stream1,jpeg_buf_size);//传输长度为jpeg_buf_size*4字节
			DMA_Cmd(DMA2_Stream1, ENABLE);			//重新传输
			jpeg_data_ok=0;						//标记数据未采集
		}
	}
} 
//JPEG测试
//JPEG数据,通过串口2发送给电脑.
void jpeg_test(void)
{
	u32 i; 
	u8 *p;
	u8 key;
	u8 effect=0,saturation=2,contrast=2;
	u8 size=3;		//默认是QVGA 320*240尺寸
	u8 msgbuf[15];	//消息缓存区 
	LCD_Clear(WHITE);
  POINT_COLOR=RED; 
	LCD_ShowString(30,50,200,16,16,"ALIENTEK STM32F4");
	LCD_ShowString(30,70,200,16,16,"OV2640 JPEG Mode");
	LCD_ShowString(30,100,200,16,16,"KEY0:Contrast");			//对比度
	LCD_ShowString(30,120,200,16,16,"KEY1:Saturation"); 		//色彩饱和度
	LCD_ShowString(30,140,200,16,16,"KEY2:Effects"); 			//特效 
	LCD_ShowString(30,160,200,16,16,"KEY_UP:Size");				//分辨率设置 
	sprintf((char*)msgbuf,"JPEG Size:%s",JPEG_SIZE_TBL[size]);
	LCD_ShowString(30,180,200,16,16,msgbuf);					//显示当前JPEG分辨率
	
 	OV2640_JPEG_Mode();		//JPEG模式
	My_DCMI_Init();			//DCMI配置
	DCMI_DMA_Init((u32)&jpeg_buf,jpeg_buf_size,DMA_MemoryDataSize_Word,DMA_MemoryInc_Enable);//DCMI DMA配置   
	OV2640_OutSize_Set(jpeg_img_size_tbl[size][0],jpeg_img_size_tbl[size][1]);//设置输出尺寸 
	DCMI_Start(); 		//启动传输
	while(1)
	{
		if(jpeg_data_ok==1)	//已经采集完一帧图像了
		{  
			p=(u8*)jpeg_buf;
			LCD_ShowString(30,210,210,16,16,"Sending JPEG data..."); //提示正在传输数据
			for(i=0;i<jpeg_data_len*4;i++)		//dma传输1次等于4字节,所以乘以4.
			{
        while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET);	//循环发送,直到发送完毕  		
				USART_SendData(USART2,p[i]); 
				key=KEY_Scan(0); 
				if(key)break;
			} 
			if(key)	//有按键按下,需要处理
			{  
				LCD_ShowString(30,210,210,16,16,"Quit Sending data   ");//提示退出数据传输
				switch(key)
				{				    
					case KEY0_PRES:	//对比度设置
						contrast++;
						if(contrast>4)contrast=0;
						OV2640_Contrast(contrast);
						sprintf((char*)msgbuf,"Contrast:%d",(signed char)contrast-2);
						break;
					case KEY1_PRES:	//饱和度Saturation
						saturation++;
						if(saturation>4)saturation=0;
						OV2640_Color_Saturation(saturation);
						sprintf((char*)msgbuf,"Saturation:%d",(signed char)saturation-2);
						break;
					case KEY2_PRES:	//特效设置				 
						effect++;
						if(effect>6)effect=0;
						OV2640_Special_Effects(effect);//设置特效
						sprintf((char*)msgbuf,"%s",EFFECTS_TBL[effect]);
						break;
					case WKUP_PRES:	//JPEG输出尺寸设置   
						size++;  
						if(size>8)size=0;   
						OV2640_OutSize_Set(jpeg_img_size_tbl[size][0],jpeg_img_size_tbl[size][1]);//设置输出尺寸  
						sprintf((char*)msgbuf,"JPEG Size:%s",JPEG_SIZE_TBL[size]);
						break;
				}
				LCD_Fill(30,180,239,190+16,WHITE);
				LCD_ShowString(30,180,210,16,16,msgbuf);//显示提示内容
				delay_ms(800); 				  
			}else LCD_ShowString(30,210,210,16,16,"Send data complete!!");//提示传输结束设置 
			jpeg_data_ok=2;	//标记jpeg数据处理完了,可以让DMA去采集下一帧了.
		}		
	}    
} 
//RGB565测试
//RGB数据直接显示在LCD上面
void rgb565_test(void)
{ 
	u8 key;
	u8 effect=0,saturation=2,contrast=2;
	u8 scale=1;		//默认是全尺寸缩放
	u8 msgbuf[15];	//消息缓存区 
	LCD_Clear(WHITE);
    POINT_COLOR=RED; 
	LCD_ShowString(30,50,200,16,16,"ALIENTEK STM32F4");
	LCD_ShowString(30,70,200,16,16,"OV2640 RGB565 Mode");
	
	LCD_ShowString(30,100,200,16,16,"KEY0:Contrast");			//对比度
	LCD_ShowString(30,130,200,16,16,"KEY1:Saturation"); 		//色彩饱和度
	LCD_ShowString(30,150,200,16,16,"KEY2:Effects"); 			//特效 
	LCD_ShowString(30,170,200,16,16,"KEY_UP:FullSize/Scale");	//1:1尺寸(显示真实尺寸)/全尺寸缩放
	
	OV2640_RGB565_Mode();	//RGB565模式
	My_DCMI_Init();			//DCMI配置
	DCMI_DMA_Init((u32)&LCD->LCD_RAM,1,DMA_MemoryDataSize_HalfWord,DMA_MemoryInc_Disable);//DCMI DMA配置  
 	OV2640_OutSize_Set(lcddev.width,lcddev.height); 
	DCMI_Start(); 		//启动传输
	while(1)
	{ 
		key=KEY_Scan(0); 
		if(key)
		{ 
			DCMI_Stop(); //停止显示
			switch(key)
			{				    
				case KEY0_PRES:	//对比度设置
					contrast++;
					if(contrast>4)contrast=0;
					OV2640_Contrast(contrast);
					sprintf((char*)msgbuf,"Contrast:%d",(signed char)contrast-2);
					break;
				case KEY1_PRES:	//饱和度Saturation
					saturation++;
					if(saturation>4)saturation=0;
					OV2640_Color_Saturation(saturation);
					sprintf((char*)msgbuf,"Saturation:%d",(signed char)saturation-2);
					break;
				case KEY2_PRES:	//特效设置				 
					effect++;
					if(effect>6)effect=0;
					OV2640_Special_Effects(effect);//设置特效
					sprintf((char*)msgbuf,"%s",EFFECTS_TBL[effect]);
					break;
				case WKUP_PRES:	//1:1尺寸(显示真实尺寸)/缩放	    
					scale=!scale;  
					if(scale==0)
					{
						OV2640_ImageWin_Set((1600-lcddev.width)/2,(1200-lcddev.height)/2,lcddev.width,lcddev.height);//1:1真实尺寸
						OV2640_OutSize_Set(lcddev.width,lcddev.height); 
						sprintf((char*)msgbuf,"Full Size 1:1");
					}else 
					{
						OV2640_ImageWin_Set(0,0,1600,1200);				//全尺寸缩放
						OV2640_OutSize_Set(lcddev.width,lcddev.height); 
						sprintf((char*)msgbuf,"Scale");
					}
					break;
			}
			LCD_ShowString(30,50,210,16,16,msgbuf);//显示提示内容
			delay_ms(800); 
			DCMI_Start();//重新开始传输
		} 
		delay_ms(10);		
	}    
} 
int main(void)
{ 
	u8 key;
	u8 t;
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2
	delay_init(168);  //初始化延时函数
	uart_init(115200);		//初始化串口波特率为115200
	usart2_init(42,115200);		//初始化串口2波特率为115200
	LED_Init();					//初始化LED 
 	LCD_Init();					//LCD初始化  
 	KEY_Init();					//按键初始化 
	TIM3_Int_Init(10000-1,8400-1);//10Khz计数,1秒钟中断一次
	
 	usmart_dev.init(84);		//初始化USMART
 	POINT_COLOR=RED;//设置字体为红色 
	LCD_ShowString(30,50,200,16,16,"Explorer STM32F4");	
	LCD_ShowString(30,70,200,16,16,"OV2640 TEST");	
	LCD_ShowString(30,90,200,16,16,"ATOM@ALIENTEK");
	LCD_ShowString(30,110,200,16,16,"2014/5/14");  	 
	while(OV2640_Init())//初始化OV2640
	{
		LCD_ShowString(30,130,240,16,16,"OV2640 ERR");
		delay_ms(200);
	    LCD_Fill(30,130,239,170,WHITE);
		delay_ms(200);
	}
	LCD_ShowString(30,130,200,16,16,"OV2640 OK");  	  
 	while(1)
	{	
		key=KEY_Scan(0);
		if(key==KEY0_PRES)			//RGB565模式
		{
			ov2640_mode=0;   
			break;
		}else if(key==KEY1_PRES)	//JPEG模式
		{
			ov2640_mode=1;
			break;
		}
		t++; 									  
		if(t==100)LCD_ShowString(30,150,230,16,16,"KEY0:RGB565  KEY1:JPEG"); //闪烁显示提示信息
 		if(t==200)
		{	
			LCD_Fill(30,150,210,150+16,WHITE);
			t=0; 
		}
		delay_ms(5);	  
	}
	if(ov2640_mode)jpeg_test();
	else rgb565_test(); 
}

3.准备阿莫论坛的颜色跟踪代码

EasyTracer.c代码如下

#include "EasyTracer.h"
//#include "MT9M111.h"
#define imgwidth 640
#define imgheight 480

#define min3v(v1, v2, v3)   ((v1)>(v2)? ((v2)>(v3)?(v3):(v2)):((v1)>(v3)?(v3):(v1)))
#define max3v(v1, v2, v3)   ((v1)<(v2)? ((v2)<(v3)?(v3):(v2)):((v1)<(v3)?(v3):(v1)))

#define GUI_ReadBit16Point   LCD_ReadPoint


//读取RBG格式颜色,唯一需要移植的函数
extern unsigned short GUI_ReadBit16Point(unsigned short x,unsigned short y);
static void ReadColor(unsigned int x,unsigned int y,COLOR_RGB *Rgb)
{
	unsigned short C16;

	C16 = GUI_ReadBit16Point(2*x,2*y);

	Rgb->red   =	 (unsigned char)((C16&0xf800)>>8);
	Rgb->green =	 (unsigned char)((C16&0x07e0)>>3);
	Rgb->blue  =     (unsigned char)((C16&0x001f)<<3);
}



//RGB转HSL
static void RGBtoHSL(const COLOR_RGB *Rgb, COLOR_HSL *Hsl)
{
    int h,s,l,maxVal,minVal,difVal;
	int r  = Rgb->red;
	int g  = Rgb->green;
  int b  = Rgb->blue;
	
	maxVal = max3v(r, g, b);
	minVal = min3v(r, g, b);
	
	difVal = maxVal-minVal;
	
	//计算亮度
    l = (maxVal+minVal)*240/255/2;
	
	if(maxVal == minVal)//若r=g=b
	{
		h = 0; 
		s = 0;
	}
	else
	{
		//计算色调
		if(maxVal==r)
		{
			if(g>=b)
				h = 40*(g-b)/(difVal);
			else
				h = 40*(g-b)/(difVal) + 240;
		}
		else if(maxVal==g)
			h = 40*(b-r)/(difVal) + 80;
		else if(maxVal==b)
			h = 40*(r-g)/(difVal) + 160;
		//计算饱和度
		if(l == 0)
			s = 0;
		else if(l<=120)
			s = (difVal)*240/(maxVal+minVal);
		else
			s = (difVal)*240/(480 - (maxVal+minVal));
	}
    Hsl->hue =        (unsigned char)(((h>240)? 240 : ((h<0)?0:h)));
    Hsl->saturation = (unsigned char)(((s>240)? 240 : ((s<0)?0:s)));
    Hsl->luminance =  (unsigned char)(((l>240)? 240 : ((l<0)?0:l)));
}

//匹配颜色
static int ColorMatch(const COLOR_HSL *Hsl,const TARGET_CONDI *Condition)
{
	if( 
		Hsl->hue		>=	Condition->H_MIN &&
		Hsl->hue		<=	Condition->H_MAX &&
		Hsl->saturation	>=	Condition->S_MIN &&
		Hsl->saturation	<=   Condition->S_MAX &&
		Hsl->luminance	>=	Condition->L_MIN &&
		Hsl->luminance	<=   Condition->L_MAX 
    )
		return 1;
	else
		return 0;
}

//搜索腐蚀中心
static int SearchCentre(unsigned int *x,unsigned int *y,const TARGET_CONDI *Condition,const SEARCH_AREA *Area)
{
	unsigned int SpaceX,SpaceY,i,j,k,FailCount=0;
	COLOR_RGB Rgb;
	COLOR_HSL Hsl;
	
	SpaceX = Condition->WIDTH_MIN/3;
	SpaceY = Condition->HIGHT_MIN/3;

	for(i=Area->Y_Start;i<Area->Y_End;i+=SpaceY)
	{
		for(j=Area->X_Start;j<Area->X_End;j+=SpaceX)
		{
			FailCount=0;
			for(k=0;k<SpaceX+SpaceY;k++)
			{
				if(k<SpaceX)
					ReadColor(j+k,i+SpaceY/2,&Rgb);
				else
					ReadColor(j+SpaceX/2,i+(k-SpaceX),&Rgb);
				RGBtoHSL(&Rgb,&Hsl);
				
				if(!ColorMatch(&Hsl,Condition))
					FailCount++;
				if(FailCount>((SpaceX+SpaceY)>>ALLOW_FAIL_PER))
					break;
			}
			if(k==SpaceX+SpaceY)
			{
				*x = j+SpaceX/2;
				*y = i+SpaceY/2;
				return 1;
			}
		}
	}
	return 0;
}

//从腐蚀中心向外腐蚀,得到新的腐蚀中心
static int Corrode(unsigned int oldx,unsigned int oldy,const TARGET_CONDI *Condition,RESULT *Resu)
{
	unsigned int Xmin,Xmax,Ymin,Ymax,i,FailCount=0;
	COLOR_RGB Rgb;
	COLOR_HSL Hsl;
	
	for(i=oldx;i>IMG_X;i--)
	{
		ReadColor(i,oldy,&Rgb);
		RGBtoHSL(&Rgb,&Hsl);
		if(!ColorMatch(&Hsl,Condition))
			FailCount++;
		if(FailCount>(((Condition->WIDTH_MIN+Condition->WIDTH_MAX)>>2)>>ALLOW_FAIL_PER))
			break;	
	}
	Xmin=i;
	FailCount=0;
	
	for(i=oldx;i<IMG_X+IMG_W;i++)
	{
		ReadColor(i,oldy,&Rgb);
		RGBtoHSL(&Rgb,&Hsl);
		if(!ColorMatch(&Hsl,Condition))
			FailCount++;
		if(FailCount>(((Condition->WIDTH_MIN+Condition->WIDTH_MAX)>>2)>>ALLOW_FAIL_PER))
			break;	
	}
	Xmax=i;
	FailCount=0;
	
	for(i=oldy;i>IMG_Y;i--)
	{
		ReadColor(oldx,i,&Rgb);
		RGBtoHSL(&Rgb,&Hsl);
		if(!ColorMatch(&Hsl,Condition))
			FailCount++;
		if(FailCount>(((Condition->HIGHT_MIN+Condition->HIGHT_MAX)>>2)>>ALLOW_FAIL_PER))
			break;	
	}
	Ymin=i;
	FailCount=0;
	
	for(i=oldy;i<IMG_Y+IMG_H;i++)
	{
		ReadColor(oldx,i,&Rgb);
		RGBtoHSL(&Rgb,&Hsl);
		if(!ColorMatch(&Hsl,Condition))
			FailCount++;
		if(FailCount>(((Condition->HIGHT_MIN+Condition->HIGHT_MAX)>>2)>>ALLOW_FAIL_PER))
			break;	
	}
	Ymax=i;
	FailCount=0;
	
	Resu->x	= (Xmin+Xmax)/2;
	Resu->y	= (Ymin+Ymax)/2;
	Resu->w	= Xmax-Xmin;
	Resu->h	= Ymax-Ymin;

	if(((Xmax-Xmin)>(Condition->WIDTH_MIN)) && ((Ymax-Ymin)>(Condition->HIGHT_MIN)) &&\
	   ((Xmax-Xmin)<(Condition->WIDTH_MAX)) && ((Ymax-Ymin)<(Condition->HIGHT_MAX)) )
		return 1;	
	else
		return 0;	
}

//single API, caculates the target x,y width and height
//return 1 for success,0 for failure
int Trace(const TARGET_CONDI *Condition,RESULT *Resu)
{
	unsigned int i;
	static unsigned int x0,y0,flag=0;
	static SEARCH_AREA Area={IMG_X,IMG_X+IMG_W,IMG_Y,IMG_Y+IMG_H};
	RESULT Result;	
	

	if(flag==0)
	{
		if(SearchCentre(&x0,&y0,Condition,&Area))
			flag=1;
		else
		{
			Area.X_Start= IMG_X	;
			Area.X_End  = IMG_X+IMG_W  ;
			Area.Y_Start= IMG_Y		;
			Area.Y_End  = IMG_Y+IMG_H;

			if(SearchCentre(&x0,&y0,Condition,&Area))	
			{
				flag=0;
				return 0;
			}	
		}
	}
	Result.x = x0;
	Result.y = y0;
	
	for(i=0;i<ITERATE_NUM;i++)
		Corrode(Result.x,Result.y,Condition,&Result);
		
	if(Corrode(Result.x,Result.y,Condition,&Result))
	{
		x0=Result.x;
		y0=Result.y;
		Resu->x=Result.x;
		Resu->y=Result.y;
		Resu->w=Result.w;
		Resu->h=Result.h;
		flag=1;

		Area.X_Start= Result.x - ((Result.w)>>1);
		Area.X_End  = Result.x + ((Result.w)>>1);
		Area.Y_Start= Result.y - ((Result.h)>>1);
		Area.Y_End  = Result.y + ((Result.h)>>1);


		return 1;
	}
	else
	{
		flag=0;
		return 0;
	}

}

int Trace1(const TARGET_CONDI *Condition,RESULT *Resu)
{
	static unsigned int x0,y0;
	static SEARCH_AREA FullFrameArea1={IMG_X,IMG_X+IMG_W-1,IMG_Y,IMG_Y+IMG_H-1};
	static SEARCH_AREA LastFrameArea1;
	static TRACE_STATE TraceState1 = TS_FIND_IN_FULL_FRAME;
	
	RESULT TempResult;	
	
	switch(TraceState1)
	{
		case TS_FIND_IN_FULL_FRAME:
			if(SearchCentre(&x0,&y0,Condition,&FullFrameArea1))
			{
				TraceState1 = TS_TRACING;
			}
			else
			{
				return 0;
			}
		break;
		
		case TS_FIND_IN_LAST_FRAME:
			if(SearchCentre(&x0,&y0,Condition,&LastFrameArea1))
			{
				TraceState1 = TS_TRACING;
			}
			else
			{
				TraceState1 = TS_FIND_IN_FULL_FRAME;
				return 0;
			}
		break;
		
		case TS_TRACING:
			break;
	}
	
	TempResult.x = x0;
	TempResult.y = y0;

	
	
	if(Corrode(TempResult.x,TempResult.y,Condition,&TempResult)==0)
	{
			TraceState1 = TS_FIND_IN_LAST_FRAME;
			return 0;
	}
			
	Resu->x=TempResult.x;
	Resu->y=TempResult.y;
	Resu->w=TempResult.w;
	Resu->h=TempResult.h;
	
	LastFrameArea1.X_Start= TempResult.x - ((TempResult.w)>>1);
	LastFrameArea1.X_End  = TempResult.x + ((TempResult.w)>>1);
	LastFrameArea1.Y_Start= TempResult.y - ((TempResult.h)>>1);
	LastFrameArea1.Y_End  = TempResult.y + ((TempResult.h)>>1);
	
	x0=TempResult.x;
	y0=TempResult.y;
	
	
	return 1;
}


char hue[5],sat[5],lum[5];
char r1[5],g1[5],b1[5];

void cam_cal()
{
	COLOR_RGB RGB;
  COLOR_HSL HSL;
	ReadColor(316,236,&RGB);
	r1[0]=RGB.red;
	g1[0]=RGB.green;
	b1[0]=RGB.blue;
	RGBtoHSL(&RGB,&HSL);
	hue[0]=HSL.hue;
	sat[0]=HSL.saturation;
	lum[0]=HSL.luminance;
	ReadColor(324,236,&RGB);
	r1[1]=RGB.red;
	g1[1]=RGB.green;
	b1[1]=RGB.blue;
	RGBtoHSL(&RGB,&HSL);
	hue[1]=HSL.hue;
	sat[1]=HSL.saturation;
	lum[1]=HSL.luminance;
	ReadColor(316,244,&RGB);
	r1[2]=RGB.red;
	g1[2]=RGB.green;
	b1[2]=RGB.blue;
	RGBtoHSL(&RGB,&HSL);
	hue[2]=HSL.hue;
	sat[2]=HSL.saturation;
	lum[2]=HSL.luminance;
	ReadColor(324,244,&RGB);
	r1[3]=RGB.red;
	g1[3]=RGB.green;
	b1[3]=RGB.blue;
	RGBtoHSL(&RGB,&HSL);
	hue[3]=HSL.hue;
	sat[3]=HSL.saturation;
	lum[3]=HSL.luminance;
	hue[4]=(hue[0]+hue[1]+hue[2]+hue[3])/4;
	sat[4]=(sat[0]+sat[1]+sat[2]+sat[3])/4;
	lum[4]=(lum[0]+lum[1]+lum[2]+lum[3])/4;
	//LCD_Fill(159,239,161,241,0xf800);
// 	ReadColor(165,245,&RGB);
// 	RGBtoHSL(&RGB,&HSL);
// 	hue[1]=HSL.hue;
// 	sat[1]=HSL.saturation;
// 	lum[1]=HSL.luminance;
// 	ReadColor(165,235,&RGB);
// 	RGBtoHSL(&RGB,&HSL);
// 	hue[2]=HSL.hue;
// 	sat[2]=HSL.saturation;
// 	lum[2]=HSL.luminance;
// 	ReadColor(155,235,&RGB);
// 	RGBtoHSL(&RGB,&HSL);
// 	hue[3]=HSL.hue;
// 	sat[3]=HSL.saturation;
// 	lum[3]=HSL.luminance;
// 	ReadColor(155,245,&RGB);
// 	RGBtoHSL(&RGB,&HSL);
// 	hue[4]=HSL.hue;
// 	sat[4]=HSL.saturation;
// 	lum[4]=HSL.luminance;
	
}


EasyTracer.h代码如下

#ifndef EASY_TRACER_H
#define EASY_TRACER_H

#define IMG_X 0	  //图片x坐标
#define IMG_Y 0	  //图片y坐标
#define IMG_W imgwidth/2 //图片宽度
#define IMG_H imgheight/2 //图片高度

#define ALLOW_FAIL_PER 3 //容错率,没1<<ALLOW_FAIL_PER个点允许出现一个错误点,容错率越大越容易识别,但错误率越大
#define ITERATE_NUM    10 //迭代次数,迭代次数越多识别越精确,但计算量越大

typedef struct{
    unsigned char  H_MIN;//目标最小色调
    unsigned char  H_MAX;//目标最大色调	
    
	unsigned char  S_MIN;//目标最小饱和度  
    unsigned char  S_MAX;//目标最大饱和度
	
	unsigned char  L_MIN;//目标最小亮度  
    unsigned char  L_MAX;//目标最大亮度
	
	unsigned int  WIDTH_MIN;//目标最小宽度
	unsigned int  HIGHT_MIN;//目标最小高度

	unsigned int  WIDTH_MAX;//目标最大宽度
	unsigned int  HIGHT_MAX;//目标最大高度

}TARGET_CONDI;//判定为的目标条件

typedef struct{
	unsigned int x;//目标的x坐标
	unsigned int y;//目标的y坐标
	unsigned int w;//目标的宽度
	unsigned int h;//目标的高度
}RESULT;//识别结果

typedef struct{
    unsigned char  red;             // [0,255]
    unsigned char  green;           // [0,255]
    unsigned char  blue;            // [0,255]
}COLOR_RGB;//RGB格式颜色

typedef struct{
    unsigned char hue;              // [0,240]
    unsigned char saturation;       // [0,240]
    unsigned char luminance;        // [0,240]
}COLOR_HSL;//HSL格式颜色

typedef struct{
    unsigned int X_Start;              
    unsigned int X_End;
	unsigned int Y_Start;              
    unsigned int Y_End;
}SEARCH_AREA;//区域

typedef enum{
	TS_FIND_IN_FULL_FRAME,//full frame 
	TS_FIND_IN_LAST_FRAME,//last frame
	TS_TRACING,// trace
}TRACE_STATE;

//唯一的API,用户将识别条件写入Condition指向的结构体中,该函数将返回目标的x,y坐标和长宽
//返回1识别成功,返回1识别失败
int Trace(const TARGET_CONDI *Condition,RESULT *Resu);

#endif

4.修改源码和组合源码(这部分原创)

这里有部分原创的代码:

if(ov_sta)//有帧中断更新?
	{
		
		if(Trace(&condition5,&result))//;  //run trace yellow
			{  
				
				u16 ledge,redge,uedge,dedge;//四个边沿
				lock1=1;//找到目标的标志位置1
			  xt1=result.x*2;
			  yt1=result.y*2;
				
			  wt1=result.w*2;
			  ht1=result.h*2;
			  /*draw the marker box*/
				u16 * lockcolor;
	      u16 c=lockcolor1 ;//找到目标的时候画黑色
	      lockcolor=&c;
		    LCD_Color_Fill(xt1-3,yt1-3,xt1+3,yt1+3,lockcolor);//画出一个中心点
				printf("catch_color:(%d,%d)\n",xt1,yt1);
			  ledge=xt1-(wt1>>1); 
			  redge=xt1+(wt1>>1);
			  uedge=yt1+(ht1>>1);
		  	dedge=yt1-(ht1>>1);
		  	LCD_Color_Fill(ledge,dedge,redge,dedge,lockcolor);//画出一个框
		  	LCD_Color_Fill(ledge,uedge,redge,uedge,lockcolor);
		  	LCD_Color_Fill(ledge,dedge,ledge,uedge,lockcolor);
		  	LCD_Color_Fill(redge,dedge,redge,uedge,lockcolor);
			}
			else
			{
				lock1=0;
				printf("can not catch_color\n");
			}
		
		LCD_Scan_Dir(U2D_L2R);		//从上到下,从左到右  
		if(lcddev.id==0X1963)//可以看出屏幕分辨率是 240*320或者是320*240
			LCD_Set_Window((lcddev.width-240)/2,(lcddev.height-320)/2,240,320);//将显示区域设置到屏幕中央
		else if(lcddev.id==0X5510||lcddev.id==0X5310)
			LCD_Set_Window((lcddev.width-320)/2,(lcddev.height-240)/2,320,240);//将显示区域设置到屏幕中央
		LCD_WriteRAM_Prepare();     //开始写入GRAM	
		OV7670_RRST=0;				//开始复位读指针 
		OV7670_RCK_L;
		OV7670_RCK_H;
		OV7670_RCK_L;
		OV7670_RRST=1;				//复位读指针结束 
		OV7670_RCK_H;
		for(j=0;j<76800;j++)//一个像素点是RGB565格式共16位320*240=76800
		{
			OV7670_RCK_L;
			color=OV7670_DATA;	//读数据
			OV7670_RCK_H; 
			color<<=8;  
			OV7670_RCK_L;
			color|=OV7670_DATA;	//读数据
			OV7670_RCK_H; 
			LCD->LCD_RAM=color;//将读取的到16位颜色值写到RAM中,一直循环整个屏幕的点  
			//可以从LCD_DrawPoint(u16 x,u16 y)理解怎么显示一个像素点来理解这句代码
      //扩展,可以将16位的color值通过这个for循环存进一个数组中去,这就是一张图片的信息了
		}   							  
 		ov_sta=0;					//清零帧中断标
		LCD_Scan_Dir(DFT_SCAN_DIR);	//恢复默认扫描方向
		LED1=!LED1;		
	} 

说明:以上就是我关键的代码,代码量大,另外也请读者尊重原创,尊重劳动成果,请下载我最后附录上的工程代码,工程代码注释详细,代码精简。

三、运行与调试

(1)我使用的是正点原子的探索者STM32F407ZGT6开发板,用的是OV7725三十万像素摄像头,检测到目标颜色的时候就会在周围画一个框,同时在PA9,PA10串口打印出 catch_color的字符串,编程到测试成功一共花了两天。效果不错。实时性也很好。
(2)运行机器之后,打开串口调试助手,参数如下图所示,打印出显示屏的型号,和捕捉目标色的情况,“can not catch_color”表示不能捕捉到目标,“catch_color:(170,328)”表示捕捉到目标颜色:黄色,并且黄色物体的中心坐标就是(170,328),在显示屏就是画点的地方。
在这里插入图片描述
在这里插入图片描述
根据以上调试结果,满足博文的要求。


总结

    代码是我进行平台匹配的,所以我的注释和分析就相当到位。正点原子的开发板和模块我都购置了一大批,一般情况我不设计电路硬件,也不画原理图,以软件设计为主,但是要定制硬件电路的话,我也有帮手搞定,一个人专注于自己擅长的东西才会越发强大。这个源码也有一个缺陷,是很多博主都没有说出来的,这个缺陷就是:这个颜色跟踪有很多限制,比如必须要有显示屏,因为需要在显示屏的寄存器中读取颜色来进行计算和跟踪,另外还跟踪算法也有局限性,也是要跟踪显示屏的寄存器相关的值,所以基本上除了相近的几款单片机,这套代码可移植性不高,不能单纯地移植到VC++6.0上去。所以打算用这个代码做高档的设计就算了,做个捡球机器人还是可以的或者是做个车牌识别。后期我会推出自己ESP32CAM无显示屏颜色跟踪的方案,摆脱显示屏的干扰。
    后期我会推出几个系列的基于国产芯片ESP8266和ESP32的物联网设计方案,涉及到摄像头图传技术,嵌入式图像处理,人工智能,机械臂,远程控制,MQTT,国产WS2812酷炫彩灯,ESP8266和ESP32智能小车,ESP8266和ESP32集群控制机器人等等,敬请期待,支持国产芯片,从购买我国产芯片开源软硬件电路方案开始。

最后附上本博文代码下载地址:https://www.cirmall.com/circuit/23930/
直接跳转
正点原子相关代码免费下载

版权声明:本文为CSDN博主「daodanjishui」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/niruxi0401/article/details/119685347

STM32库函数开发系列文章目录

第一篇:STM32F103ZET6单片机双串口互发程序设计与实现
第二篇:最简单DIY基于STM32单片机的蓝牙智能小车设计方案
第三篇:最简单DIY基于STM32F407探索者开发板的MPU6050陀螺仪姿态控制舵机程序
第四篇:最简单DIY基于STM32F407探索者开发板和PCA9685舵机控制模块的红外遥控机械臂控制程序
第五篇:注释最详细、代码最简单的STM32+摄像头+显示屏的颜色跟踪电路软硬件方案



前言

    daodanjishui物联网核心原创技术之注释最详细、代码最简单的STM32+摄像头+显示屏的颜色跟踪电路软硬件方案。
    市面上有各种开源STM32+摄像头+显示屏构成颜色跟踪系统,但是有复杂的有简单的,如果想快速入门STM32带显示屏和摄像头的颜色跟踪,这个方案会给你一个快捷高效的方案。


一、注释最详细、代码最简单的STM32+摄像头+显示屏的颜色跟踪电路软硬件方案是什么?

    我记得本栏的第三篇和第四篇博文的设计中大量使用了库函数和别人的开源代码,鲁迅先生的“拿来主义”表现的淋漓尽致,这也是STM32库函数开发的魅力所在,为了展示结合网络资源和各种资源呈现一个曾经热门的设计,所以诞生第五篇博文。
    这次的方案主要是:最简单的单片机摄像头颜色跟踪方案。下面请看全家福:
在这里插入图片描述

优酷视频演示地址:https://v.youku.com/v_show/id_XNTEzOTQxMTA5Mg==.html

直接观看

正点原子探索者开发板摄像头颜色跟踪

这个方案历史的起源是2012年阿莫论坛一个博主发布了一个颜色跟踪的代码,链接是:https://www.amobbs.com/thread-5499408-1-1.html

截图如下(铭记历史):
在这里插入图片描述
    差不多国内的所有基于单片机的颜色识别方案都用了这个内容,包括电路城里面很多卖家的电路。这个曾经火热的开源代码网站逐步走向了没落,这个网站现在有些服务好像是要花钱的了,取而代之的是正点原子开源论坛和野火开源论坛,这些论坛真的开源力度够,我也不是他们的托,事实说话。为了学习ESP8266,我曾经把正点原子关于ESP8266的帖子全部看完了,呵呵,有意思。这个颜色开源的代码也被拿来作为买卖商品,不过这也是无可厚非,想把这套代码在固定的硬件跑起来确实要花费不少功夫的,正点原子赠送的源码是没有F407驱动OV7725摄像头的,只有F1驱动OV7670的代码,我从F103的OV7670移植过去成为现在的F407驱动OV7725,正点原子配套的F407手册也只有OV2640摄像头的教程而已,OV2640摄像头没有FIFO意义不大,OV7725的性能要比OV7670的要好,所以我全选最好的。我使用的硬件平台是正点原子全家桶:探索者F407开发板,OV7725摄像头模块,ALIENTEK 4.3 TFTLCD,全部模块都是插上就用,原理图都是用的他们家的省事省力,这就是项目开头所说的:“注释最详细、代码最简单”的效果了,高校的学生只要有这个开发板,买了我的方案保证能快速掌握颜色跟踪,我的代码绝对让你物有所值。

二、使用步骤

1.准备硬件

1.1 正点原子探索者开发板一个,如果没有的话用STM32F407ZGT6核心板也可以的,现在美国ST公司禁售该芯片,估计以后这个芯片就被淘汰了。
1.2 OV7755带FIFO摄像头模块一个(正点原子出品)。
1.3 ALIENTEK 4.3 TFTLCD一个。

2.准备正点原子开源摄像头的代码

我采用库函数开发,所以代码直接使用该开发板配套的免费开源源码进行二次开发。源码来源:探索者光盘资料\A盘\4,程序源码\2,标准例程-库函数版本\实验35 摄像头实验

代码如下(示例):

#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "key.h"
#include "lcd.h"
#include "usmart.h"  
#include "usart2.h"  
#include "timer.h" 
#include "ov2640.h" 
#include "dcmi.h" 
//ALIENTEK 探索者STM32F407开发板 实验35
//摄像头 实验 -库函数版本
//技术支持:www.openedv.com
//淘宝店铺:http://eboard.taobao.com  
//广州市星翼电子科技有限公司  
//作者:正点原子 @ALIENTEK

u8 ov2640_mode=0;						//工作模式:0,RGB565模式;1,JPEG模式

#define jpeg_buf_size 31*1024  			//定义JPEG数据缓存jpeg_buf的大小(*4字节)
__align(4) u32 jpeg_buf[jpeg_buf_size];	//JPEG数据缓存buf
volatile u32 jpeg_data_len=0; 			//buf中的JPEG有效数据长度 
volatile u8 jpeg_data_ok=0;				//JPEG数据采集完成标志 
										//0,数据没有采集完;
										//1,数据采集完了,但是还没处理;
										//2,数据已经处理完成了,可以开始下一帧接收
//JPEG尺寸支持列表
const u16 jpeg_img_size_tbl[][2]=
{
	176,144,	//QCIF
	160,120,	//QQVGA
	352,288,	//CIF
	320,240,	//QVGA
	640,480,	//VGA
	800,600,	//SVGA
	1024,768,	//XGA
	1280,1024,	//SXGA
	1600,1200,	//UXGA
}; 
const u8*EFFECTS_TBL[7]={"Normal","Negative","B&W","Redish","Greenish","Bluish","Antique"};	//7种特效 
const u8*JPEG_SIZE_TBL[9]={"QCIF","QQVGA","CIF","QVGA","VGA","SVGA","XGA","SXGA","UXGA"};	//JPEG图片 9种尺寸 


//处理JPEG数据
//当采集完一帧JPEG数据后,调用此函数,切换JPEG BUF.开始下一帧采集.
void jpeg_data_process(void)
{
	if(ov2640_mode)//只有在JPEG格式下,才需要做处理.
	{
		if(jpeg_data_ok==0)	//jpeg数据还未采集完?
		{	
			DMA_Cmd(DMA2_Stream1, DISABLE);//停止当前传输 
			while (DMA_GetCmdStatus(DMA2_Stream1) != DISABLE){}//等待DMA2_Stream1可配置  
			jpeg_data_len=jpeg_buf_size-DMA_GetCurrDataCounter(DMA2_Stream1);//得到此次数据传输的长度
				
			jpeg_data_ok=1; 				//标记JPEG数据采集完按成,等待其他函数处理
		}
		if(jpeg_data_ok==2)	//上一次的jpeg数据已经被处理了
		{
			DMA2_Stream1->NDTR=jpeg_buf_size;	
			DMA_SetCurrDataCounter(DMA2_Stream1,jpeg_buf_size);//传输长度为jpeg_buf_size*4字节
			DMA_Cmd(DMA2_Stream1, ENABLE);			//重新传输
			jpeg_data_ok=0;						//标记数据未采集
		}
	}
} 
//JPEG测试
//JPEG数据,通过串口2发送给电脑.
void jpeg_test(void)
{
	u32 i; 
	u8 *p;
	u8 key;
	u8 effect=0,saturation=2,contrast=2;
	u8 size=3;		//默认是QVGA 320*240尺寸
	u8 msgbuf[15];	//消息缓存区 
	LCD_Clear(WHITE);
  POINT_COLOR=RED; 
	LCD_ShowString(30,50,200,16,16,"ALIENTEK STM32F4");
	LCD_ShowString(30,70,200,16,16,"OV2640 JPEG Mode");
	LCD_ShowString(30,100,200,16,16,"KEY0:Contrast");			//对比度
	LCD_ShowString(30,120,200,16,16,"KEY1:Saturation"); 		//色彩饱和度
	LCD_ShowString(30,140,200,16,16,"KEY2:Effects"); 			//特效 
	LCD_ShowString(30,160,200,16,16,"KEY_UP:Size");				//分辨率设置 
	sprintf((char*)msgbuf,"JPEG Size:%s",JPEG_SIZE_TBL[size]);
	LCD_ShowString(30,180,200,16,16,msgbuf);					//显示当前JPEG分辨率
	
 	OV2640_JPEG_Mode();		//JPEG模式
	My_DCMI_Init();			//DCMI配置
	DCMI_DMA_Init((u32)&jpeg_buf,jpeg_buf_size,DMA_MemoryDataSize_Word,DMA_MemoryInc_Enable);//DCMI DMA配置   
	OV2640_OutSize_Set(jpeg_img_size_tbl[size][0],jpeg_img_size_tbl[size][1]);//设置输出尺寸 
	DCMI_Start(); 		//启动传输
	while(1)
	{
		if(jpeg_data_ok==1)	//已经采集完一帧图像了
		{  
			p=(u8*)jpeg_buf;
			LCD_ShowString(30,210,210,16,16,"Sending JPEG data..."); //提示正在传输数据
			for(i=0;i<jpeg_data_len*4;i++)		//dma传输1次等于4字节,所以乘以4.
			{
        while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET);	//循环发送,直到发送完毕  		
				USART_SendData(USART2,p[i]); 
				key=KEY_Scan(0); 
				if(key)break;
			} 
			if(key)	//有按键按下,需要处理
			{  
				LCD_ShowString(30,210,210,16,16,"Quit Sending data   ");//提示退出数据传输
				switch(key)
				{				    
					case KEY0_PRES:	//对比度设置
						contrast++;
						if(contrast>4)contrast=0;
						OV2640_Contrast(contrast);
						sprintf((char*)msgbuf,"Contrast:%d",(signed char)contrast-2);
						break;
					case KEY1_PRES:	//饱和度Saturation
						saturation++;
						if(saturation>4)saturation=0;
						OV2640_Color_Saturation(saturation);
						sprintf((char*)msgbuf,"Saturation:%d",(signed char)saturation-2);
						break;
					case KEY2_PRES:	//特效设置				 
						effect++;
						if(effect>6)effect=0;
						OV2640_Special_Effects(effect);//设置特效
						sprintf((char*)msgbuf,"%s",EFFECTS_TBL[effect]);
						break;
					case WKUP_PRES:	//JPEG输出尺寸设置   
						size++;  
						if(size>8)size=0;   
						OV2640_OutSize_Set(jpeg_img_size_tbl[size][0],jpeg_img_size_tbl[size][1]);//设置输出尺寸  
						sprintf((char*)msgbuf,"JPEG Size:%s",JPEG_SIZE_TBL[size]);
						break;
				}
				LCD_Fill(30,180,239,190+16,WHITE);
				LCD_ShowString(30,180,210,16,16,msgbuf);//显示提示内容
				delay_ms(800); 				  
			}else LCD_ShowString(30,210,210,16,16,"Send data complete!!");//提示传输结束设置 
			jpeg_data_ok=2;	//标记jpeg数据处理完了,可以让DMA去采集下一帧了.
		}		
	}    
} 
//RGB565测试
//RGB数据直接显示在LCD上面
void rgb565_test(void)
{ 
	u8 key;
	u8 effect=0,saturation=2,contrast=2;
	u8 scale=1;		//默认是全尺寸缩放
	u8 msgbuf[15];	//消息缓存区 
	LCD_Clear(WHITE);
    POINT_COLOR=RED; 
	LCD_ShowString(30,50,200,16,16,"ALIENTEK STM32F4");
	LCD_ShowString(30,70,200,16,16,"OV2640 RGB565 Mode");
	
	LCD_ShowString(30,100,200,16,16,"KEY0:Contrast");			//对比度
	LCD_ShowString(30,130,200,16,16,"KEY1:Saturation"); 		//色彩饱和度
	LCD_ShowString(30,150,200,16,16,"KEY2:Effects"); 			//特效 
	LCD_ShowString(30,170,200,16,16,"KEY_UP:FullSize/Scale");	//1:1尺寸(显示真实尺寸)/全尺寸缩放
	
	OV2640_RGB565_Mode();	//RGB565模式
	My_DCMI_Init();			//DCMI配置
	DCMI_DMA_Init((u32)&LCD->LCD_RAM,1,DMA_MemoryDataSize_HalfWord,DMA_MemoryInc_Disable);//DCMI DMA配置  
 	OV2640_OutSize_Set(lcddev.width,lcddev.height); 
	DCMI_Start(); 		//启动传输
	while(1)
	{ 
		key=KEY_Scan(0); 
		if(key)
		{ 
			DCMI_Stop(); //停止显示
			switch(key)
			{				    
				case KEY0_PRES:	//对比度设置
					contrast++;
					if(contrast>4)contrast=0;
					OV2640_Contrast(contrast);
					sprintf((char*)msgbuf,"Contrast:%d",(signed char)contrast-2);
					break;
				case KEY1_PRES:	//饱和度Saturation
					saturation++;
					if(saturation>4)saturation=0;
					OV2640_Color_Saturation(saturation);
					sprintf((char*)msgbuf,"Saturation:%d",(signed char)saturation-2);
					break;
				case KEY2_PRES:	//特效设置				 
					effect++;
					if(effect>6)effect=0;
					OV2640_Special_Effects(effect);//设置特效
					sprintf((char*)msgbuf,"%s",EFFECTS_TBL[effect]);
					break;
				case WKUP_PRES:	//1:1尺寸(显示真实尺寸)/缩放	    
					scale=!scale;  
					if(scale==0)
					{
						OV2640_ImageWin_Set((1600-lcddev.width)/2,(1200-lcddev.height)/2,lcddev.width,lcddev.height);//1:1真实尺寸
						OV2640_OutSize_Set(lcddev.width,lcddev.height); 
						sprintf((char*)msgbuf,"Full Size 1:1");
					}else 
					{
						OV2640_ImageWin_Set(0,0,1600,1200);				//全尺寸缩放
						OV2640_OutSize_Set(lcddev.width,lcddev.height); 
						sprintf((char*)msgbuf,"Scale");
					}
					break;
			}
			LCD_ShowString(30,50,210,16,16,msgbuf);//显示提示内容
			delay_ms(800); 
			DCMI_Start();//重新开始传输
		} 
		delay_ms(10);		
	}    
} 
int main(void)
{ 
	u8 key;
	u8 t;
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2
	delay_init(168);  //初始化延时函数
	uart_init(115200);		//初始化串口波特率为115200
	usart2_init(42,115200);		//初始化串口2波特率为115200
	LED_Init();					//初始化LED 
 	LCD_Init();					//LCD初始化  
 	KEY_Init();					//按键初始化 
	TIM3_Int_Init(10000-1,8400-1);//10Khz计数,1秒钟中断一次
	
 	usmart_dev.init(84);		//初始化USMART
 	POINT_COLOR=RED;//设置字体为红色 
	LCD_ShowString(30,50,200,16,16,"Explorer STM32F4");	
	LCD_ShowString(30,70,200,16,16,"OV2640 TEST");	
	LCD_ShowString(30,90,200,16,16,"ATOM@ALIENTEK");
	LCD_ShowString(30,110,200,16,16,"2014/5/14");  	 
	while(OV2640_Init())//初始化OV2640
	{
		LCD_ShowString(30,130,240,16,16,"OV2640 ERR");
		delay_ms(200);
	    LCD_Fill(30,130,239,170,WHITE);
		delay_ms(200);
	}
	LCD_ShowString(30,130,200,16,16,"OV2640 OK");  	  
 	while(1)
	{	
		key=KEY_Scan(0);
		if(key==KEY0_PRES)			//RGB565模式
		{
			ov2640_mode=0;   
			break;
		}else if(key==KEY1_PRES)	//JPEG模式
		{
			ov2640_mode=1;
			break;
		}
		t++; 									  
		if(t==100)LCD_ShowString(30,150,230,16,16,"KEY0:RGB565  KEY1:JPEG"); //闪烁显示提示信息
 		if(t==200)
		{	
			LCD_Fill(30,150,210,150+16,WHITE);
			t=0; 
		}
		delay_ms(5);	  
	}
	if(ov2640_mode)jpeg_test();
	else rgb565_test(); 
}

3.准备阿莫论坛的颜色跟踪代码

EasyTracer.c代码如下

#include "EasyTracer.h"
//#include "MT9M111.h"
#define imgwidth 640
#define imgheight 480

#define min3v(v1, v2, v3)   ((v1)>(v2)? ((v2)>(v3)?(v3):(v2)):((v1)>(v3)?(v3):(v1)))
#define max3v(v1, v2, v3)   ((v1)<(v2)? ((v2)<(v3)?(v3):(v2)):((v1)<(v3)?(v3):(v1)))

#define GUI_ReadBit16Point   LCD_ReadPoint


//读取RBG格式颜色,唯一需要移植的函数
extern unsigned short GUI_ReadBit16Point(unsigned short x,unsigned short y);
static void ReadColor(unsigned int x,unsigned int y,COLOR_RGB *Rgb)
{
	unsigned short C16;

	C16 = GUI_ReadBit16Point(2*x,2*y);

	Rgb->red   =	 (unsigned char)((C16&0xf800)>>8);
	Rgb->green =	 (unsigned char)((C16&0x07e0)>>3);
	Rgb->blue  =     (unsigned char)((C16&0x001f)<<3);
}



//RGB转HSL
static void RGBtoHSL(const COLOR_RGB *Rgb, COLOR_HSL *Hsl)
{
    int h,s,l,maxVal,minVal,difVal;
	int r  = Rgb->red;
	int g  = Rgb->green;
  int b  = Rgb->blue;
	
	maxVal = max3v(r, g, b);
	minVal = min3v(r, g, b);
	
	difVal = maxVal-minVal;
	
	//计算亮度
    l = (maxVal+minVal)*240/255/2;
	
	if(maxVal == minVal)//若r=g=b
	{
		h = 0; 
		s = 0;
	}
	else
	{
		//计算色调
		if(maxVal==r)
		{
			if(g>=b)
				h = 40*(g-b)/(difVal);
			else
				h = 40*(g-b)/(difVal) + 240;
		}
		else if(maxVal==g)
			h = 40*(b-r)/(difVal) + 80;
		else if(maxVal==b)
			h = 40*(r-g)/(difVal) + 160;
		//计算饱和度
		if(l == 0)
			s = 0;
		else if(l<=120)
			s = (difVal)*240/(maxVal+minVal);
		else
			s = (difVal)*240/(480 - (maxVal+minVal));
	}
    Hsl->hue =        (unsigned char)(((h>240)? 240 : ((h<0)?0:h)));
    Hsl->saturation = (unsigned char)(((s>240)? 240 : ((s<0)?0:s)));
    Hsl->luminance =  (unsigned char)(((l>240)? 240 : ((l<0)?0:l)));
}

//匹配颜色
static int ColorMatch(const COLOR_HSL *Hsl,const TARGET_CONDI *Condition)
{
	if( 
		Hsl->hue		>=	Condition->H_MIN &&
		Hsl->hue		<=	Condition->H_MAX &&
		Hsl->saturation	>=	Condition->S_MIN &&
		Hsl->saturation	<=   Condition->S_MAX &&
		Hsl->luminance	>=	Condition->L_MIN &&
		Hsl->luminance	<=   Condition->L_MAX 
    )
		return 1;
	else
		return 0;
}

//搜索腐蚀中心
static int SearchCentre(unsigned int *x,unsigned int *y,const TARGET_CONDI *Condition,const SEARCH_AREA *Area)
{
	unsigned int SpaceX,SpaceY,i,j,k,FailCount=0;
	COLOR_RGB Rgb;
	COLOR_HSL Hsl;
	
	SpaceX = Condition->WIDTH_MIN/3;
	SpaceY = Condition->HIGHT_MIN/3;

	for(i=Area->Y_Start;i<Area->Y_End;i+=SpaceY)
	{
		for(j=Area->X_Start;j<Area->X_End;j+=SpaceX)
		{
			FailCount=0;
			for(k=0;k<SpaceX+SpaceY;k++)
			{
				if(k<SpaceX)
					ReadColor(j+k,i+SpaceY/2,&Rgb);
				else
					ReadColor(j+SpaceX/2,i+(k-SpaceX),&Rgb);
				RGBtoHSL(&Rgb,&Hsl);
				
				if(!ColorMatch(&Hsl,Condition))
					FailCount++;
				if(FailCount>((SpaceX+SpaceY)>>ALLOW_FAIL_PER))
					break;
			}
			if(k==SpaceX+SpaceY)
			{
				*x = j+SpaceX/2;
				*y = i+SpaceY/2;
				return 1;
			}
		}
	}
	return 0;
}

//从腐蚀中心向外腐蚀,得到新的腐蚀中心
static int Corrode(unsigned int oldx,unsigned int oldy,const TARGET_CONDI *Condition,RESULT *Resu)
{
	unsigned int Xmin,Xmax,Ymin,Ymax,i,FailCount=0;
	COLOR_RGB Rgb;
	COLOR_HSL Hsl;
	
	for(i=oldx;i>IMG_X;i--)
	{
		ReadColor(i,oldy,&Rgb);
		RGBtoHSL(&Rgb,&Hsl);
		if(!ColorMatch(&Hsl,Condition))
			FailCount++;
		if(FailCount>(((Condition->WIDTH_MIN+Condition->WIDTH_MAX)>>2)>>ALLOW_FAIL_PER))
			break;	
	}
	Xmin=i;
	FailCount=0;
	
	for(i=oldx;i<IMG_X+IMG_W;i++)
	{
		ReadColor(i,oldy,&Rgb);
		RGBtoHSL(&Rgb,&Hsl);
		if(!ColorMatch(&Hsl,Condition))
			FailCount++;
		if(FailCount>(((Condition->WIDTH_MIN+Condition->WIDTH_MAX)>>2)>>ALLOW_FAIL_PER))
			break;	
	}
	Xmax=i;
	FailCount=0;
	
	for(i=oldy;i>IMG_Y;i--)
	{
		ReadColor(oldx,i,&Rgb);
		RGBtoHSL(&Rgb,&Hsl);
		if(!ColorMatch(&Hsl,Condition))
			FailCount++;
		if(FailCount>(((Condition->HIGHT_MIN+Condition->HIGHT_MAX)>>2)>>ALLOW_FAIL_PER))
			break;	
	}
	Ymin=i;
	FailCount=0;
	
	for(i=oldy;i<IMG_Y+IMG_H;i++)
	{
		ReadColor(oldx,i,&Rgb);
		RGBtoHSL(&Rgb,&Hsl);
		if(!ColorMatch(&Hsl,Condition))
			FailCount++;
		if(FailCount>(((Condition->HIGHT_MIN+Condition->HIGHT_MAX)>>2)>>ALLOW_FAIL_PER))
			break;	
	}
	Ymax=i;
	FailCount=0;
	
	Resu->x	= (Xmin+Xmax)/2;
	Resu->y	= (Ymin+Ymax)/2;
	Resu->w	= Xmax-Xmin;
	Resu->h	= Ymax-Ymin;

	if(((Xmax-Xmin)>(Condition->WIDTH_MIN)) && ((Ymax-Ymin)>(Condition->HIGHT_MIN)) &&\
	   ((Xmax-Xmin)<(Condition->WIDTH_MAX)) && ((Ymax-Ymin)<(Condition->HIGHT_MAX)) )
		return 1;	
	else
		return 0;	
}

//single API, caculates the target x,y width and height
//return 1 for success,0 for failure
int Trace(const TARGET_CONDI *Condition,RESULT *Resu)
{
	unsigned int i;
	static unsigned int x0,y0,flag=0;
	static SEARCH_AREA Area={IMG_X,IMG_X+IMG_W,IMG_Y,IMG_Y+IMG_H};
	RESULT Result;	
	

	if(flag==0)
	{
		if(SearchCentre(&x0,&y0,Condition,&Area))
			flag=1;
		else
		{
			Area.X_Start= IMG_X	;
			Area.X_End  = IMG_X+IMG_W  ;
			Area.Y_Start= IMG_Y		;
			Area.Y_End  = IMG_Y+IMG_H;

			if(SearchCentre(&x0,&y0,Condition,&Area))	
			{
				flag=0;
				return 0;
			}	
		}
	}
	Result.x = x0;
	Result.y = y0;
	
	for(i=0;i<ITERATE_NUM;i++)
		Corrode(Result.x,Result.y,Condition,&Result);
		
	if(Corrode(Result.x,Result.y,Condition,&Result))
	{
		x0=Result.x;
		y0=Result.y;
		Resu->x=Result.x;
		Resu->y=Result.y;
		Resu->w=Result.w;
		Resu->h=Result.h;
		flag=1;

		Area.X_Start= Result.x - ((Result.w)>>1);
		Area.X_End  = Result.x + ((Result.w)>>1);
		Area.Y_Start= Result.y - ((Result.h)>>1);
		Area.Y_End  = Result.y + ((Result.h)>>1);


		return 1;
	}
	else
	{
		flag=0;
		return 0;
	}

}

int Trace1(const TARGET_CONDI *Condition,RESULT *Resu)
{
	static unsigned int x0,y0;
	static SEARCH_AREA FullFrameArea1={IMG_X,IMG_X+IMG_W-1,IMG_Y,IMG_Y+IMG_H-1};
	static SEARCH_AREA LastFrameArea1;
	static TRACE_STATE TraceState1 = TS_FIND_IN_FULL_FRAME;
	
	RESULT TempResult;	
	
	switch(TraceState1)
	{
		case TS_FIND_IN_FULL_FRAME:
			if(SearchCentre(&x0,&y0,Condition,&FullFrameArea1))
			{
				TraceState1 = TS_TRACING;
			}
			else
			{
				return 0;
			}
		break;
		
		case TS_FIND_IN_LAST_FRAME:
			if(SearchCentre(&x0,&y0,Condition,&LastFrameArea1))
			{
				TraceState1 = TS_TRACING;
			}
			else
			{
				TraceState1 = TS_FIND_IN_FULL_FRAME;
				return 0;
			}
		break;
		
		case TS_TRACING:
			break;
	}
	
	TempResult.x = x0;
	TempResult.y = y0;

	
	
	if(Corrode(TempResult.x,TempResult.y,Condition,&TempResult)==0)
	{
			TraceState1 = TS_FIND_IN_LAST_FRAME;
			return 0;
	}
			
	Resu->x=TempResult.x;
	Resu->y=TempResult.y;
	Resu->w=TempResult.w;
	Resu->h=TempResult.h;
	
	LastFrameArea1.X_Start= TempResult.x - ((TempResult.w)>>1);
	LastFrameArea1.X_End  = TempResult.x + ((TempResult.w)>>1);
	LastFrameArea1.Y_Start= TempResult.y - ((TempResult.h)>>1);
	LastFrameArea1.Y_End  = TempResult.y + ((TempResult.h)>>1);
	
	x0=TempResult.x;
	y0=TempResult.y;
	
	
	return 1;
}


char hue[5],sat[5],lum[5];
char r1[5],g1[5],b1[5];

void cam_cal()
{
	COLOR_RGB RGB;
  COLOR_HSL HSL;
	ReadColor(316,236,&RGB);
	r1[0]=RGB.red;
	g1[0]=RGB.green;
	b1[0]=RGB.blue;
	RGBtoHSL(&RGB,&HSL);
	hue[0]=HSL.hue;
	sat[0]=HSL.saturation;
	lum[0]=HSL.luminance;
	ReadColor(324,236,&RGB);
	r1[1]=RGB.red;
	g1[1]=RGB.green;
	b1[1]=RGB.blue;
	RGBtoHSL(&RGB,&HSL);
	hue[1]=HSL.hue;
	sat[1]=HSL.saturation;
	lum[1]=HSL.luminance;
	ReadColor(316,244,&RGB);
	r1[2]=RGB.red;
	g1[2]=RGB.green;
	b1[2]=RGB.blue;
	RGBtoHSL(&RGB,&HSL);
	hue[2]=HSL.hue;
	sat[2]=HSL.saturation;
	lum[2]=HSL.luminance;
	ReadColor(324,244,&RGB);
	r1[3]=RGB.red;
	g1[3]=RGB.green;
	b1[3]=RGB.blue;
	RGBtoHSL(&RGB,&HSL);
	hue[3]=HSL.hue;
	sat[3]=HSL.saturation;
	lum[3]=HSL.luminance;
	hue[4]=(hue[0]+hue[1]+hue[2]+hue[3])/4;
	sat[4]=(sat[0]+sat[1]+sat[2]+sat[3])/4;
	lum[4]=(lum[0]+lum[1]+lum[2]+lum[3])/4;
	//LCD_Fill(159,239,161,241,0xf800);
// 	ReadColor(165,245,&RGB);
// 	RGBtoHSL(&RGB,&HSL);
// 	hue[1]=HSL.hue;
// 	sat[1]=HSL.saturation;
// 	lum[1]=HSL.luminance;
// 	ReadColor(165,235,&RGB);
// 	RGBtoHSL(&RGB,&HSL);
// 	hue[2]=HSL.hue;
// 	sat[2]=HSL.saturation;
// 	lum[2]=HSL.luminance;
// 	ReadColor(155,235,&RGB);
// 	RGBtoHSL(&RGB,&HSL);
// 	hue[3]=HSL.hue;
// 	sat[3]=HSL.saturation;
// 	lum[3]=HSL.luminance;
// 	ReadColor(155,245,&RGB);
// 	RGBtoHSL(&RGB,&HSL);
// 	hue[4]=HSL.hue;
// 	sat[4]=HSL.saturation;
// 	lum[4]=HSL.luminance;
	
}


EasyTracer.h代码如下

#ifndef EASY_TRACER_H
#define EASY_TRACER_H

#define IMG_X 0	  //图片x坐标
#define IMG_Y 0	  //图片y坐标
#define IMG_W imgwidth/2 //图片宽度
#define IMG_H imgheight/2 //图片高度

#define ALLOW_FAIL_PER 3 //容错率,没1<<ALLOW_FAIL_PER个点允许出现一个错误点,容错率越大越容易识别,但错误率越大
#define ITERATE_NUM    10 //迭代次数,迭代次数越多识别越精确,但计算量越大

typedef struct{
    unsigned char  H_MIN;//目标最小色调
    unsigned char  H_MAX;//目标最大色调	
    
	unsigned char  S_MIN;//目标最小饱和度  
    unsigned char  S_MAX;//目标最大饱和度
	
	unsigned char  L_MIN;//目标最小亮度  
    unsigned char  L_MAX;//目标最大亮度
	
	unsigned int  WIDTH_MIN;//目标最小宽度
	unsigned int  HIGHT_MIN;//目标最小高度

	unsigned int  WIDTH_MAX;//目标最大宽度
	unsigned int  HIGHT_MAX;//目标最大高度

}TARGET_CONDI;//判定为的目标条件

typedef struct{
	unsigned int x;//目标的x坐标
	unsigned int y;//目标的y坐标
	unsigned int w;//目标的宽度
	unsigned int h;//目标的高度
}RESULT;//识别结果

typedef struct{
    unsigned char  red;             // [0,255]
    unsigned char  green;           // [0,255]
    unsigned char  blue;            // [0,255]
}COLOR_RGB;//RGB格式颜色

typedef struct{
    unsigned char hue;              // [0,240]
    unsigned char saturation;       // [0,240]
    unsigned char luminance;        // [0,240]
}COLOR_HSL;//HSL格式颜色

typedef struct{
    unsigned int X_Start;              
    unsigned int X_End;
	unsigned int Y_Start;              
    unsigned int Y_End;
}SEARCH_AREA;//区域

typedef enum{
	TS_FIND_IN_FULL_FRAME,//full frame 
	TS_FIND_IN_LAST_FRAME,//last frame
	TS_TRACING,// trace
}TRACE_STATE;

//唯一的API,用户将识别条件写入Condition指向的结构体中,该函数将返回目标的x,y坐标和长宽
//返回1识别成功,返回1识别失败
int Trace(const TARGET_CONDI *Condition,RESULT *Resu);

#endif

4.修改源码和组合源码(这部分原创)

这里有部分原创的代码:

if(ov_sta)//有帧中断更新?
	{
		
		if(Trace(&condition5,&result))//;  //run trace yellow
			{  
				
				u16 ledge,redge,uedge,dedge;//四个边沿
				lock1=1;//找到目标的标志位置1
			  xt1=result.x*2;
			  yt1=result.y*2;
				
			  wt1=result.w*2;
			  ht1=result.h*2;
			  /*draw the marker box*/
				u16 * lockcolor;
	      u16 c=lockcolor1 ;//找到目标的时候画黑色
	      lockcolor=&c;
		    LCD_Color_Fill(xt1-3,yt1-3,xt1+3,yt1+3,lockcolor);//画出一个中心点
				printf("catch_color:(%d,%d)\n",xt1,yt1);
			  ledge=xt1-(wt1>>1); 
			  redge=xt1+(wt1>>1);
			  uedge=yt1+(ht1>>1);
		  	dedge=yt1-(ht1>>1);
		  	LCD_Color_Fill(ledge,dedge,redge,dedge,lockcolor);//画出一个框
		  	LCD_Color_Fill(ledge,uedge,redge,uedge,lockcolor);
		  	LCD_Color_Fill(ledge,dedge,ledge,uedge,lockcolor);
		  	LCD_Color_Fill(redge,dedge,redge,uedge,lockcolor);
			}
			else
			{
				lock1=0;
				printf("can not catch_color\n");
			}
		
		LCD_Scan_Dir(U2D_L2R);		//从上到下,从左到右  
		if(lcddev.id==0X1963)//可以看出屏幕分辨率是 240*320或者是320*240
			LCD_Set_Window((lcddev.width-240)/2,(lcddev.height-320)/2,240,320);//将显示区域设置到屏幕中央
		else if(lcddev.id==0X5510||lcddev.id==0X5310)
			LCD_Set_Window((lcddev.width-320)/2,(lcddev.height-240)/2,320,240);//将显示区域设置到屏幕中央
		LCD_WriteRAM_Prepare();     //开始写入GRAM	
		OV7670_RRST=0;				//开始复位读指针 
		OV7670_RCK_L;
		OV7670_RCK_H;
		OV7670_RCK_L;
		OV7670_RRST=1;				//复位读指针结束 
		OV7670_RCK_H;
		for(j=0;j<76800;j++)//一个像素点是RGB565格式共16位320*240=76800
		{
			OV7670_RCK_L;
			color=OV7670_DATA;	//读数据
			OV7670_RCK_H; 
			color<<=8;  
			OV7670_RCK_L;
			color|=OV7670_DATA;	//读数据
			OV7670_RCK_H; 
			LCD->LCD_RAM=color;//将读取的到16位颜色值写到RAM中,一直循环整个屏幕的点  
			//可以从LCD_DrawPoint(u16 x,u16 y)理解怎么显示一个像素点来理解这句代码
      //扩展,可以将16位的color值通过这个for循环存进一个数组中去,这就是一张图片的信息了
		}   							  
 		ov_sta=0;					//清零帧中断标
		LCD_Scan_Dir(DFT_SCAN_DIR);	//恢复默认扫描方向
		LED1=!LED1;		
	} 

说明:以上就是我关键的代码,代码量大,另外也请读者尊重原创,尊重劳动成果,请下载我最后附录上的工程代码,工程代码注释详细,代码精简。

三、运行与调试

(1)我使用的是正点原子的探索者STM32F407ZGT6开发板,用的是OV7725三十万像素摄像头,检测到目标颜色的时候就会在周围画一个框,同时在PA9,PA10串口打印出 catch_color的字符串,编程到测试成功一共花了两天。效果不错。实时性也很好。
(2)运行机器之后,打开串口调试助手,参数如下图所示,打印出显示屏的型号,和捕捉目标色的情况,“can not catch_color”表示不能捕捉到目标,“catch_color:(170,328)”表示捕捉到目标颜色:黄色,并且黄色物体的中心坐标就是(170,328),在显示屏就是画点的地方。
在这里插入图片描述
在这里插入图片描述
根据以上调试结果,满足博文的要求。


总结

    代码是我进行平台匹配的,所以我的注释和分析就相当到位。正点原子的开发板和模块我都购置了一大批,一般情况我不设计电路硬件,也不画原理图,以软件设计为主,但是要定制硬件电路的话,我也有帮手搞定,一个人专注于自己擅长的东西才会越发强大。这个源码也有一个缺陷,是很多博主都没有说出来的,这个缺陷就是:这个颜色跟踪有很多限制,比如必须要有显示屏,因为需要在显示屏的寄存器中读取颜色来进行计算和跟踪,另外还跟踪算法也有局限性,也是要跟踪显示屏的寄存器相关的值,所以基本上除了相近的几款单片机,这套代码可移植性不高,不能单纯地移植到VC++6.0上去。所以打算用这个代码做高档的设计就算了,做个捡球机器人还是可以的或者是做个车牌识别。后期我会推出自己ESP32CAM无显示屏颜色跟踪的方案,摆脱显示屏的干扰。
    后期我会推出几个系列的基于国产芯片ESP8266和ESP32的物联网设计方案,涉及到摄像头图传技术,嵌入式图像处理,人工智能,机械臂,远程控制,MQTT,国产WS2812酷炫彩灯,ESP8266和ESP32智能小车,ESP8266和ESP32集群控制机器人等等,敬请期待,支持国产芯片,从购买我国产芯片开源软硬件电路方案开始。

最后附上本博文代码下载地址:https://www.cirmall.com/circuit/23930/
直接跳转
正点原子相关代码免费下载

版权声明:本文为CSDN博主「daodanjishui」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/niruxi0401/article/details/119685347

daodanjishui

我还没有学会写个人说明!

暂无评论

相关推荐

W806-ADC-PWM-TIM尝鲜

本文使用环境: 电脑:windows10 主控:W806(240MHZ) 编译环境:平头哥的CDK 注意:本文默认已经搭建好平台。 前言 写这