小松鼠嚇了一跳,有了魔法眼鏡後,這世界看起來完全不一樣了

顯示具有 python 標籤的文章。 顯示所有文章
顯示具有 python 標籤的文章。 顯示所有文章

2014年12月19日 星期五

Leap motion 擴增實境


利用 OpenCV 校正攝影機以及 Leap Motion Sensor 的位置,再利用 Three.js 來達成擴增實境的效果。

這是試玩的結果(還是跟 IPython notebook 一起探索),所以並沒有以最佳效果為目標。實際上 leap motion sensor 可以放在比較好的位置。

稍早拿來和 google hangout api 配合的效果。

原理上不難,只是中間要解決一堆技術上的小麻煩。

2014年11月30日 星期日

用 Spynner 來抓 8Comic 的漫畫 (4): 多線程


平行化的好處

之前抓檔案的方式是,
  1. 用瀏覽器抓 .html,找到圖片 url。
  2. 下載圖片
  3. 換下一頁,跳到第一步。
但是網路可以同時開好幾個連線。所以我們要利用這點來加速。我們的策略是,
  1. 用瀏覽器抓 .html,找到圖片 url。
  2. 把圖片 url 丟進一個 Thread Pool 裡面(平行下載,但是不等待)。
  3. 換下一頁,跳到第一步。
當然也有其他的方式平行化,比方連抓 .html 的工作也一併丟入 Thread Pool。不過這樣代表要多開好幾個 browser,而且相對來說,抓網頁會比抓圖片快,所以我們選擇上面的方式。

import 將會用到的 module

因為使用 python 2.7, 我們利用 urllib2 來抓圖, ThreadPool 來 multi threading 平行處理。
In []:
import spynner
import os, sys
from PyQt4.QtWebKit import QWebSettings # 用來設定 QtWebKit
from PyQt4.QtNetwork import QNetworkAccessManager, QNetworkRequest # 控制 browser 的網路連線
from PyQt4.QtCore import QUrl # Qt 的 Url 類別

# 下面是新增的兩個 module
import urllib2
from multiprocessing.pool import ThreadPool

# 下面是 IPython 相關
from IPython.display import display, Image
from IPython.html.widgets import ImageWidget, IntProgressWidget

建立瀏覽器

這部份一樣
In []:
# 建立瀏覽器
browser = spynner.Browser(debug_level=spynner.ERROR, debug_stream=sys.stderr)

# 建立一個 webview
browser.create_webview()
settings = browser.webview.settings()
# settings.setAttribute(QWebSettings.AutoLoadImages, False)
settings.setAttribute(QWebSettings.JavaEnabled, False)        # 不需要  Java
settings.setAttribute(QWebSettings.DnsPrefetchEnabled, True)  # 試著節省 Dns 花的時間
settings.setAttribute(QWebSettings.PrivateBrowsingEnabled, True) # 不需要瀏覽紀錄或者 cookie

# 建立一個空的  url
BLANK_REQUEST = QNetworkRequest(QUrl())
# 建立一個空的圖片 url
DUMMY_IMG_REQUEST = QNetworkRequest(QUrl("data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs="))

# 客製化的 NetworkAccessManager
class EightComicNetworkAccessManager(QNetworkAccessManager):
    # 只需要取代  createRequest 這個 method 即可 
    def createRequest(self, op, request, device=None):        
        url = str(request.url().toString()) # 參數很多,但只取 url 就夠用        
        if 'comic' not in url[:20]: 
            # 用很醜的方式來判斷非 8comic 網站的 url 
            # 用空的 url  取代原本的 url
            return QNetworkAccessManager.createRequest(self, self.GetOperation, BLANK_REQUEST)
        elif not url.endswith('js') and not url.endswith('css') and '.html' not in url:
            # 凡是  .js .css .html 之外的,都用空的圖片 url  取代原本的 url
            return QNetworkAccessManager.createRequest(self, self.GetOperation, DUMMY_IMG_REQUEST)
        else:
            # 傳回原本的 url
            return QNetworkAccessManager.createRequest(self, op, request, device)

# 設定  browser 的 NetworkAccessManager
browser.webpage.setNetworkAccessManager(EightComicNetworkAccessManager())

設定 Widget

增加一個 Progress Bar, 分別來顯示分析過的 .html 數字以及已經下載的圖片數
In []:
browser.show()
# 漫畫的網頁
base_url = 'http://new.comicvip.com/show/cool-5614.html?ch='

