图像处理 小车巡线 识别

2019-08-23 13:44:33 weixin_44702440 阅读数 2285
     断断续续学openmv和arduino一个月,所以没什么编程基础,所以这个写的比较简单,只希望作为引子记录一下第一个小实验然后好好把视觉一只学下去,虽然现在看来很简单,但入门时可谓处处碰壁,做出来还是挺开心。话不多说,开始介绍吧。

所需基本知识:串口通讯,字符串处理,电机控制,识别色块,arduino编程基础(C/C++),会调用openmvIde里的一些函数,会配置和使用openmv

材料清单;
6v电机2;
openmv4;
arduinomega2560;
l298n电机驱动
1;
小车底座;
牛眼轮1;
普通轮胎
2;
电源;
蓝色物料;

思路: 通过UART在openmv和arduino之间通信,openmv识别物料并确定物料中心坐标发送给arduino,arduino接收并读取字符串,然后设定一个阈值(中心点的值),用if else对阈值进行比较 ,大于阈值也就是在右侧,小于阈值在左侧。

附上接线图(不含电源) :openmv p4脚(TX)接arduino 10脚(RX),gnd接gnd(共地),l298n 逻辑输入口(1-4)分别接arduino6,7,8,9脚
使能端接5,11脚
在这里插入图片描述

arduino程序

#include <SoftwareSerial.h>
SoftwareSerial softSerial(10,11); //定义软串口,rx为10号端口,tx为11号端口
int x;
int input1 = 6;  //定义输出引脚
int input2 = 7; //定义输出引脚
int input3 = 8; //定义输出引脚
int input4 = 9; //定义输出引脚
int ENA=5;//定义使能端引脚(下同)
int ENB=11;




void setup()
{
  pinMode(input1,OUTPUT);//下列配置各引脚为输出模式
pinMode(input2,OUTPUT);
pinMode(input3,OUTPUT);
pinMode(input4,OUTPUT);
pinMode(ENA,OUTPUT);
pinMode(ENB,OUTPUT);
  softSerial.begin(9600); //初始化虚拟串口
  Serial.begin(9600); //初始化硬串口
  
}
String A_String = "";//定义用来存数据的字符串

void loop()
{
  
  
  
  if (softSerial.available() > 0) //判断软串口是否接收到openmv数据,然后读取,然后在串口监视器打印
  {
    if(softSerial.peek() != '\n')
    {
      A_String += (char)softSerial.read();//把串口读的单个字符逐加到字符串
    }
    else
    {
      softSerial.read();
     
      
       x=A_String.toInt();//把字符串转成int类型存放
   
       if(x>80)//坐标和阈值进行判断
       {
        Serial.println("1");//串口打印数字1
          Serial.println(x);//串口打印坐标
         left();//左转
       }
       else if(x<80)
       {
         Serial.println("2");//串口打印数字2
          Serial.println(x);//串口打印坐标
          right();//右转
       }
       else
       {
        stop1();//停转
       }
       A_String = "";//重置字符串
    }
  }
}

void right()   //右转
{
  analogWrite(ENA,240);
 
  digitalWrite(input1,HIGH); 
  digitalWrite(input2,LOW);  
  digitalWrite(input3,LOW); 
  digitalWrite(input4,LOW);   
}


void stop1()//停转
{
 
  digitalWrite(input1,LOW); 
  digitalWrite(input2,LOW);  
  digitalWrite(input3,LOW); 
  digitalWrite(input4,LOW);   
}

void left()//左转
{
 
  analogWrite(ENB,240);
  digitalWrite(input1,LOW); 
  digitalWrite(input2,LOW);  
  digitalWrite(input3,HIGH); 
  digitalWrite(input4,LOW);   
}

openmv程序

import sensor, image, time
from pyb import UART
import json
bule_threshold   = (48,12,-19,33,-49,-14)//蓝色的阈值
sensor.reset() # Initialize the camera sensor.
sensor.set_hmirror(True)
sensor.set_vflip(True)
sensor.set_pixformat(sensor.RGB565) 
sensor.set_framesize(sensor.QQVGA) 
sensor.skip_frames(10)
sensor.set_auto_whitebal(False) 
clock = time.clock()

uart = UART(3, 9600)//串口波特率需要和arduino一致 这里设为9600
def find_max(blobs):
    max_size=0
    for blob in blobs:
        if blob.pixels() > max_size:
            max_blob=blob
            max_size = blob.pixels()
    return max_blob //寻找最大色块并返回最大色块的坐标

