2023大学生电子设计竞赛E题openmv摄像头思路及部分代码(遗憾省二)

今年8月初题主初次参加了大学生电子设计竞赛,只能说遗憾挺多的,遇到了很多问题,也没能进入国赛,因此在省赛结束了半个月之后想着分享一下自己的经验,以及分享自己本次比赛的思路和部分程序,如果有任何不足或者说的不对的地方请大伙指出。
今年的题目一共是4个基础题,2个拓展题,当然本质来说是3个拓展。
分享一个题目链接,不知道可以转载嘛

首先是比赛经验

在备赛过程中我们主要是做了2021年的F题,因为清单里有小车,外加我们校赛也是做的小车,因此购买了一些新车模以及小车套件。刚出题目的时候发现小车和无人机在一道题目里时人是很懵逼的,而且我们购买的云台是很廉价的那种,精度很低。给我们的启示就是不要试图猜测主办方的题目,最好是根据清单把至少把材料都备好。我就猜测云台和激光笔是给无人机题的,赛前都没有买。
第一天主要就是在等待好云台和激光笔,同时把摄像头基础部分写了(因为我只负责摄像头,因此舵机部分的进度就不细说了),一早上都在思考策略,事实证明花费至少一早上的宝贵时间思考策略方案是很正确的,以为在之后的几天不止一个队伍因为发现进行不下去不得不更换方案,从头来过。到晚上基本做出了基础第二题。
第二天好云台到了,开始一边写程序一边测试,一直到晚上做出了基础第三题,编写好了基础第四题,然后大概就3点了,只能先回去睡觉。
第三天早上做出了基础第四题(当然后来在实际测试中并没有跑出来),下午开始搭建第二个云台并且做拓展部分,拓展部份程序是共用的而且并不复杂,因此到了晚上就做出来了,剩下就是舵机部分同学在调参。
第三天晚上熬了通宵在写报告,第四天早上和下午的时候开始进行最后阶段的测试和调参,此时发现了致命的问题,导致了最终第四问和拓展的一部分没有得分。但是由于此时距离比赛结束还有不到两个小时的时间了,因此只能选择听天由命。
实际测试过程中第四问确实如料想一样出现了问题,没有得分,同时还有一个题目出现了小问题,导致应该是扣掉了5分左右。从做题情况和获奖情况来看,即使没有做出来基础第四问以及第四问对应的拓展题其实也是可以得省一并且进入国赛的,但是前提是前面的部分几乎不能扣分,我们小组在拓展的第一题扣除了5分可能,导致了最后差了三个小组得省一,这是非常令人遗憾的。因此根据我的个人推断,只需要做出基础第一问和第二问就可以获得省三,再做个拓展的第一问应该就可以得到省二了,而做出第三问以及对应的拓展就可以获得省一,当然前提条件就是做出来的部分不能出现扣分,这个其实并不算太难,因为最容易扣分的还是基础第四问和对应的拓展。

接下来就是整体的思路

首先我们基础部分的整体策略是视觉闭环,简单来说就是通过识别出红点当前位置并计算找到下一个位置,计算出横纵坐标的偏差error_xerror_y反馈给舵机,并由舵机来矫正偏差,因此就将问题简化成了三部分:

1. 当前的红点的位置
2. 下一个位置
3. 任务结束了吗

当前红点的位置可以使用openmv最经典的找色块函数find_blobs,然后经过简单的处理得到当前红点的相对坐标。
下一个位置就需要根据题目要求进行计算了,比如第一问是到屏幕中心点,就将屏幕中心的坐标设置为下一个位置
任务结束了吗就是判断是否已经结束任务,如果结束了就不需要再计算下一个位置了,同时也要告诉舵机任务已经结束。
这个思路是很清晰的而且也适用于拓展部分,拓展部分我们将激光笔和舵机固定在一起,保证了绿点在摄像头里的相对坐标不变,因此只需要读取一开始绿点在屏幕中的位置,就不再需要识别了,这个坐标就会始终作为当前绿点的位置,而红点的位置作为下一个位置来求出偏差并跟踪,这当然是存在误差的,但是在实际测试中绿点几乎可以和红点完全重合,证明了这个误差完全是可以接收的。