# 要下載第一本
book_no = 1

# 取得總頁數
browser.load(base_url+str(book_no))
total_pages = browser.runjs('ps').toInt()[0] 

# 建立 Image Widget 用來顯示圖片預覽
img = ImageWidget()
img.set_css("height", 300) # 讓圖片不要太大

# 顯示下載進度的 Progress bar
html_progress = IntProgressWidget(min=1, value=1, max=total_pages)
img_progress = IntProgressWidget(min=1, value=1, max=total_pages)

# 顯示 Widget
display(html_progress)
display(img_progress)
display(img)

利用 ThreadPool 來下載

另用 ThreadPool 來達成 multithreading (多線程) 即為容易,只要將想丟進 pool 的程式碼包進函數裡面即可。
In []:
# 建立一個下載目錄
dir_name = "download/{:02d}".format(book_no)
if not os.path.exists(dir_name):
            os.makedirs(dir_name) 
print "Download to {}/{}".format(os.getcwd(), dir_name)
sys.stdout.flush()

# 建立 ThreadPool, 5 條 thread
pool = ThreadPool(5)
        
# 開始下載
downloaded_images = 0
for page in range(1, total_pages+1):
    # 取得 image url
    browser.load("{}{}-{}".format(base_url, book_no, page))
    img_url = str(browser.runjs('document.getElementById("TheImg").getAttribute("src")').toString())

    # 將下載圖片的工作包成 save_img,推進 pool 裡
    def save_img(img_url, page):
        global downloaded_images
        fn = "{}/{:03d}.jpg".format(dir_name, page)
        data = urllib2.urlopen(img_url).read()
        with open(fn, "wb") as f:
            f.write(data)
        # 更新 widget 的狀態
        downloaded_images += 1
        img_progress.description = "img: %d/%d"%(downloaded_images, total_pages)
        img_progress.value = downloaded_images
        img.value = Image(filename=fn).data
    pool.apply_async(save_img, (img_url, page))
    
    # 更新 Widget 的狀態
    html_progress.description = "html: %d/%d"%(page, total_pages)
    html_progress.value = page

    # 等待所有任務結束
pool.close()
pool.join()
    

結果

到這裡,探索期正式結束,該有的技術已經完整。
有興趣的話,也可以實測比較一下有 multithreading 和沒有 multithreading 的差異。但這裡就發現我們需要封裝了,因為沒有封裝、整理好的關係,要測試兩種程式碼,必須要把程式碼寫兩遍,而無法共用相同的部份。
另外,程式碼裡面用到了 global 這個 keyword, 常常也代表我們需要封裝了。



用 Spynner 來抓 8Comic 的漫畫 (3): 節省頻寬


要解決的問題

  • 還是有文字廣告這樣不需要的流量來浪費頻寬和時間。
  • QtWebKit 的 QWebSettings.AutoLoadImages=false 有 bug,記憶體已用不還。

解法

如同這裡 http://stackoverflow.com/questions/21357157/is-there-any-solution-for-the-qtwebkit-memory-leak 以及相關的連結、討論,記憶體的 bug 目前找得到的解法就是定期砍掉 process 重新再開。
理論上,也可以深入 QtWebKit, 找到配置的記憶空間,然後手動釋放,順便將解法回給上游。這樣雖然是正解,但是與我們的主題無關。
既然無解,那要等 QtWebKit 修正 bug 之後再來抓? 太久。
定期砍 Process 重開? 可以,但太醜。
不用 QtWebKit, 改用其他套件來解? 可以,但其他套件可能有其他問題,人生不能一直逃避,會養成習慣的。
仔細思考,其實我們的目標是要讓 browser 不去抓不必要的網路資源,這個原則包含了圖片以及文字廣告,但不僅限於這兩個,還有像是 google 統計等等。
所以只要我們封鎖這些不必要的連線,看起來瀏覽器的圖片就不會被顯示了,不需要真的設定 QWebSettings.AutoLoadImages=false
比方可以設定 proxy,由 proxy 來控制網路資訊流。不過 QtWebKit 有幾個內建的功能,可以讓你控制網路的存取,比 proxy 更裡層一點。一個是 browser.webpage 的 acceptNavigationRequest, loadStarted, loadFinished. 這幾個搭配起來,你可以限制 browser 不抓取子 iframe。 但這個方法對我們來說,不夠用。我們想要控制更多,所以我們用另外一種方式,直接控制比較外層一點的 QNetworkAccessManager 。

