2017年2月25日

用moodle檢核表來控制實驗流程

過去帶實驗的時候,要教學生一些物品或是活動後的物件收拾,總是會花去很多時間。就拿使用顯微鏡觀察標本的活動來說,最後要請學生收或清洗蓋玻片載玻片、把東西放到定位等等。如果是一次把要做的事情吩咐完,那麼學生就只會記得一開始講的吩咐,或是最後一個吩咐。不然就是要說一件事完後,再做一件事,但這樣就要等每一組都完成了,才能進行下一件事。

要解決這樣的方法,那就可以用檢核表來完成,像是黑板上寫了一條一條的步驟,讓學生看著黑板一步一布完成,而這個檢核表在行動學習裡也能做好。

moodle的活動模組裡有一個「檢核表」

0



因為檢核表的目的是讓學生自己勾選自己完成的項目,所以要讓學生的畫面能正常呈現「進度列」,因此誰可以更改的這個選項,就要選擇「限學生」
5




檢核表的設計,可以在moodle一筆一筆輸入,或是在試算表裡打好,再用moodle預設的格式匯入。

moodle的設計畫面可以調整左右階層的位置,還可以改變字的顏色

4



用試算表的話,就是這樣的格式,我是先在moodle上隨便敲兩筆,然後匯出看它預設格式是什麼,就用它的格式來打完。
  • A欄是名稱
  • B欄調整字的縮排,像我是把標題都設定為0,流程步驟設定為1。這樣標題會比較往左突出
  • C欄設定為0的話就是都是可勾選的,如果是1就是選填的,如果是2就是變成標題,只能看到資訊,但無法勾選。
  • D欄是設定時間
  • E欄是設定顏色的



6

特別要注意的事情是moodle是以UTF8編碼,所以匯出匯入的文字檔也都是UTF8編碼,但是windows的excel是預設用Big5編碼,所以沒辦法直接處理。

這有兩種解決方法:
  • 用excel處理完後,用notepad++改編碼為「UTF-8 without BOM
  • 或是用libreoffice來處理,這可以直接處理UTF8




匯入之後呈現的結果就是這樣,學生所看到的也就是這樣
2



我讓學生每組使用一台iPad,由組長登入moodle在活動前和活動後,利用這個線上檢核表勾選進度。組長需看檢核表內容之後,念給同組的人聽,並指導組員完成那個條目的進度之後,才能勾選進度。而我在教師界面可以看到各組組長的進度,這樣就能確切掌握各組的進度。

比起過去由老師口述進行步驟,這樣的各組自行線上檢核的方法讓實驗流程順利很多呢,先做完的組,也能先進行後續流程,而不須等待其他組。

1

2017年2月19日

線上共用白板AWWAPP複習生態系

前幾天要給九年級學生複習生態系,就想他們分組去整理各個生態系,突然靈機一動,想看看有什麼線上服務可以使用。

找到了一個可以線上共用的網路白板 AWWAPP,可以免費使用(也有付費版本)。


學生進入網站後,就會進入新的白板,如果要同組共用,就只要用Invite to Board,就可以秀出這個白板的連結還有QR CODE。因此我先讓組長先各自開一個白板,再把白板共用給組員們,而同時組長也把連結透過Google表單回傳給我,我就可以直接掌握各組繪圖的進度。

學生在這個課中活動中,每組分配一個生態系,每人要畫出兩種以上的生物,並且要寫出一種此生態系的特色。而組員合作要做的事情,就是要把生物之間的關係連線或是寫出來,還有畫出環境特色。

因為是線上共用的白板,所以同組內的繪圖彼此也都看得見進度,甚至要擦掉別人的畫面也行,因此組員之間的溝通非常重要。
IMAG3183 IMAG3174 IMAG3179


在學生平板上使用AWWAPP,是沒有辦法放大和縮小的,也就不知道全貌是怎樣的,不過平板上可以自由平移畫面。

如果是用電腦的話,就可以透過瀏覽器的縮小畫面,呈現出整個白板的繪圖狀況。


由於是透過網路連線在繪製,而上週學校連出的網路又有點問題,所以有發生A學生畫的畫面沒有呈現B學生的畫面。但如果網路都正常,這些問題就會減少很多。

免費版本的白板無法永久保存,沒有動作的情況,兩小時後會失效。所以我會把學生完成的白板再存圖下來,這有內建的[download image]可以使用。

學生製作完白板圖之後,我會再請學生報告圖中的初級消費者、次級消費者等。報告的模式,一種是用我電腦呈現的完整圖,另一種就是讓學生的平板透過air server投影到教師機,學生再利用平移畫面的方式來報告。