下面就是每个小问的思路和程序

基础第一问

正如前文所说,我们解决第一个问题当前的红点位置,程序如下:

def find_max(blobs):
    max_size=0
    for blob in blobs:
        if blob[2]*blob[3] > max_size:
            max_blob=blob
            max_size = blob[2]*blob[3]
    return max_blob
def find_light_max(img,max_blob):
    #print('rgb')
    #print(img.get_pixel(max_blob[5],max_blob[6]))
    max_light=0
    location=[0,0]

    for i in range(max_blob.x(),max_blob.x()+max_blob.w()):
        for j in range(max_blob.y(),max_blob.y()+max_blob.h()):
            rgbtemp=img.get_pixel(i,j)
            rgb=rgbtemp[0]+rgbtemp[1]+rgbtemp[2]
            if rgb>max_light:
                max_light=rgb
                location[0]=i
                location[1]=j
    return location

def find_max_blobs(img,threshold):
    blobs = img.find_blobs([threshold],area_threshold=1,pixels_threshold=1)
    if blobs:
        max_blob = find_max(blobs)
        location=find_light_max(img,max_blob)
        img.draw_rectangle(max_blob[0:4]) # rect
        img.draw_cross(location[0], location[1]) # cx, cy
        return location[0],location[1]
    else:
        return -1,-1

很好理解,首先找到固定阈值的最大色块,并且将色块中亮度最高的像素坐标作为当前红点的位置
随后我们将屏幕中心的坐标作为下一个位置,这里一个难点就是怎么找到屏幕的中心,这里我们采用的方式是手动标定,这是很容易第一时间想到的,在测评开始前手动标定了坐标,实际测评期间就不再需要标定了,因此肯定不会违反比赛规则。只需要写一个额外的函数与下位机进行通讯,同时手动用激光笔打在屏幕上我们人眼看到的中心并且告诉下位机这就是我们需要的下一个位置,这个函数我也粘在下面:

def rect_sign(received_data,threshold):
    global data_flag
    global RT_x,RB_x,LT_x,LB_x,LT_y,RT_y,LB_y,RB_y,C_x,C_y
    img = sensor.snapshot().lens_corr(strength = 1.8, zoom = 1.0)
    find_max_blobs(img,threshold)
    if received_data==b'\x00':
        data_flag=1
    if data_flag==1:
        img = sensor.snapshot().lens_corr(strength = 1.8, zoom = 1.0)
        [LT_x,LT_y]=find_max_blobs(img,threshold)
        print(LT_x,LT_y)
        if LT_x!=-1 and LT_y!=-1:
            send_data(b'g')
            data_flag=6
    if received_data==b'\x01':
        data_flag=2
    if data_flag==2:
        img = sensor.snapshot().lens_corr(strength = 1.8, zoom = 1.0)
        [LB_x,LB_y]=find_max_blobs(img,threshold)
        print(LB_x,LB_y)
        if LB_x!=-1 and LB_y!=-1:
            send_data(b'g')
            data_flag=6
    if received_data==b'\x02':
        data_flag=3
    if data_flag==3:
        img = sensor.snapshot().lens_corr(strength = 1.8, zoom = 1.0)
        [RT_x,RT_y]=find_max_blobs(img,threshold)
        print(RT_x,RT_y)
        if RT_x!=-1 and RT_y!=-1:
            send_data(b'g')
            data_flag=6
    if received_data==b'\x03':
        data_flag=4
    if data_flag==4:
        img = sensor.snapshot().lens_corr(strength = 1.8, zoom = 1.0)
        [RB_x,RB_y]=find_max_blobs(img,threshold)
        print(RB_x,RB_y)
        if RB_x!=-1 and RB_y!=-1:
            send_data(b'g')
            data_flag=6
    if received_data==b'\x04':
        data_flag=5
    if data_flag==5:
        img = sensor.snapshot().lens_corr(strength = 1.8, zoom = 1.0)
        [C_x,C_y]=find_max_blobs(img,threshold)
        print(C_x,C_y)
        if C_x!=-1 and C_y!=-1:
            send_data(b'g')
            data_flag=6
write_black_flag=0
quad_flag=0
center_flag=0