一樣 import 所有我們將會用到的東西

In []:
import spynner
import os, sys
from PyQt4.QtWebKit import QWebSettings # 用來設定 QtWebKit
from PyQt4.QtNetwork import QNetworkAccessManager, QNetworkRequest # 控制 browser 的網路連線
from PyQt4.QtCore import QUrl # Qt 的 Url 類別

# 下面這行是 IPython 相關
from IPython.display import display, Image
from IPython.html.widgets import ImageWidget, IntProgressWidget

建立瀏覽器

In []:
# 建立瀏覽器
browser = spynner.Browser(debug_level=spynner.ERROR, debug_stream=sys.stderr)

# 建立一個 webview
# 我們不設定 AutoLoadImages=False, 但增加一些其他設定
# 這裡並不是重點,但適合我們的應用
browser.create_webview()
settings = browser.webview.settings()
# settings.setAttribute(QWebSettings.AutoLoadImages, False)
settings.setAttribute(QWebSettings.JavaEnabled, False)        # 不需要  Java
settings.setAttribute(QWebSettings.DnsPrefetchEnabled, True)  # 試著節省 Dns 花的時間
settings.setAttribute(QWebSettings.PrivateBrowsingEnabled, True) # 不需要瀏覽紀錄

建立一個 QNetworkAccessManager 子類別

當 browser.webpage 在要求網路資源前,會先詢問 QNetworkAccessManager 來確定要不要抓,或者怎麼來抓這個資源。 我們可以用 browser.webpage.setNetworkAccessManager 來指定自己客製過的 manager。
In []:
# 建立一個空的  url
BLANK_REQUEST = QNetworkRequest(QUrl())
# 建立一個空的圖片 url
DUMMY_IMG_REQUEST = QNetworkRequest(QUrl("data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs="))

# 因為只需要用一次,可以取個又臭又長的名字
class EightComicNetworkAccessManager(QNetworkAccessManager):
    # 只需要取代  createRequest 這個 method 即可 
    def createRequest(self, op, request, device=None):        
        url = str(request.url().toString()) # 參數很多,但只取 url 就夠用        
        if 'comic' not in url[:20]: 
            # 用很醜的方式來判斷非 8comic 網站的 url 
            # 用空的 url  取代原本的 url
            return QNetworkAccessManager.createRequest(self, self.GetOperation, BLANK_REQUEST)
        elif not url.endswith('js') and not url.endswith('css') and '.html' not in url:
            # 凡是  .js .css .html 之外的,都用空的圖片 url  取代原本的 url
            return QNetworkAccessManager.createRequest(self, self.GetOperation, DUMMY_IMG_REQUEST)
        else:
            # 傳回原本的 url
            return QNetworkAccessManager.createRequest(self, op, request, device)

# 設定  browser 的 NetworkAccessManager
browser.webpage.setNetworkAccessManager(EightComicNetworkAccessManager())

後面的程式碼都一樣

In []:
# 漫畫的網頁
base_url = 'http://new.comicvip.com/show/cool-5614.html?ch='

# 顯示瀏覽器,確認 browser 內容乾淨清爽
browser.show()

# 要下載第一本
book_no = 1

# 取得總頁數
browser.load(base_url+str(book_no))
total_pages = browser.runjs('ps').toInt()[0] 

# 建立 Image Widget 用來顯示圖片預覽
img = ImageWidget()
img.set_css("height", 300) # 讓圖片不要太大

# 顯示下載進度的 Progress bar
progress = IntProgressWidget(min=1, value=1, max=total_pages)

# 顯示 Widget
display(progress)
display(img)

# 建立一個下載目錄
dir_name = "download/{:02d}".format(book_no)
if not os.path.exists(dir_name):
            os.makedirs(dir_name) 
print "Download to {}/{}".format(os.getcwd(), dir_name)
sys.stdout.flush()

# 開始下載
for page in range(1, total_pages+1):
    # 取得 image url
    browser.load("{}{}-{}".format(base_url, book_no, page))
    img_url = str(browser.runjs('document.getElementById("TheImg").getAttribute("src")').toString())
    # 下載圖片
    fn = "{}/{:03d}.jpg".format(dir_name, page)
    with open(fn, "wb") as f:
        browser.download(img_url, outfd=f)
    
    # 更新 Widget 的狀態
    progress.description = "%d/%d"%(page, total_pages)
    progress.value = page
    img.value = Image(filename=fn).data