B3森林



6





5

3

2017年2月10日

用python爬蟲爬科教館的歷屆科展資料

科展群傑廳是科教館將歷屆科展作品集結起來的資料庫型網站,什麼時候建置的?我只能確定是2008年以後,因為在那之前,我還特別去把生物科展的資料撈出來做成一個網頁連結。(見此文章:來客全國生物科展總匯吧


最近幾年科教館的網頁已經有更新了,搜尋速度快很多了
科展群傑廳建置之後,搜尋資料應該都很順利了吧?是有比以前好,但是還是有很多可以改進的地方。

首先是伺服器的處理速度,我用Google的PageSpeed Tools對科展群傑廳的分類連接進行分析,伺服器要花近10秒的時間,才能載入頁面。預設每個搜尋結果都是呈現10筆資料,每次換下一頁,又要花上近10秒的時間。如果搜尋時選擇全網站搜尋(全國科展和國際科展都一起找),搜尋時間又會花上更久,例如我搜尋「水蘊草」,則要花21.5秒才能呈現第一頁的結果。

第二是顯示模式,搜尋結果預設是顯示摘要的前100字,但是許多科展都沒有把摘要寫好,可能前五十個字都是廢話,因此還得再一一點入,才能看到比較完整的摘要。


每次讓學生上去查東西,都要花上很久的時間,實在太折騰人了,所以我決定了!
就寫一個python爬蟲去把頁面呈現的資訊全部都爬下來,像是標題、作者、摘要、連結...那些。(code就放在本篇文章最底下,是給我自己看的)

我把全部的科展資料都爬下來了,放在雲端硬碟上,你可以直接用搜尋的功能找到想要查的關鍵字。你也可以下載之後,用excel開啟後進行自動篩選或搜尋,速度都會比你在網站上找要快。當你找到要細看的文章時,最後一個欄位是檔案連結位置,可以直接點選或利用瀏覽器下載。

在雲端硬碟上的歷屆科展彙整連結 科展資料彙整 (至2023)





以下是技術細節:

2024.09.01更新
科教館網頁架構改變,所以重新寫程式抓取2020-2023年的,然後跟舊的合併了。現在就是不是抓sid,而是在每年的架構下,先把該年所有的頁面連結爬出來,然後再爬資料出來。


在科展群傑廳的資料庫裡,每一篇作品都會有一個sid變數(隨便點一篇科展的頁面,看網址列的最後一個變數就是)。所以我就是用sid從1去抓到最後一筆,目前sid用到最大是 13353,所以等到今年(2017)的科展作品也上線時,我就只要從13354繼續往上抓,就可以找到更新的資料了。
2020.0426更新
目前已加入了2019年的資料,最後一筆的sid是16132。下次抓再從16133開始抓



我使用的電腦環境是linux,所以我要搜尋科展資料的話,還可以用命令列來搜尋或是直接下載檔案。科展資料彙整的檔案,我存檔為data.csv

如果只是要搜尋關鍵字,可以用grep 去找關鍵字的科展作品
cat data.csv |grep "水蘊"

如果同時要找兩個關鍵字的交集,就只要把前一個關鍵字的搜尋結果,再用pipeline送給第二個grep
cat data.csv |grep "水蘊"|grep "聲音"

如果想要把搜尋出來的結果格式化成為比較容易閱讀的樣子,可把搜尋結果 pipeline到awk
cat data.csv |grep "水蘊"|awk -F"," '{print  "================\n",$2,"\t",$4,"\n================\n",$12,"\n\n" }
結果就會像這樣


搜尋關鍵字後想要一次下載全部有關的作品說明書,可以用awk 把wget的命令寫出來,再送給bash去執行。wget 的-O參數是指定下載的檔案名稱為科展作品的標題,所以這段小程式自動會將pdf檔改名為該科展的標題。
cat data.csv |grep "水蘊"|awk -F"," '{print "wget " $13 " -O " $2".pdf"}' |bash



python 3.0的code如下,之後使用上要特別修改的地方,就是用藍色標示的文字部份。


最底下淺灰色是舊網頁的抓法,已經淘汰。因為2020年時,網頁架構不一樣了,新的抓法如下
=====================


#!/usr/bin/env python
#-*- coding: utf-8 -*-
from bs4 import BeautifulSoup
#import urllib.request
from requests import get

def getData2(num):
    print(num)
 
 
    url_sid = num
    url_site = 'https://www.ntsec.edu.tw/Science-Content.aspx?sid=' 
    url = url_site + str(url_sid)


    page = get(url)
    #print(response.text)

    soup = BeautifulSoup(page.text, "html.parser")
    #s = soup.title.text


    #檢查是否error

    #如果Oops,代表沒有這份資料
    if 'Oops' in soup.p.text :
        print('no data')
        return
 

    #標題
    title = soup.find(class_ = 'pageTitle')
    title = title.get_text()
    #print(title + ',')
 
    line = str(num) + '\t'
    line = line + title + '\t'

    #id
    contents = ['ctl00_cphMainContent_ddScienceCategory',
                'ctl00_cphMainContent_ddScienceNumber',
                'ctl00_cphMainContent_ddSubject',
                'ctl00_cphMainContent_ddAward',
                'ctl00_cphMainContent_ddSchool',
                'ctl00_cphMainContent_ddTeacher',
                'ctl00_cphMainContent_ddAuthor',               
                'ctl00_cphMainContent_ddKeyWord',
                'ctl00_cphMainContent_dtNote']

    for content in contents:
        temp = soup.find(id = content)
        #國際科展有些會缺指導老師的資料,另外大多數科展會缺少備註欄位
        if temp != None :
            temp = temp.get_text()
            temp = temp.replace('\n',',')
            temp = temp.replace('\t',',')
        else:
            temp = ''
        #print(temp + ',')
        line = line + temp + '\t'



    #摘要
    abstract = soup.find(class_ = 'pagePanel__title')
    abstract = abstract.find_all_next('p')
    abstract = abstract[0]
    abstract = abstract.get_text()
    abstract = abstract.replace('\n','')

    #print(abstract + ',')
    line = line + abstract + '\t'
 
    #pdf link
    for link in soup.findAll("a", { "id" : "ctl00_cphMainContent_rptAtt_ctl00_aAttFile" }):
        link = "https://www.ntsec.edu.tw/" + link['href']
        #print(link)
        line = line + link
             
    line = line + "\n"
    return str(line)

file = open("data_16000-.csv","a")
for num in range(16000,17000,1):    
    line = getData2(num)
    #print(line)
    if line is not None:
        file.write(line)
file.close()

=====================

#!/usr/bin/env python
#-*- coding: utf-8 -*-

from bs4 import BeautifulSoup
from urllib.request import urlopen


def getData(url2):
    print(url2)
    file = open("data-6000.csv","a")  
    line = str(url2) + ","
 
    url1 = 'http://science.ntsec.edu.tw/Science-Content.aspx?sid='  
    url = url1+str(url2)
    page = urlopen(url)

    soup = BeautifulSoup(page, "html.parser")
 

    #如果網頁標題有Error,代表沒有這份資料
    if "Error" in soup.title.text :
        return

    #印出網頁編碼
    #print(soup.original_encoding)

    #印出科展標題
    for science_title1 in soup.findAll("div", { "class" : "science_title1" }):
        title = science_title1.contents[0]
        title = title.replace(" ", "")
        title = title.replace(",", ",")
        title = title.replace(";", ";")
        title = title.replace("\"", "")
        title = title.replace(r"\n", "")
        print("標題",title)
        line = line + title + ","
        #如果科展標題是評語有"評語",代表不是科展
        if "評語" in title :
            return

    #印出基本資料的欄位名稱和內容
    scienceContent1 = soup.findAll("li", { "class" : "scienceContent1" })
    scienceContent1s = soup.findAll("span", { "class" : "scienceContent1s" })
 
    #如果科展類別的字數小於3,代表無此資料,則跳出副程式
    if len(scienceContent1s[0].contents) == 0 :
        return


 
    for dataName,data in zip(scienceContent1,scienceContent1s):
        dataName = dataName.contents[0][0:-3]
        dataName = dataName.replace(" ", "")
        dataName = dataName.replace(" ", "")
     
        #處理沒有得獎的科展
        if len(data) == 0:
            data = ""
        else:
            data = data.contents[0]
            data = data.replace(" ", "")
            #data = data.replace(r"\r", "")
            data = data.replace(",", ",")
            data = data.replace(";", ";")
        print(dataName,data)
        line = line + data + ","
    #印出摘要
    #abstractName = soup.findAll("div", { "class" : "science_title2" })
 
    for abstract in soup.findAll("div", { "class" : "scienceContent2_font" }):
        abstract = abstract.text
        abstract = abstract.replace("\n", "")
        abstract = abstract.replace(r"\r", "")
        abstract = abstract.replace(",", ",")
        abstract = abstract.replace(";", ";")
        abstract = abstract.replace('^M', "")
     
        print("摘要",abstract)
        line = line + abstract + ","
    print("===")

    for link in soup.findAll("a", { "id" : "ctl00_cphMainContent_aDownload" }):
        #如果連結有正確的話,才加入連結
        if "href" in str(link):
            link = link['href']
            link = "http://science.ntsec.edu.tw/"+link
            print("連結",link)
            line = line + link
    line = line + "\n"
 
    file.write(line)
    file.close()
for url2 in range(5832,6001,1):
    getData(url2)


2017年2月1日

python+openCV電腦視覺即時分析車輛軌跡,偵測車速並拍照

去年11月寫了一篇《python+OpenCV做車輛追蹤》,分析定點攝影拍到的車流影片。不過之前的作法只有作到背景萃取出車輛的動態影像,對於車輛的計數是尚未作到的。

雖然openCV可以用cv2.findContours把影片中的每一幀中動態的部份分析出輪廓,得知每一幀中有多少移動的車輛,並且給予編號。但是同一輛車在前後幀的編號不一定相同的。

比如說第一幀有3輛車,分別給予編號123,到第二幀的時候,第一輛車已經消失在畫面,而有新的一輛車進入畫面。雖然前後兩幀的車輛數目都一樣,但是原本在第一幀的二號車,到第二幀時,就變成一號車了。

要讓電腦知道不同幀的車輛是屬於同一輛,可以利用一些演算法來做,像是knn、kmeans、meahshift、camshift、kalman filter等等。

不過我先用另外一種方式來做看看,因為我目的就是想把影片中的每一輛車擷取出來,所以我就在畫面中設定一個區域,只要車輛的輪廓進入這個區域,就把這個輪廓截圖出來。判定是否進入區域的方法,就是比較車輛的質心(或是其外框的一個角)是否在區域的範圍內。

這個區域的大小是一個重要因素,區域設得大,那麼同一輛車在不同幀的時候,可能都會落入這個區域,以至於同一輛車可能會被截圖二到三張。如果設得小,那麼某些開得快的車輛,可能會進不到這個區域(前一幀在區域的右邊,到下一幀已經到區域的左邊了)

雖然有這些缺點,但是還是可以滿足部份的需求,就至少可以把車輛截圖都抓取出來。以下就是某一分鐘內的車輛影像,白色或淺色的車輛佔比較高的比例呢。


python+OpenCV的車輛追蹤



後來我想到另一個方式讓電腦在不同幀中也能認出同一台車,原理就跟我們看車是類似的,如果給我們看兩張間隔一秒的靜態車流影像,我們怎麼認出兩張影像中的車輛是同一台呢?我們是藉由車輛的顏色、外型、位置去判斷的。

既然如此,那麼我就用位置來做吧。原理是把這一幀的車,跟上一幀的每一台車去比較位置,當位置差異最小而且在某一個範圍內,我就可以把不同幀的兩台車視作同一台車。

用程式實做出來,當然是可行,不過還是有一些問題。比方說一台車在某些幀的位置沒有被抓到(或是被抓歪了),那麼下一幀就沒辦法延續下去了。或是兩輛車在背景萃取的時候,因為太靠近,因此被視作同一部車,那麼到下一幀時,其中一部車也會無法延續下去。

以下影片就是實做出這樣的功能,藉由兩幀之間的位置變化,可以估算車速,也同時將車輛截圖下來,並且計算車輛數目。



雖然程式上的限制,會讓車輛數量被低估,但至少一些基本功能都已經有了。其實會想做這樣的事情,原因是十年前一個學生的科展需求。當初那學生的科展中有一部份要用到道路的車流量資料,那時她的作法就是週末花一些時間去站在路口算車子,當時我就很想看看是不是有其他方式可以達成目標,過了十年,總算是完成了(部份)。







定點攝影裡的頭前溪火災

前兩天一早看見窗外的頭前溪發生火災,河床上盡是煙霧和明火,不知道起火的原因是什麼,我推測是有人放鞭炮引起的吧。
DSC00647


DSC00648 DSC00645

野火燒到隔天,剩下一些灰燼,還有一些餘存的煙
DSC00650

擺一台定點攝影的攝影機好處就在這,可以用歷史紀錄去追溯出到底起火點在哪,
頭前溪野火起火點



也能夠將火災前後的畫面互相對比,看到燒過的範圍有什麼改變。可惜這部份的畫面太小,不然未來可以建立一系列連續的變化,觀察河床上的植被在火災後演替的變化情形。
頭前溪野火前後