里面大部分都是一些通讯协议,方便下位机解析数据的,其实可以封装在函数里让代码更简洁,但是答主比较懒就直接ctrl+CV了。
最后解决任务结束了吗就是通过判断当前红点的位置下一个位置的偏差是否在可接受的范围内,也就是题目要求的2cm内。而求出偏差的公式为
err_x=n_x-C_x
err_y=C_y-n_y
具体代码如下:

def get_center_error(C_x,C_y,threshold):
    global last_err_x
    global last_err_y
    global center_flag
    img = sensor.snapshot().lens_corr(strength = 1.8, zoom = 1.0)
    [n_x,n_y]=find_max_blobs(img,threshold)
    if n_x!=-1 and n_y!=-1:
        err_x=n_x-C_x
        err_y=C_y-n_y
    else:
        err_x=last_err_x
        err_y=last_err_y

    print('err_x:',err_x)
    print('err_y:',err_y)
    send_data(b'\xff')
    send_data(str(int(err_x)))
    send_data(b'\xee')
    send_data(str(int(err_y)))
    send_data(b'\xdd')
    last_err_x=err_x
    last_err_y=err_y
    if math.sqrt(pow(err_x,2)+pow(err_y,2))<=2:
        send_data(b'\xff')
        send_data(b'e')
        send_data(b'\xdd')
        center_flag=0

基础第二问

第二问和第一问差不多,主要就是将到达一个点改成了在一条线上移动,移动过程中不能偏移,同样我们提前标定了四个角点的坐标(代码也在上面的函数里),然后每次到达一个点就将下一个点设置为下一个位置,当然求偏差的方式是发生了一定变化的,因为在移动过程中虽然整体趋势是向下一个位置*移动,但是难免因为各种误差导致偏理边框线,所以需要将偏差设置为距离边框线的偏差和下一个点的偏差,这个详细见我写的设计报告。
这个是我熬了一个通宵写的设计报告,逻辑不是很好也不全面,看看就行
代码我贴在了这里,可以看到这个函数非常长而且复杂,因为我把第二,三,四问全部融合在了这一个函数里,通过置不同的flag来让这个函数运行出不同的情况。