結果

和第一篇的結果比較,
的確清爽很多,廣告和圖片都消失了。速度也快了很多。其實 .css 檔案也可以不用抓,不過因為有 cache 的緣故, .css 和 .js 本來都只會抓一次,所以影響有限。
因為這一部分的探索已經告一段落,所以現在是封裝的時候。不過因為之後我們要介紹兩種不同的封裝方式,所以在這之前,我們要再做一件事情,那就是更加節省一點。
看起來不是我們不是已經夠節儉了嗎?只抓有需要的東西,沒有多抓任何東西。 也許 .html 也可以不用抓?沒錯,也許可以直接反推出每一頁漫畫圖片的 url,但即使這個例子可以,但一般來說,伺服器端完全可以將必要的資訊放在 .html 裡面,讓你必須要抓 .html 才能獲得必要資訊。 這系列的目的是介紹一個簡單的萬用抓資料方式,所以做到這裡就可以了。
那還有什麼可以節省的? 頻寬就這樣了,但是時間還可以節省。 下一篇介紹簡單的 multithreading 抓圖。
In []:



2014年11月29日 星期六

用 Spynner 來抓 8Comic 的漫畫 (2): 用 Widget 美化介面


已經可以抓了,還有什麼問題?

  • browser 瀏覽頁面時,已經顯示圖了。之後,又再 download 一次,浪費頻寬。
  • 介面不夠美觀,無法看到進度。

頻寬問題

概念上,有兩個方向。 一是既然 browser 顯示了圖片,表示 browser 有這份圖,我們跟 browser 要就好了。另一個剛好相反,告訴瀏覽器,不要顯示圖片,把圖片的 url 交給我們即可。
這兩個方向各有利弊,以現在這個應用來說,我選擇第二個。原因有三:

  •  QtWebKit 有選項讓你這樣做。 
  • 這樣可行。 browser 仍然會傳回正確的圖片 url。 
  • 可以順便擋住廣告圖片。

介面問題

因為我們用使用 IPython notebook,所以使用 IPython notebook 的 interactive widget。

接下來,一樣先 import 所有我們將會用到的東西

In []:
import spynner
import os, sys
from PyQt4.QtWebKit import QWebSettings # 用來設定 QtWebKit
# 下面是 IPython 相關
from IPython.display import display, Image
from IPython.html.widgets import ImageWidget, IntProgressWidget

再來是建立瀏覽器,並且設定不要載入圖片

In []:
base_url = 'http://new.comicvip.com/show/cool-5614.html?ch='
# 建立瀏覽器
browser = spynner.Browser(debug_level=spynner.ERROR, debug_stream=sys.stderr)

# 建立一個 webview,並且設定不要自動載入圖片
browser.create_webview()
settings = browser.webview.settings()
settings.setAttribute(QWebSettings.AutoLoadImages, False)

# 要下載第一本
book_no = 1

# 取得總頁數
browser.load(base_url+str(book_no))
total_pages = browser.runjs('ps').toInt()[0] 

再來是建立 Interactive Widget

In []:
# 建立 Image Widget 用來顯示圖片預覽
img = ImageWidget()
img.set_css("height", 300) # 讓圖片不要太大

# 顯示下載進度的 Progress bar
progress = IntProgressWidget(min=1, value=1, max=total_pages)

下載

跟之前一樣,不過外加進度顯示
In []:
# 顯示之前建立的 Widget
display(progress)
display(img)

# 建立一個下載目錄
dir_name = "download/{:02d}".format(book_no)
if not os.path.exists(dir_name):
            os.makedirs(dir_name) 
print "Download to {}/{}".format(os.getcwd(), dir_name)
sys.stdout.flush()

# 開始下載
for page in range(1, total_pages+1):
    # 取得 image url
    browser.load("{}{}-{}".format(base_url, book_no, page))
    img_url = str(browser.runjs('document.getElementById("TheImg").getAttribute("src")').toString())
    # 下載圖片
    fn = "{}/{:03d}.jpg".format(dir_name, page)
    with open(fn, "wb") as f:
        browser.download(img_url, outfd=f)
    
    # 更新 Widget 的狀態
    progress.description = "%d/%d"%(page, total_pages)
    progress.value = page
    img.value = Image(filename=fn).data

看起來還不錯?