while(True):
    img = sensor.snapshot()//采集图像

    blobs = img.find_blobs([blue_threshold])
    if blobs:
        max_blob=find_max(blobs)
        img.draw_rectangle(max_blob.rect())//框选最大色块
        img.draw_cross(max_blob.cx(), max_blob.cy())//在最大色块中心画十字
        pcx = max_blob.cx()//定义pcx为最大色块中心的横坐标


      
        output_str=json.dumps(max_blob.cx()) //把pcx用json字符串的形式发送给arduino
        uart.write(output_str + '\r\n')
        print(pcx)//打印输出pcx
    else:
        print('not found!')

2019-03-06 18:31:40 weixin_43679759 阅读数 6234

此篇文章是自己在学习制作寻迹小车中遇到的问题以及解决方法,写出来供同样的人参考参考…
-------------->直接开始------------>
STM32各类小车工作原理及学习
http://www.yahboom.com/study/bc-32

简介:循迹小车由三轮或四轮小车和摄像头两大部分组成,通过镜头识别路径,将其得到的图像做处
理并发送给小车,小车对应做出动作。
寻迹小车核心组成:openMV和STM3F1。
涉及主要知识:STM32相关知识(学习视频 https://ke.qq.com/course/279403);
openMV相关知识(学习网站 http://book.myopenmv.com/quick-start/ide-tutorial.html);
USART通信(运用在摄像头与小车之间通信);
SPI通信(用在OLED显示);
直流电机有关资料;
直流电机驱动TB6612FNG(http://tech.hqew.com/circuit_1476430);
等等。

掌握具体知识后便可搭建循迹小车了。
然而并不是一帆风顺,肯定会漏洞百出…所以在搭建时候就细心耐心,一点一点解决问题。搭建时候的注意事项:
1、在用杜邦线连接小车各部分模块时候,首先确保杜邦线没有坏;
2、杜邦线也不能有松动或者没有插紧的情况,不然很容易造成接触不良,在调试陈旭时候也会被误认为程序问题而浪费时间;
3、接的电压要按照各器件额定值接入。

到现在小车基本上是搭建好了(除了摄像头部分),先启动小车,让它能够跑起来,还要能够跑直线,特别直!
同时还要时不时地检查一下接线是否有松动的情况!!
一切都OK了,那就装上openMV进行数据对接。

有几点软件部分需要注意的事项:
1、注意使用USART通信要明确自己所用的协议,否则会出现不是自己预想的数据;
2、在我使用openMV时候,左右的偏移量为0到45 和 -45到0,但是将该数据传输回STM32时候负数段数据不正常。解决方法为将左右偏移量的数据范围加上相应的数字,使其左右偏移量范围里没有负数数据。

搞好以后迫不及待的要跑一下了,但是刚按下开关,小车按照轨迹过弯道过到一半时候乱跑了,然后又是瞎跑。。。。做到这一步又有几点需要注意,可耐心读完:
1、 注意,这几点很重要!!!
2、小车能够简单寻迹,但又不能完全寻迹时候,可以把摄像头从小车上拿下来,固定在一个摄像头倾斜角和高度相同的简易支架上,连接到电脑上观看小车视野,,在OPENMV IDE里面查看画面,是否在过弯到一半时候找不到视野。若是这个问题则根据自己的需求配合程序进行相应的调整;
3、 小车向左转很顺利,向右转会“出轨”,或是左转到一半会瞎跑。首先要确保摄像头摆放位置是正的,接线是否有松动而影响数据传输,然后确保硬件没问题时候检查偏移量的数据。通过查看偏移时候openMV处理出来的数据,发现左边过弯很顺利是其偏移量正常,而右边偏移量不足,导致其过弯一半“出轨”。适当增加右转偏移量即可解决。
4、 有时会出现数据传输错乱,但是硬件又没有问题,这时候应该把数据传输有关的模块单独接电源或是原理接线群,其他的线路可能会有些许的影响(没有固然最好,但是值得一试)。
5、 测试时候-务-必-一-定-必-须-要在光线好的地方测试,很重要!!!!因为光线弱会影响到识别路径,从而导致乱跑、跑到旁边的路径上,此时又会误以为是程序出了问题,又去调试程序浪费时间。程序调试N多次,不如灯光照一次!!

好了,到这里你的小车应该会跑的很顺利了
最后附上openMV的Python程序

# Black Grayscale Line Following Example
#
# Making a line following robot requires a lot of effort. This example script
# shows how to do the machine vision part of the line following robot. You
# can use the output from this script to drive a differential drive robot to
# follow a line. This script just generates a single turn value that tells
# your robot to go left or right.
#
# For this script to work properly you should point the camera at a line at a
# 45 or so degree angle. Please make sure that only the line is within the
# camera's field of view.

import sensor, image, time, math#调用声明
from pyb import UART

# Tracks a black line. Use [(128, 255)] for a tracking a white line.
GRAYSCALE_THRESHOLD = [(0, 64)]
#设置阈值,如果是黑线,GRAYSCALE_THRESHOLD = [(0, 64)];
#如果是白线,GRAYSCALE_THRESHOLD = [(128,255)]


# Each roi is (x, y, w, h). The line detection algorithm will try to find the
# centroid of the largest blob in each roi. The x position of the centroids
# will then be averaged with different weights where the most weight is assigned
# to the roi near the bottom of the image and less to the next roi and so on.
ROIS = [ # [ROI, weight]
        (0, 100, 160, 20, 0.7), # You'll need to tweak the weights for you app
        (0, 050, 160, 20, 0.3), # depending on how your robot is setup.
        (0, 000, 160, 20, 0.1)
       ]
#roi代表三个取样区域,(x,y,w,h,weight),代表左上顶点(x,y)宽高分别为w和h的矩形,
#weight为当前矩形的权值。注意本例程采用的QQVGA图像大小为160x120,roi即把图像横分成三个矩形。
#三个矩形的阈值要根据实际情况进行调整,离机器人视野最近的矩形权值要最大,
#如上图的最下方的矩形,即(0, 100, 160, 20, 0.7)

# Compute the weight divisor (we're computing this so you don't have to make weights add to 1).
weight_sum = 0 #权值和初始化
for r in ROIS: weight_sum += r[4] # r[4] is the roi weight.
#计算权值和。遍历上面的三个矩形,r[4]即每个矩形的权值。

# Camera setup...
sensor.reset() # Initialize the camera sensor.
sensor.set_pixformat(sensor.GRAYSCALE) # use grayscale.
sensor.set_framesize(sensor.QQVGA) # use QQVGA for speed.
sensor.skip_frames(30) # Let new settings take affect.
sensor.set_auto_gain(False) # must be turned off for color tracking
sensor.set_auto_whitebal(False) # must be turned off for color tracking
#关闭白平衡
clock = time.clock() # Tracks FPS.

while(True):
    clock.tick() # Track elapsed milliseconds between snapshots().
    img = sensor.snapshot() # Take a picture and return the image.
    uart = UART(3,19200)
    uart.init(19200,bits=8,parity=None,stop=1)#init with given parameters

    centroid_sum = 0
    #利用颜色识别分别寻找三个矩形区域内的线段
    for r in ROIS:
        blobs = img.find_blobs(GRAYSCALE_THRESHOLD, roi=r[0:4], merge=True)
        # r[0:4] is roi tuple.
        #找到视野中的线,merge=true,将找到的图像区域合并成一个

        #目标区域找到直线
        if blobs:
            # Find the index of the blob with the most pixels.
            most_pixels = 0
            largest_blob = 0
            for i in range(len(blobs)):
            #目标区域找到的颜色块(线段块)可能不止一个,找到最大的一个,作为本区域内的目标直线
                if blobs[i].pixels() > most_pixels:
                    most_pixels = blobs[i].pixels()
                    #merged_blobs[i][4]是这个颜色块的像素总数,如果此颜色块像素总数大于                     #most_pixels,则把本区域作为像素总数最大的颜色块。更新most_pixels和largest_blob
                    largest_blob = i

            # Draw a rect around the blob.
            img.draw_rectangle(blobs[largest_blob].rect())
            img.draw_rectangle((0,0,30, 30))
            #将此区域的像素数最大的颜色块画矩形和十字形标记出来
            img.draw_cross(blobs[largest_blob].cx(),
                           blobs[largest_blob].cy())

            centroid_sum += blobs[largest_blob].cx() * r[4] # r[4] is the roi weight.
            #计算centroid_sum,centroid_sum等于每个区域的最大颜色块的中心点的x坐标值乘本区域的权值

    center_pos = (centroid_sum / weight_sum) # Determine center of line.
    #中间公式

    # Convert the center_pos to a deflection angle. We're using a non-linear
    # operation so that the response gets stronger the farther off the line we
    # are. Non-linear operations are good to use on the output of algorithms
    # like this to cause a response "trigger".
    deflection_angle = 0
    #机器人应该转的角度

    # The 80 is from half the X res, the 60 is from half the Y res. The
    # equation below is just computing the angle of a triangle where the
    # opposite side of the triangle is the deviation of the center position
    # from the center and the adjacent side is half the Y res. This limits
    # the angle output to around -45 to 45. (It's not quite -45 and 45).
    deflection_angle = -math.atan((center_pos-80)/60)
    #角度计算.80 60 分别为图像宽和高的一半,图像大小为QQVGA 160x120.
    #注意计算得到的是弧度值

    # Convert angle in radians to degrees.
    deflection_angle = math.degrees(deflection_angle)
    #将计算结果的弧度值转化为角度值
    A=deflection_angle
    print("Turn Angle: %d" % int (A))#输出时强制转换类型为int
    #print("Turn Angle: %d" % char (A))
    # Now you have an angle telling you how much to turn the robot by which
    # incorporates the part of the line nearest to the robot and parts of
    # the line farther away from the robot for a better prediction.
    print("Turn Angle: %f" % deflection_angle)
    #将结果打印在terminal中
    uart_buf = bytearray([int (A)])
    #uart_buf = bytearray([char (A)])
    #uart.write(uart_buf)#区别于uart.writechar是输出字符型,这个函数可以输出int型
    uart.write(uart_buf)
    uart.writechar(0x41)#通信协议帧尾
    uart.writechar(0x42)

    time.sleep(10)#延时
    print(clock.fps()) # Note: Your OpenMV Cam runs about half as fast while
    # connected to your computer. The FPS should increase once disconnected.

2017-06-17 21:14:21 cosmispower 阅读数 3423

sobel的数学公式表达

(1)离散表达


(2)矩阵表达


(3)梯度的近似表达(滤波后的灰度值)


背景,在巡线中经常有一些轨迹鉴别的问题存在。如果不想随大流用普通的灰度传感器,又或者路面比较复杂的情况下。可以使用摄像头采集画面,对每一帧进行边界的识别(个人建议因为在巡线小车这样小巧的物体上不能放置电脑,所以采用51单片机或者stm32单片机的小伙伴们可以各帧采样或者隔合适帧采集,毕竟单片机的运算慢。)那么根据上面的公式我们可以得到轨迹的路线。其实还有其他的算法,不过呢,相比sobel算子,其他的算子计算量大,或者对于噪点的处理不佳。比较对于小车来说,先滤波什么的是不存在的,精度没这么高。


在得到轨迹线了之后就要循迹了,那么怎么循迹呢?最简单的是pd算法循迹,pd算法是pid算法除去了积分过程,只留下普通项与微分项。根据论文2,舵机的延时偏转就相当于一个积分过程。

pd算法

舵机PWM占空比=KP*偏心距+KD*偏心距变化率


舵机PWM占空比=KP*偏心距+KD*偏心距变化率+KA ∗V2/R

R为轨道半径;l:为前瞻;h为前瞻离轨道距离如图所示



如果想程序更好自动化水平高,那么可以考虑这样的特殊情况



程序

下面是给出静态图片的sobel轨迹识别的程序(matlab)

%%%%%%%边缘识别与画出中心线%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
imag = imread('D:\广东工业大学机器人学院\数字图像处理\1.jpg');  %读取关键帧,写上你的文件名
imag = rgb2gray(imag);        %转化为灰度图
subplot(1,4,1);imshow(imag);title('原图');
[high,width] = size(imag);   % 获得图像的高度和宽度
U = double(imag);
uSobel = imag;
%%%%%%%sobel边缘检测%%%%%%%
for i = 2:high - 1   %忽略边缘行以防溢出
for j = 2:width - 1
Gx = (U(i+1,j-1) + 2*U(i+1,j) + U(i+1,j+1)) - (U(i-1,j-1) + 2*U(i-1,j) + U(i-1,j+1));
Gy = (U(i-1,j+1) + 2*U(i,j+1) + U(i+1,j+1)) - (U(i-1,j-1) + 2*U(i,j-1) + U(i+1,j-1));
uSobel(i,j) = abs(Gx) + abs(Gy);
end
end
subplot(1,4,2);imshow(im2uint8(uSobel));title('边缘检测后');  %画出边缘检测后的图像
%%%%%%%二值化%%%%%%%
for i = 2:high - 1
for j = 2:width - 1
if uSobel(i,j)<150    %阈值可调
uSobel(i,j)=0;
elseif  uSobel(i,j)>=150   %阈值可调
uSobel(i,j)=255;
end
end
end
subplot(1,4,3);imshow(im2uint8(uSobel));title('threshold');
%%%%%%%画出中心线%%%%%%%
for i = 2:high - 1
k=0;K=[];u=[];
for j = 2:width - 1
if  uSobel(i,j)>=200
k=k+1;
K(k)=uSobel(i,j);
u(k)=j;
end
end
p=length(u);
if p>0;
P=ceil((u(1)+u(p))/2);
uSobel(i,P)=255;
end
end
subplot(1,4,4);imshow(im2uint8(uSobel));title('center line');

效果如图:



参考文献

【1】蒋旭 . 两点算法求智能车赛道曲率 . 新技术新工艺 . 2014 . 32-33

【2】 第九届“飞思卡尔”杯全国大学生智能汽车竞赛技术报告 北京邮电大学 

【3】冈萨雷斯 数字图像处理 第二版 463-470


2020-04-04 12:24:32 QbitAI 阅读数 254
主讲人 | 何琨 英伟达
量子位编辑 | 公众号 QbitAI

3月26日,英伟达图像处理系列公开课第三期线上开播,来自NVIDIA开发者社区的何琨老师,与数百位开发者共同探讨了:

  • 利用NVIDIA迁移式学习工具包构建SSD目标检测网络的推理引擎

  • 将推理引擎迁移到以Jetson Nano为核心的Jetbot智能小车上

  • 在Jetbot智能小车上部署推理引擎

应读者要求,我们将分享内容整理出来,与大家分享。直播中主要的live coding环节受限于文字描述,还请大家观看直播回放(文末附有PPT、直播回放链接) 。

以下为本次分享的内容整理:

大家好,我是来自NVIDIA开发者社区的何琨。我的每次分享都会给大家展示这张图。

它很好的诠释了深度学习的架构,从应用角度来说,视觉、语音识别、NLP等是目前发展较快的领域。市面上有很多框架支持这些深度学习的应用,如Caffe、TensorFlow、PyTorch等,而支撑起这些框架的基础是强大的计算能力。

NVIDIA提供了大量的GPU、边缘设备等,为深度学习框架、推理训练提供了强大的支撑能力。在英伟达CUDA生态系统上,建立了cuDNN、TensorRT、DeepStream SDK、cuBLAS等一系列工具,都是中层的框架应用的基础的内容。

今天分享的主题是“利用迁移式学习工具包加速Jetbot智能小车的推理引擎部署”,首先介绍下这两个工具。

迁移式学习工具包

NVIDIA迁移式学习工具包(Transfer Learning Toolkit,简称TLT)是一个基于Python的工具包,它提供了大量预先训练的模型,并提供一系列的工具,使流行的网络架构适应开发者自己的数据,并且能够训练、调整、修剪和导出模型,以进行部署。


也就是说,我们使用TLT时,就不需要再掌握(上图)左侧这些工具了,大大提高深度学习工作流的效率和精度。

TLT提供了很多训练好的模型,(上图)列举了30多种常用的预训练模型,后面也将推出更多预训练模型,大家可以在NGC(https://www.nvidia.cn/gpu-cloud/)上下载。

选好预训练模型后,我们可以通过TLT对其进行训练、剪枝、再训练等。一键输出后的模型可以直接在DeepStream和TensorRT上使用;优化加速后可以部署在移动端或嵌入式产品上,比如自动驾驶汽车、无人机上。

Jetbot智能小车

Jetbot是以Jetson Nano为计算核心的自动驾驶小车模型。Jetson Nano的体积非常小,只有巴掌大小,但是可以提供470GFLOPS的计算能力。

Nano支持多种接口、双电源,为我们的训练与部署深度学习模型提供了便利。

上图是我去年参加的无人车驾驶比赛的现场,大家可以看到,Jetbot小车能够识别路牌、建筑物,自己找到路径,其计算核心就是Jetson Nano。

上图是Jetbot升级版本的赛车,可以看出它的速度非常快。我们在推理时最高达到了60FPS的速度。实际应用中,为了平衡摄像头的编解码,我们将推理速度减少到45FPS。

上图展示了Jetbot在复杂的环境中自动避障、识别路径的过程,这些功能都是基于深度学习在视觉领域的应用。

我们可以看到,Jetbot小车上有一个前端摄像头,几个控制接口,两个主动轮、一个从动轮,便于大家控制和实践。

我们可以自己设置或直接使用设置好的函数。

前端摄像头的视频接口。我们不需要考虑怎么调用前端摄像头,代码中已经设置好了。

深度学习模型推理接口。


实战:部署一辆Jetbot小车

接下来,何琨老师展示了如何用TLT训练模型,以及在Jetbot上运行模型。受限于文字描述,欢迎大家点击链接观看直播回放(第25分钟起):

直播链接:https://info.nvidia.com/303606-ondemand.html

PPT地址:https://www.nvidia.cn/content/dam/en-zz/zh_cn/assets/webinars/2020/mar26/TLT--2020.03.26.pdf

传送门

第一期课程:利用 NVIDIA 迁移式学习工具包和Deepstream实现实时目标检测

>>直播回放:https://info.nvidia.com/272903-ondemand.html

>>PPT:https://www.nvidia.cn/content/dam/en-zz/zh_cn/assets/webinars/2020/feb27/TLT--2020.02.27.pdf

第二期课程:利用TensorRT 7.0部署高速目标检测引擎

>>直播回放链接:https://info.nvidia.com/291730-ondemand.html

>>PPT:https://www.nvidia.cn/content/dam/en-zz/zh_cn/assets/webinars/2020/mar12/dev/TLT--2020.03.12.pdf


作者系网易新闻·网易号“各有态度”签约作者

—  —

<英伟达NLP公开课> 开始报名啦,4月9号晚8点,英伟达GPU计算专家将分享 FasterTransformer 2.0 的原理与应用,分享如何针对 decoder 和 decoding 进行优化。

戳二维码,备注“英伟达”即可报名、加交流群,主讲老师也会进群与大家交流互动哦~ 

直播报名 | Decoder与Decoding的优化与加速

天文航天亲子社群招募,一起来玩吧~

2020年是个天文大年,4月8日有全年最大的超级月亮,6月有横跨中国的日环食,8月有英仙座流星雨,10月还有两年一次的火星冲日,12月有双子座流星雨。

我们邀请「美国国家地理极致中国探享家刘允」和天文爱好者们,组建了一个天文航天亲子社群,群里除了交流天文航天知识,还会组织一系列讲座、线下观星、航天探访活动。

如果你有宝宝,也热爱天文航天,一起来玩啊。

量子位 QbitAI · 头条号签约作者

վ'ᴗ' ի 追踪AI技术和产品新动态

喜欢就点「在看」吧 !

2017-07-11 15:09:10 xinlongkun 阅读数 6780

Freescale实纪——线性CCD图像处理与二值化

 

        上一篇博文我简单论述了Freescale光电组传感器——线性CCD的曝光与采集,那在对赛道的路面信息处理中,大多有两种算法:凹槽法,可详见上海交大的技术报告与论文;更常见且本人采取的做法是软件二值化。

        大家知道,线性CCD一个曝光周期只能采集视野范围内一条线的信息,即128个像素信息。我们通过AD采样获取的数据其实是通过运算电路得到的像素点的电压值,是一个数值。通过采样函数ImageCapture(Pixel)将一个曝光周期内采集到的128个像素点的电压值寄存到位长128的数组Pixel[i]中,从而用于计算与数据处理。在128个像素点的信息中,光线越强的点电压值越高,越弱的点电压值越低。针对赛道来说,CCD采集到白色的赛道部分电压值较高,我们认为是区域白电平;赛道两边的黑线以及黑线外的深蓝色背景光线暗,电压值低,即暗电压。由此我们可以区分赛道与边界,通过寻找赛道两边的黑线位置,计算赛道中间位置,让小车近似循中线行进。

        引出本文重点内容——二值化与动态阈值。对于CCD来说,黑与白的电压差是很大的。下图显示的是反映在上位机中CCD采集的一个画面。中间的凹槽表明在视野的那个位置有一个黑色区域。

        实际赛道黑色与白色部分的电压差值比上图要大许多,如果我们找到白电压与黑电压数值上的一个合适的中间值作为阈值,高于此阈值的像素点电压一律拉高到250(250数值根据上位机显示设置),低于此阈值的像素点电压一律置0,那么画面中的数据只有两个值,会产生明显的黑白电压跳变沿,这样只要找到两边的跳变沿,就可以确定黑线位置,从而计算出赛道中值。这就是二值化的基本原理与用法。

        二值化的数据处理不难,难在找到一个合适的阈值。首先要保证黑白电压的差值要很明显,通过更改曝光时间,加偏振片可以一定程度改变电压的差距。我个人还用了一个方法。在上一篇博文里我介绍的采样函数中有这一条采样语句:

temp_int = AD_Measure12(0);

*ImageData++ =(byte)(temp_int>>4); 

我们把第二句改成这样:

*ImageData++ = k* (byte)(temp_int>>4); 加上系数k,黑白电压的数值都乘上一个合理的系数,在保证图像清晰的前提下可以黑白电压的差值也相应增大,这样也扩大了我们可寻找的阈值范围,避免了出现阈值离黑白电压的上下限太近,二值出现紊乱的情形。

        现在有以下三种方法确定阈值:

        1、固定阈值

        在黑白电压值得较稳定,浮动范围很小的情况下,如白电压始终在170上下极小的范围内浮动,黑电压在50左右稳定,此时阈值可选范围很大,我们随机取一个100或120都是没问题的。但很多CCD存在畸变,视野两侧的电压始终很低,而且有时考虑到CCD视角的变化(如更改前瞻,焦距),图像的波动可能也会较大,用以下两种方法可能更实用。

        2、取平均电压值作阈值

        我们计算出128个像素点电压的平均值,取此值作为二值化的阈值,可以增强环境的适应性,哪怕黑白电压的门限不断变化,有一定的波动,或者光线略有不均,此方法都还比较凑效。

        3、计算max与min的均值作阈值

        我们对128个像素点做排序,取出电压最大与最小的两个像素点的电压值,取二者的平均值作阈值,事实证明效果也还是可以的,本人在安徽赛区的赛场上用的就是这个方法。但是考虑到比赛赛场上的光线过强,加上CCD畸变的影响,即使加上偏振片,得到的图像可能仍不理想,如下:

        这是我在比赛前一天试车时观察到的真实图像,两边的下降沿不明显,有个渐变的过程。此时用上述均值的方法得到的阈值可能恰好卡在平缓的下降沿那里,容易严重影响二值化后黑线的间距。我的做法是在所得阈值的基础上加上一个合适的常数作为阈值,使阈值达到稍靠近白电压的位置,这样的处理二值化后是没有问题的。

        当然针对安徽赛区的坑爹光线,第一种固定阈值的方案也是不错的选择。我们学校一个光电组一等奖的队员在试车后听了我们队关于阈值确定的建议,改成固定阈值,效果也是不错的。

        下面是我写的固定阈值选取与二值化的函数(本队使用的是双CCD):

//======固定阈值======//

 void Get_Dyn_Th(void) 

{

     extern unsigned char PixelAverageValue;     //CCD1阈值

     extern unsigned char Pixel2AverageValue;     //CCD2阈值

     unsigned char i;

     

     PixelAverageValue_old=PixelAverageValue;

     Pixel2AverageValue_old=Pixel2AverageValue;

     

     //计算CCD1像素点最大值

     value1_max=Pixel[0];

     for(i=1;i<128;i++) 

     {

        if(value1_max<=Pixel[i])

        value1_max=Pixel[i];

     }

     //计算CCD1像素点最小值

     value1_min=Pixel[0];

     for(i=1;i<128;i++) 

     {

        if(value1_min>=Pixel[i])

        value1_min=Pixel[i];

     }

     //计算CCD1阈值

     PixelAverageValue=(value1_max+value1_min)/2+40;

     

     if(abs(value1_max-value1_min)<=110)

     {

        PixelAverageValue=PixelAverageValue_old;   

     }

     

     //计算CCD2像素点最大值

     value2_max=Pixel2[0];

     for(i=1;i<128;i++) 

     {

        if(value2_max<=Pixel2[i])

        value2_max=Pixel2[i];

     }

     //计算CCD2像素点最小值

     value2_min=Pixel2[0];

     for(i=1;i<128;i++) 

     {

        if(value2_min>=Pixel2[i])

        value2_min=Pixel2[i];

     }

     //计算CCD2阈值

     Pixel2AverageValue=(value2_max+value2_min)/2+30;

     

     if(abs(value2_max-value2_min)<=100)

     {

        Pixel2AverageValue=Pixel2AverageValue_old;   

     }

}

 

//=========二值化处理==========//

void Bi_conversion(void) 

{

     unsigned char i;

     Get_Dyn_Th();

     for(i=0;i<128;i++)

     {

         if(Pixel[i]>PixelAverageValue)      //PixelAverageValue即为阈值

         {

              Pixel[i]=250;

         }

         else

         {

              Pixel[i]=0;

         }

         if(Pixel2[i]>Pixel2AverageValue)//下午用110,晚上用100

         {

              Pixel2[i]=250;

         }

         else

         {

              Pixel2[i]=0;

         }

     }

}

        还有二值化后的数据均值滤波函数(参考了第八届安工大的均值滤波算法):

//====================均值数据滤波==================//

void Filter_Pixel_Two(void)

{

     unsigned char i;

     for(i=1;i<127;i++)

     {

         if(Pixel[i]==0&&Pixel[i]!=Pixel[i-1]&&Pixel[i]!=Pixel[i+1])

         {

              Pixel[i]=250;

         } 

         else if(Pixel[i]==250&&Pixel[i]!=Pixel[i-1]&&Pixel[i]!=Pixel[i+1])

         {

              Pixel[i]=0;

         }

         

         if(Pixel2[i]==0&&Pixel2[i]!=Pixel2[i-1]&&Pixel2[i]!=Pixel2[i+1])

         {

              Pixel2[i]=250;

         } 

         else if(Pixel2[i]==250&&Pixel2[i]!=Pixel2[i-1]&&Pixel2[i]!=Pixel2[i+1])

         {

              Pixel2[i]=0;

         }

     }

            

     //当黑线上的点数超过三个时使用

     for(i=1;i<126;i++)

     {

          if(Pixel[i]==0&&Pixel[i]==Pixel[i+1]&&Pixel[i]!=Pixel[i-1]&&Pixel[i]!=Pixel[i+2])

          {

              Pixel[i]=250;

              Pixel[i+1]=250;

          }

          if(Pixel[i]==250&&Pixel[i]==Pixel[i+1]&&Pixel[i]!=Pixel[i-1]&&Pixel[i]!=Pixel[i+2])            

          {

              Pixel[i]=0;

              Pixel[i+1]=0;

          }

          

          if(Pixel2[i]==0&&Pixel2[i]==Pixel2[i+1]&&Pixel2[i]!=Pixel2[i-1]&&Pixel2[i]!=Pixel2[i+2])

          {

              Pixel2[i]=250;

              Pixel2[i+1]=250;

          }

          if(Pixel2[i]==200&&Pixel2[i]==Pixel2[i+1]&&Pixel2[i]!=Pixel2[i-1]&&Pixel2[i]!=Pixel2[i+2])            

          {

              Pixel2[i]=0;

              Pixel2[i+1]=0;

          }

      }

}

        这样,固定曝光的动态阈值与二值化数据处理就完成了,这些都是图像处理的基本步骤。

        曾看过第八届长春理工的图像处理算法,考虑到CCD畸变的影响,越往视野两边电压值因硬件缘故会自然的降低,所以他们将很多组CCD采集的128个像素点电压值导入Matlab进行数据分析,得出了一个畸变的曲线,然后用软件分析得出了一个128位加权数组,也就是让每个采集到的像素点原始电压值乘上加权数组里对应的权值,再加上一个补偿常数(也是经过计算得到)后得到的值作为待处理的像素点电压值。这样得到的图像即使不二值化,也跟二值化后的图像几乎类似了,再辅以一些滤波函数,最终二值化出来的图像极为稳定好看。   还有电子科大的离散余弦法处理图像,都是些高大上的处理方法。本人是非985,211的三流大学学生,学校、老师都提供不了这样高级的数学理论支持,本人用的处理方法和他们一比简直土的掉渣,不过对于不到2.8m/s的小车还是够用的。

        图像处理这块可挖掘的东西真的很多,是一门单独的学问。

        在后面的内容中我会提到鄙人的黑线提取算法与中线处理,包括特别针对十字弯与丢线情况的判断与解决方案。

        本人的水平十分有限,希望大家批评指正,不胜荣幸!

OpenCV颜色识别

阅读数 0

OV7670循迹算法整理

阅读数 24915