def get_rect_error(RT_x,RB_x,LT_x,LB_x,LT_y,RT_y,LB_y,RB_y,threshold):
    global rect_flag
    global quad_flag
    global last_err_x
    global last_err_y
    global part_k
    global part_num
    global last_error_flag

    if rect_flag==0:
        img = sensor.snapshot().lens_corr(strength = 1.8, zoom = 1.0)
        [n_x,n_y]=find_max_blobs(img,red_threshold)
        if n_x!=-1 and n_y!=-1:
            last_error_flag=1
            err_x=n_x-LT_x
            err_y=LT_y-n_y
        else:
            last_error_flag=0
            err_x=last_err_x
            err_y=last_err_y

        print('err_x:',err_x)
        print('err_y:',err_y)
        send_data(b'\xff')
        send_data(str(int(err_x)))
        send_data(b'\xee')
        send_data(str(int(err_y)))
        send_data(b'\xdd')
        if math.sqrt(pow(err_x,2)+pow(err_y,2))<=2.1 and (last_error_flag==1):
            send_data(b'\xff')
            send_data(b'n')
            send_data(b'\xdd')
            rect_flag=1
            part_k=1
            print('partk',part_k)

        last_err_x=err_x
        last_err_y=err_y



    if rect_flag==1:
        img = sensor.snapshot().lens_corr(strength = 1.8, zoom = 1.0)
        [n_x,n_y]=find_max_blobs(img,threshold)
        if n_x!=-1 and n_y!=-1 and quad_flag==0:
            last_error_flag=1
            err_x=n_x-RT_x
            err_y=(LT_y+(n_x-LT_x)/(RT_x-LT_x)*(RT_y-LT_y))-n_y
        elif n_x!=-1 and n_y!=-1 and quad_flag==1:
            last_error_flag=1
            img.draw_cross(int((RT_x-LT_x)*part_k/part_num+LT_x),int((RT_y-LT_y)*part_k/part_num+LT_y),(0,255,0))
            err_x=n_x-((RT_x-LT_x)*part_k/part_num+LT_x)
            err_y=((RT_y-LT_y)*part_k/part_num+LT_y)-n_y
        else:
            last_error_flag=0
            err_x=last_err_x
            err_y=last_err_y
        #print('err_x:',err_x)
        #print('err_y:',err_y)
        send_data(b'\xff')
        send_data(str(int(err_x)))
        send_data(b'\xee')
        send_data(str(int(err_y)))
        send_data(b'\xdd')
        if math.sqrt(pow(err_x,2)+pow(err_y,2))<=2.1 and (last_error_flag==1):
            part_k=part_k+1
            print('partk',part_k)

            if part_k>part_num:
                send_data(b'\xff')
                send_data(b'n')
                send_data(b'\xdd')
                rect_flag=2
                part_k=1
        last_err_x=err_x
        last_err_y=err_y

    if rect_flag==2:
        img = sensor.snapshot().lens_corr(strength = 1.8, zoom = 1.0)
        [n_x,n_y]=find_max_blobs(img,threshold)
        if n_x!=-1 and n_y!=-1 and quad_flag==0:
            last_error_flag=1
            err_x=n_x-(RT_x+(n_y-RT_y)/(RB_y-RT_y)*(RB_x-RT_x))
            err_y=RB_y-n_y
        elif n_x!=-1 and n_y!=-1 and quad_flag==1:
            last_error_flag=1
            img.draw_cross(int((RB_x-RT_x)*part_k/part_num+RT_x),int((RB_y-RT_y)*part_k/part_num+RT_y),(0,255,0))
            err_x=n_x-((RB_x-RT_x)*part_k/part_num+RT_x)
            err_y=((RB_y-RT_y)*part_k/part_num+RT_y)-n_y
        else:
            last_error_flag=0
            err_x=last_err_x
            err_y=last_err_y
        send_data(b'\xff')
        send_data(str(int(err_x)))
        send_data(b'\xee')
        send_data(str(int(err_y)))
        send_data(b'\xdd')
        if math.sqrt(pow(err_x,2)+pow(err_y,2))<=2.1 and (last_error_flag==1):
            part_k=part_k+1
            print('partk',part_k)
            if part_k>part_num:
                send_data(b'\xff')
                send_data(b'n')
                send_data(b'\xdd')
                rect_flag=3
                part_k=1
        last_err_x=err_x
        last_err_y=err_y
    if rect_flag==3:
        img = sensor.snapshot().lens_corr(strength = 1.8, zoom = 1.0)
        [n_x,n_y]=find_max_blobs(img,threshold)
        if n_x!=-1 and n_y!=-1 and quad_flag==0:
            last_error_flag=1
            err_x=n_x-LB_x
            err_y=(LB_y+(n_x-LB_x)/(RB_x-LB_x)*(RB_y-LB_y))-n_y
        elif n_x!=-1 and n_y!=-1 and quad_flag==1:
            last_error_flag=1
            img.draw_cross(int((LB_x-RB_x)*part_k/part_num+RB_x),int((LB_y-RB_y)*part_k/part_num+RB_y),(0,255,0))
            err_x=n_x-((LB_x-RB_x)*part_k/part_num+RB_x)
            err_y=((LB_y-RB_y)*part_k/part_num+RB_y)-n_y
        else:
            last_error_flag=0
            err_x=last_err_x
            err_y=last_err_y
        send_data(b'\xff')
        send_data(str(int(err_x)))
        send_data(b'\xee')
        send_data(str(int(err_y)))
        send_data(b'\xdd')
        if math.sqrt(pow(err_x,2)+pow(err_y,2))<=2.1 and (last_error_flag==1):
            part_k=part_k+1
            print('partk',part_k)
            if part_k>part_num:
                send_data(b'\xff')
                send_data(b'n')
                send_data(b'\xdd')
                rect_flag=4
                part_k=1
        last_err_x=err_x
        last_err_y=err_y
    if rect_flag==4:
        img = sensor.snapshot().lens_corr(strength = 1.8, zoom = 1.0)
        [n_x,n_y]=find_max_blobs(img,threshold)
        if n_x!=-1 and n_y!=-1 and quad_flag==0:
            last_error_flag=1
            err_x=n_x-(LT_x+(n_y-LT_y)/(LB_y-LT_y)*(LB_x-LT_x))
            err_y=LT_y-n_y
        elif n_x!=-1 and n_y!=-1 and quad_flag==1:
            last_error_flag=1
            img.draw_cross(int((LT_x-LB_x)*part_k/part_num+LB_x),int((LT_y-LB_y)*part_k/part_num+LB_y),(0,255,0))
            err_x=n_x-((LT_x-LB_x)*part_k/part_num+LB_x)
            err_y=((LT_y-LB_y)*part_k/part_num+LB_y)-n_y
        else:
            last_error_flag=0
            err_x=last_err_x
            err_y=last_err_y
        #print(err_x,err_y)
        send_data(b'\xff')
        send_data(str(int(err_x)))
        send_data(b'\xee')
        send_data(str(int(err_y)))
        send_data(b'\xdd')
        if math.sqrt(pow(err_x,2)+pow(err_y,2))<=2.1 and (last_error_flag==1):
            part_k=part_k+1
            print('partk',part_k)

            if part_k>part_num:
                send_data(b'\xff')
                send_data(b'e')
                send_data(b'\xdd')
                rect_flag=5
                part_k=1
        last_err_x=err_x
        last_err_y=err_y