這樣似乎看起來還不錯,沒有幾行程式碼,邊抓還能邊看 preview。 盯著朝著目標奔跑的 progress bar,有種莫名的療癒效果。而且沒有重複抓圖,省了不少頻寬。似乎探索期已經結束,接下來只要把這些程式碼封裝起來,方便重複使用就行了。
但仔細一看,還是有不少問題:

  •  如果用 browser.show() 打開瀏覽器,會發現還是有許多廣告被載入。我們希望這些廣告也不見。 
  • 用了一段時間之後,發現機器越來越慢,仔細一看,記憶體使用量大得驚人。相當詭異。 
第一個問題的成因很單純,有些廣告是文字廣告。第二個問題就比較棘手一點了, QWebSettings.AutoLoadImages 有 bug,會造成記憶體無法回收的問題: http://stackoverflow.com/questions/21357157/is-there-any-solution-for-the-qtwebkit-memory-leak 仔細查看討論之後,會發現這個問題目前基本無解,要等 Qt 解決。(不然就只能很醜的 fork,然後 close process。
那怎麼辦? 放棄這個方案? 碰到無解的問題,如果是駭客(hacker),要深入系統,修正 bug,釋放記憶體。身為自造者(maker),那要捨棄壞掉的系統,自幹一個。
但不過是抓個漫畫,這樣搞,未免太累了點。下一篇,我們要用廢客(faker)的方式來解決問題。那就是,假裝解決問題就行了。
In []:



2014年11月24日 星期一

用 Spynner 來抓 8Comic 的漫畫 (1): 基本技術



需要套件

  • Spynner (需要 PyQt4 或 PySide, autopy)
  • IPython notebook (因為這個範例是用 IPython notebook 示範,不然跳過 IPython 相關部份也行) 安裝 先安裝 Python, IPython notebook, PyQT4

在 mac 下(如果 autopy 安裝不起來):

  • 先安裝 Qt。 brew 的話, brew install qt 即可。
  • 安裝 PyQt 。 brew, pip 都行。
  • easy_install -N spynner
  • 在適當的地方, touch autopy.py,如 touch /usr/local/lib/python2.7/site-packages/autopy.py。假裝有 autopy 就行了,因為 autopy 其實用不到。

在 windows 下:

  • 安裝 python(x,y) 2.7
  • 接下來打開 IPython 然後輸入 !easy_install spynner(win8 可用搜尋找到 IPython)
  • 最後,打開 IPython notebook,按下 New notebook 開始。
In []:
# This is for windows 
# on linux, simply sudo easy_install spynner in command line
!easy_install spynner 
# restart the kernel

先 import 所有我們將會用到的東西

In []:
import spynner
import os, sys

# 下面這行是 IPython 相關
from IPython.display import display, Image
In []:

再來我們試試看建立瀏覽器

browser = spynner.Browser(debug_level=spynner.ERROR, debug_stream=sys.stderr)
如果看起來什麼事情都沒發生,那大概就對了。 spynner 已經在背景建立了一個 webkit 瀏覽器(叫做 browser)。
通常我們不需要 browser 真的被顯示出來,不過為了方便了解發生了什麼事情,我們先讓它能夠被顯示。
In []:
browser.show() # 告訴  browser,要它之後不要隱身
# 為了避免法律上的疑慮,這裡你要自己找到適當的 url,把 ???? 換掉
base_url = 'http://???.com/show/????-????.html?ch='  
browser.load( base_url+'1')
這時候,成功的話,一個瀏覽器會跳出來,顯示漫畫第 1 話的封面。
瀏覽器能夠改變大小,但是看來像是當掉一樣,沒有回應。
這其實是好事,因為我們希望能夠完全控制瀏覽器,所以先凍結它,再慢慢來蹂躪它。
接下來,我們要把封面圖的 url 抓出來。
In []:
browser.load_jquery(True)   #  spynner 內建有 jquery,用這個 method 載入,比較方便。
img_url = str(browser.runjs('$("#TheImg").attr("src")').toString())
print img_url
# 當然不用 jquery 也可以
img_url = str(browser.runjs('document.getElementById("TheImg").getAttribute("src")').toString())
print img_url
上面先用 runjs 跑 javascript 得到一個結果。
這個結果是一個 Qt (C++)物件,可能是數字、字串或者物件。因為我們知道我們要的是字串,所以用 .toString 讓他成為一個 Qt 字串。
最後,再用 str 轉成 Python 字串。

抓圖

知道了圖片的 url, 那要如何將圖片抓下來呢?
可以用 browser.download(img_url, outfd=fd) 直接下載到檔案裏面。
不過這裡先直接在 IPython notebook 裡面秀一下圖片。
In []:
# 直接顯示 url 看看
display(Image(url=img_url, width=200))
In []:
# 先用 browser 抓下圖檔內容, 然後顯示
display(Image(data=browser.download(img_url), width=200))
漫畫每一頁的 url 格式是 .......ch=M-N 其中 M, N 是數字, 分別是卷數及頁數, 所以現在我們只要知道有幾頁就行了。
一般來說,可以從 html 內容中找到資訊。 8comic 控制 UI 的 javascript 就有這個資訊了,我們直接利用。
一樣先用 runjs 得到 ps 這個 javascript 變數的內容, 然後轉成整數。
因為 toInt 的結果包含一些額外資訊,所以我們用 [0] 取出數字。
In []:
total_pages = browser.runjs('ps').toInt()[0] 
print total_pages
所以我們用一個迴圈把每一頁都抓下來吧
In []:
book_no = 1
for page in range(1, total_pages+1):
    browser.load("{}{}-{}".format(base_url, book_no, page))
    img_url = str(browser.runjs('document.getElementById("TheImg").getAttribute("src")').toString())
    print page, img_url
    display(Image(url=img_url, width=100))
    continue
    # 上面只是顯示每一頁的圖片
    # 如果你現在就想真的抓檔案下來, 把上面那個 continue 註解掉
    with open("{}-{}.jpg".format(book_no, page), "wb") as f:
        browser.download(img_url, outfd=f)
        print "File saved in", os.getcwd()

到這裡為止,基本的功能已經有了,下一篇將會討論一些細節問題。


題外話。
寫這篇一部份是因為「如何用 Python 抓網站內容」的詢問度一直很高,另一部分是因為發現  JComicDownloader 無法抓 8Comic 的圖。我看了一下,覺得這是一個不錯的例子。




2014年10月15日 星期三

Python 超級新手教學




暑假錄的 Python 入門教學影片,對象是程式語言的超級新手。所以進度非常的慢。

一些細節會省略,等著有人問的時候再回答。

但拍出來後,一直覺得效果還可以再改進,所以一直沒放上來。

但現在想想,還是先放上再說。內容還會陸續增加,短期目標是寫出一個可玩的遊戲。
有任何可以利用的地方,歡迎自行取用。



2014年8月27日 星期三

IPython notebook UI 自訂

嘗試利用 IPython Widget 和 JavaScript 來控制 IPython 的 cell 行為。
目前可以控制 cell 的高度還有模擬 vim, sublime, emacs 的 key binding。
程式碼在 http://nbviewer.ipython.org/github/tjwei/tjw_ipynb/blob/master/vim.ipynb
我目前用的 css 在 https://github.com/tjwei/tjw_ipynb/blob/master/custom.css
是從 Base16 Ocean Dark 改的。

2014年8月22日 星期五

fuse 練習: Asus 和 卡提諾檔案系統


拿到了幾張 ASUS WebStorage 的的 100G 體驗卡,想說就用用看, 我的 macbook 只有 100G 的 SSD,拿來當延伸空間不錯。 沒想到 OSX 上好像只有像是 Dropbox 那樣直接 sync 的軟體。於是就自己寫了一下,當成練習。目前是 read only,也不太穩。我猜中文檔名應該會掛,就像 MegaFS 一樣。
不過目前至少放放影片,聽聽音樂還算可以。

source code 放在 https://github.com/tjwei/AsusWebStorage
在寫這個之前,另外有一個練習用的  fuse 檔案系統 ck101fs, 可以把卡提諾論壇的圖檔 mount 成一個虛擬檔案系統。

source code 在 https://github.com/tjwei/ck101fs


2014年8月20日 星期三

用 IPython notebook 解 HITCON 2014 CTF


因為看到新聞上面說有六百多隊報名(後來看到是 1000 隊左右, 500 隊有分數),而且免費,所以就上去玩玩看。
由於最近在熟悉 IPython notebook,所以毫不意外的都是在用 IPython notebook 解題。
最後只解了七題,排在 50 。
一開始完全沒進入狀況,光在 vash 就卡了很久。看到 /home/vash/flag 的內容後,完全不知道這個內容要拿來做什麼。
等到再有時間的時候,已經是半夜,然後還是卡了一陣子,才發現原來 dashboard 最下面有個 textinput,用來輸入 flag 的內容。