第三问

第三问和第二问的共同点在于依然是跑一个矩形边框,不同点在于这次跑的是黑色胶布,值得注意的是黑色胶布会明显的吸收红色光,导致之前的阈值并不适用,因此我设置了两种阈值,分别对白色背景布和黑色胶布情况进行区分,代码如下:

light_red_threshold   = (43, 100, 13, 127, 0, 26)#(68, 100, 10, 127, 0, 26)
red_threshold   = (68, 100, 10, 127, -5, 26)

同时这里的一个区别在于黑色矩形的位置需要重新标定,这里其实我们依然可以采用上一问的策略,手动标定矩形坐标,因为此时矩形的位置是可以自己决定的,但是考虑到第四问是由老师来决定位置,第三问我们使用了一个矩形识别的函数,找到矩形四个角点的坐标,取代第二问的四个角点坐标,然后再跑一次,寻找矩形的函数如下:

def find_maxr(rects):
    max_size=0
    for rect in rects:
        x,y,w,h=rect.rect()
        if w*h > max_size:
            max_rect=rect
            max_size = w*h
    return max_rect

def find_max_rect(img):
    rects=img.find_rects(threshold = 10000)
    if rects:
        max_rect=find_maxr(rects)
#        img.draw_rectangle(max_rect.rect(), color = (255, 0, 0))
        point=max_rect.corners()
        return point[0],point[1],point[2],point[3]
    else:
        return (-1,-1),(-1,-1),(-1,-1),(-1,-1)



def find_rect_x_y():
    global RT_x,RB_x,LT_x,LB_x,LT_y,RT_y,LB_y,RB_y
    img = sensor.snapshot().lens_corr(strength = 1.8, zoom = 1.0)
        # 下面的`threshold`应设置为足够高的值,以滤除在图像中检测到的具有
        # 低边缘幅度的噪声矩形。最适用与背景形成鲜明对比的矩形。
    [[LB_x,LB_y],[RB_x,RB_y],[RT_x,RT_y],[LT_x,LT_y]]=find_max_rect(img)
    LT_x=LT_x+3
    LT_y=LT_y+3
    RT_x=RT_x-3
    RT_y=RT_y+3
    RB_x=RB_x-3
    RB_y=RB_y-3
    LB_x=LB_x+3
    LB_y=LB_y-3
    if RT_x-LB_x<=150 and RT_x-LB_x>=50 and RB_y-LT_y<=150 and RB_y-LT_y>=50:
        print(LT_x,LT_y,RT_x,RT_y,RB_x,RB_y,LB_x,LB_y)
        rects=img.find_rects(threshold = 10000)
        max_rect=find_maxr(rects)
        img.draw_rectangle(max_rect.rect(), color = (255, 0, 0))

        return True
    else:
        return False

其实有点类似寻找最大色块。

第四问