題目還挺有趣的,除了一開始摸索到底要作什麼的挫折外(怪我自己不夠專心),還挺像是猜謎的。
相關的 ipynb 檔案可以參考 http://nbviewer.ipython.org/github/tjwei/tjw_ipynb/tree/master/ 中的檔案。

一些小心得如下:

  • 解 diagcgi 的時候,看了一下別人留下的痕跡,就發現了我的解法可能和很多人不太一樣。
  • 解 maze 時,利用 ipython notebook widget 圖形界面來畫地圖的感覺不錯。主要卡關的地方在於 pyte 的相容性不足,導致跑到某個點的時候,地圖會解讀錯誤。
  • Puzzle 看來挺有趣的。但因為不想再重新讀一遍 jpeg 的規格,就抓一個 win32 jpeg 修復器 shareware 來修復(用 wine 來跑)。修復之後的圖片,中間有一塊黑色區塊。一直參不透玄機。所以就沒有完成。 後來才發現原來中間的黑塊是 shareware 造成的。後來改用 nanojpg 來完成。
  • 24 一開始想偷懶,上網找別人寫好的解答。很不幸,找到的解答犯了一個常見的錯誤,沒辦法解 12,12,12,10。 只好自己再寫一次。而且很有經驗的使用有理數來保證數學上的正確性。很不幸的是,題目的思考架構不同,所以只好又再改回浮點數,程式就變得很醜。



2014年8月8日 星期五

用投影片修正影片


PyCon APAC 2014 錄的一些影片,效果並不好,於是想是否能利用講者給的投影片來修補看看。
關於圖形對比,有很多方法可以選, OpenCV 裡面就內建了一些。
不過由於原始影像的品質實在太差,用找關鍵點的方式似乎需要調整,因此最後由於時間以及精力上的限制,就拿 ipython 用了非常低科技的線性代數來處理。
最後的程式碼為

基本上的步驟如下:
  • 解開投影片 pdf 成為一系列 .png 檔案。 雖然 python 有  wand module,不過這裡直很無恥的接呼叫 ImageMagick 的 convert 來解。
  • 找出投影片的座標,這裡用 ipython 寫出一個手動工具,找出部份影片內容和投影片來手動對齊。
  • 將對齊後的影片和投影片轉成 256x256 灰階向量,平移及正規化之後取內積。用內積來決定相似度。256x256 這個大小是一開始隨便選一個夠大的數字,其實可能 128x128 即可。
  • 相似度的閥值由之前的手動工具可以大致估計出。不用很準,依照不同情形可以是 0.5 - 0.95 中間的某個值。
  • 最後直接逐格比對,適當時,用投影片取代原來內容,然後利用 OpenCV 把影片壓回去。
  • 因為 OpenCV 不能處理聲音,所以用 libav 把原來的音軌和處理過得視訊直接合在一起。雖然 python 也有 libav 的 module,不過我不確定怎麼用這個 module 做 copy stream 的功能,所以還是很無恥的直接呼叫 avconv 來處理。
 以下對比原始影片及處理過的影片
Toomore 的 GRS 演講錄影算是我的「動機」,你可以看看這個動機能不能說服你
原來文字完全糊成一團
這是一開使用來試驗的對象,原本直接方差,效果還不錯。

海總理的演講,錄影品質也不行,這是第二個處理的影片。

海總理的影片,處理效果也不錯,不過第一章投影片似乎字體大小有改過,所以抓不到

Cheng-Lung Sung 的影片又是令一個問題,跟其他影片不一樣,不事由投影機訊號直接錄的。
原本想利用 OpenCV 的 Perspective Transformation 來修改互動工具,但沒想到原本的工具看起來堪用。






Andy 的影片原始畫質就還可以,不過利用投影片,還是可以讓畫質由還可以變成完美。
但一來原本畫質就還可以,二來投影片有一些動畫,所以閥值設定的比較高。


駱勁成關於立院投票指南的演講

跟其他有些不同的地方在於,替代的投影片大小放大為全螢幕。



最開始其實是打算用 Feature Matching 的方式,但因為一開始實驗的對象是最模糊的兩個演講,發現不靈。所以放棄這招。不過對於比較清楚的影片,其實 feature matching 看起來效果還不錯。

處理之後的影片效果可以參考