第四问是本次比赛最难的一个题目,我们也是因为一念之差没有做出来这一道题,首先因为矩形变为了斜放置,对点位寻找和舵机的精度要求就变高了许多,因此下一个位置不能简单的设置为下一个角点,而是将红点当前位置下一个位置之间的连线分段,将每一个小段的点作为下一个位置,这样虽然会使得舵机的移动速度变得很慢,但是会相对不容易出界,同时这里我就要说出最终没能跑出最后一道题目的原因,这里粘一个链接来更好的理解:
透视原理,侵删
因为openmv的矩形函数只能够找到外接矩形,因此如果直接使用这些求出的坐标会导致红光一直沿着外边沿移动,一定会导致出界,因此需要对这个坐标进行处理,但是因为透视原理的存在,不同角度下处理的坐标是不可能一致的,但是我们使用了一种固定的矫正方式,因此导致在最终测试过程中角度变化没能做出第四问。
这个问题的解决方式也很简单,就是透视变换,在B站上有up主使用第二问的矩形边框作为变换的基准,这是完全可行的,具体大家可以从B站上找一找。因为未能及时发现问题,因此抱憾了。
这里贴上寻找斜矩形的代码:

def find_quad_x_y():
    global RT_x,RB_x,LT_x,LB_x,LT_y,RT_y,LB_y,RB_y
    img = sensor.snapshot().lens_corr(strength = 1.8, zoom = 1.0)
        # 下面的`threshold`应设置为足够高的值,以滤除在图像中检测到的具有
        # 低边缘幅度的噪声矩形。最适用与背景形成鲜明对比的矩形。
    [[LB_x,LB_y],[RB_x,RB_y],[RT_x,RT_y],[LT_x,LT_y]]=find_max_rect(img)
    if RT_x-LB_x<=150 and RT_x-LB_x>=50 and RB_y-LT_y<=150 and RB_y-LT_y>=50:
        LT_y=LT_y+3
        RT_x=RT_x-3
        RB_y=RB_y-3
        LB_x=LB_x+3
        print(LT_x,LT_y,RT_x,RT_y,RB_x,RB_y,LB_x,LB_y)
        rects=img.find_rects(threshold = 10000)
        max_rect=find_maxr(rects)
        img.draw_rectangle(max_rect.rect(), color = (255, 0, 0))
        return True
    elif RB_x-LT_x<=150 and  RB_x-LT_x>=50 and  LB_y-RT_y<=150 and LB_y-RT_y>=50:
        LT_x=LT_x+3
        RT_y=RT_y+3
        RB_x=RB_x-3
        LB_y=LB_y-3
        print(LT_x,LT_y,RT_x,RT_y,RB_x,RB_y,LB_x,LB_y)
        rects=img.find_rects(threshold = 10000)
        max_rect=find_maxr(rects)
        img.draw_rectangle(max_rect.rect(), color = (255, 0, 0))
        return True

    else:
        return False

拓展

拓展部分就很简单了,因为只需要考虑跟随问题,因此只需要不停的计算当前红点位置绿点相对位置的偏差就可以了。
代码贴在下面:

def get_red_green_error(img):
    global green_x,green_y,red_x,red_y,rg_err_x,rg_err_y,error_flag
    red_x,red_y=find_red_point(img)
    if red_x!=-1 and red_y!=-1:
        rg_err_x=green_x-red_x
        rg_err_y=red_y-green_y
        error_flag=0
        return True
    else:
        rg_err_x=0
        rg_err_y=0
        error_flag=1
        return False

总结

这次电赛是我本科生涯的最后一次比赛,因为疫情结束第一年线下的原因,今年想要取得好名次还是很容易的,我们学校E题共有三个省一,他们都是将第四题成功做了出来,做后在国赛中两个国一,一个国二,所以说最痛苦的不是没有得奖,而是差一点国奖(答主实际距离省一只有三支队伍),当然这也怨不得别人,确实是在比赛中没有发现问题,导致最终比赛没有做出来也是意料之中,因此希望不管做任何的工科比赛,最重要的就是要保证鲁棒性,一位位我看到很多同学明明自己测试咋样都没有问题,但是一到赛场上就意外不断,这都是鲁棒性不足导致的。
差不多就说这么多了,有机会了可以把智能车的代码和硬件也开源一下,如果想了解的同学可以私戳我,欢迎交流和指出问题。

  • 6
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值