python CV 趣味項(xiàng)目 答題卡識(shí)別

英文原文來(lái)自 Bubble sheet multiple choice scanner and test grader using OMR, Python and OpenCV

說(shuō)到答題卡砚婆,滿滿的都是學(xué)生時(shí)代的回憶叉钥。本文實(shí)現(xiàn)了利用Python的計(jì)算機(jī)視覺(jué)和圖像處理技術(shù)實(shí)現(xiàn)圓點(diǎn)答題卡識(shí)別敛惊。代碼簡(jiǎn)潔,原理清晰荒澡,富有趣味姨蟋。感謝英文原作者屉凯,他的代碼和測(cè)試圖片我放在了文末。

光學(xué)劃記符號(hào)辨識(shí)(OMR

OMR結(jié)果

本文綜合了一些博文的技術(shù)芬探,包括building a document scanner神得,contour sorting以及perspective transforms

實(shí)現(xiàn)答題卡識(shí)別的7步

  • Step #1: 檢測(cè)到圖片中的答題卡
  • Step #2: 應(yīng)用透視變換來(lái)提取圖中的答題卡(以自上向下的鳥(niǎo)瞰視圖)
  • Step #3: 從透視變換后的答題卡中提取 the set of 氣泡/圓點(diǎn) (答案選項(xiàng))
  • Step #4: 將題目/氣泡排序成行
  • Step #5: 判斷每行中被標(biāo)記/涂的答案
  • Step #6: 在我們的答案字典中查找正確的答案來(lái)判斷答題是否正確
  • Step #7: 為其它題目重復(fù)上述操作

算法實(shí)現(xiàn)

讓我們新建一個(gè)Python文件test_grader.py,然后添加以下內(nèi)容:

# 引入必要的庫(kù)
from imutils.perspective import four_point_transform
from imutils import contours
import numpy as np
import argparse
import imutils
import cv2
 
# 構(gòu)建命令行參數(shù)解析并分析參數(shù)
# 對(duì)應(yīng)使用方式 python test_grader.py --image images/test_01.png
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", required=True,
    help="path to the input image")
args = vars(ap.parse_args())

# 構(gòu)建答案字典偷仿,鍵為題目號(hào)哩簿,值為正確答案
ANSWER_KEY = {0: 1, 1: 4, 2: 0, 3: 3, 4: 1}

補(bǔ)充:vars()接受一個(gè)對(duì)象返回它的內(nèi)建字典

vars()

當(dāng)然你需要有OpenCV和Numpy的包,但你可能沒(méi)有最新版本的imutils酝静,一個(gè)便于基本圖像處理操作的庫(kù)节榜。使用下面的命令來(lái)安裝和升級(jí)該庫(kù):

pip install --upgrade imutils

補(bǔ)充:在Windows7_64位+ Python 3.5測(cè)試,對(duì)于OpenCV的安裝有兩種方法别智,推薦第一種方法宗苍。(由于是python3,使用的是OpenCV 3.1.0)

  1. 使用活雷鋒編譯好的包薄榛,注意版本對(duì)應(yīng)讳窟。下載頁(yè)面選擇opencv_python-3.1.0-cp35-cp35m-win_amd64.whl,下載完成后在cmd命令行中使用pip install xx.whl安裝敞恋,xx.whl為下載的文件對(duì)應(yīng)路徑丽啡。同理可在該頁(yè)面找到numpy進(jìn)行安裝,當(dāng)然硬猫,對(duì)于科學(xué)計(jì)算庫(kù)的下載解決方案补箍,首推anaconda
  2. 下載OpenCV啸蜜,直接安裝(就是解壓)后將OpenCV安裝目錄下的\build\python\2.7\cv2.pyd復(fù)制到Python的子目錄\Lib\site-packages下坑雅。然后將opencv的\build\bin目錄添加到Windows的PATH中。
    在python命令行中import cv2成功的話就是安裝好了衬横。

我們?cè)诿钚兄兄唤馕隽艘粋€(gè)參數(shù)裹粤,那就是要分析的圖片的路徑。然后定義了答案字典蜂林,在這里蛹尝,題目對(duì)應(yīng)的值是正確答案在行中的索引位置后豫,跟python中列表的索引方式相同。

# 加載圖片突那,將它轉(zhuǎn)換為灰階挫酿,輕度模糊,然后邊緣檢測(cè)愕难。
image = cv2.imread(args["image"])
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
edged = cv2.Canny(blurred, 75, 200)

我們先從磁盤(pán)中加載了圖片文件早龟,然后將它轉(zhuǎn)換為灰階,再進(jìn)行模糊處理來(lái)消除高頻噪聲猫缭。最后葱弟,使用Canny邊緣檢測(cè)器來(lái)獲取答題卡的邊緣。結(jié)果如下 :


邊緣檢測(cè)結(jié)果

注意猜丹,答題卡長(zhǎng)方形的四個(gè)頂點(diǎn)都要在圖中出現(xiàn)芝加,這是我們事先約定的答題卡的邊緣。
獲取輪廓非常重要射窒,因?yàn)橄乱徊轿覀儗⑺鳛閼?yīng)用透視變換的標(biāo)記(錨點(diǎn))藏杖,來(lái)獲得一個(gè)答題卡的自上而下的鳥(niǎo)瞰視圖。

補(bǔ)充:stackoverflow上的提問(wèn):邊緣檢測(cè)和輪廓檢測(cè)的區(qū)別Difference between “Edge Detection” and “Image Contours” 最佳答案
簡(jiǎn)要來(lái)說(shuō)脉顿,邊緣是極值點(diǎn)蝌麸,而輪廓一般從邊緣得來(lái),是閉合的曲線艾疟。

# 從邊緣圖中尋找輪廓来吩,然后初始化答題卡對(duì)應(yīng)的輪廓
cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL,
    cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if imutils.is_cv2() else cnts[1]
docCnt = None
 
# 確保至少有一個(gè)輪廓被找到
if len(cnts) > 0:
    # 將輪廓按大小降序排序
    cnts = sorted(cnts, key=cv2.contourArea, reverse=True)
 
    # 對(duì)排序后的輪廓循環(huán)處理
    for c in cnts:
        # 獲取近似的輪廓
        peri = cv2.arcLength(c, True)
        approx = cv2.approxPolyDP(c, 0.02 * peri, True)
 
        # 如果我們的近似輪廓有四個(gè)頂點(diǎn),那么就認(rèn)為找到了答題卡
        if len(approx) == 4:
            docCnt = approx
            break

首先我們通過(guò)cv2.findContours從邊緣檢測(cè)的結(jié)果更進(jìn)一步得到輪廓值蔽莱。然后我們對(duì)輪廓的區(qū)域大小進(jìn)行排序弟疆,在這里我們假設(shè)答題卡就是我們圖像的焦點(diǎn),它會(huì)比圖中其它對(duì)象大盗冷,所以從大到小對(duì)輪廓進(jìn)行檢測(cè)兽间,符合長(zhǎng)方形特征的就是我們的答題卡了。
此外正塌,對(duì)于每個(gè)輪廓,我們進(jìn)行了近似恤溶,這在本質(zhì)上意味著我們簡(jiǎn)化了輪廓點(diǎn)的數(shù)量乓诽,使其成為一個(gè)“更基本的”幾何形狀。

補(bǔ)充:關(guān)于更多輪廓近似的內(nèi)容咒程,請(qǐng)看 building a mobile document scanner.

現(xiàn)在鸠天,如果docCnt在原始圖像中畫(huà)出來(lái)它將是這樣的:

找到的答題卡輪廓

然后我們進(jìn)行透視變換

# 對(duì)原始圖像和灰度圖都進(jìn)行四點(diǎn)透視變換
paper = four_point_transform(image, docCnt.reshape(4, 2))
warped = four_point_transform(gray, docCnt.reshape(4, 2))

我們使用了four_point_transform函數(shù),它將輪廓的(x, y) 坐標(biāo)以一種特別帐姻、可重復(fù)的方式整理稠集,并且對(duì)輪廓包圍的區(qū)域進(jìn)行透視變換奶段。暫時(shí)我們只需要知道它的變換效果就行了。

補(bǔ)充:關(guān)于更多該函數(shù)的信息剥纷,參看4 Point OpenCV getPerspective Transform ExampleOrdering coordinates clockwise with Python and OpenCV

透視變換的結(jié)果

好了痹籍,現(xiàn)在我們?nèi)〉昧艘恍┻M(jìn)展。
我們從原始圖像中獲取了答題卡晦鞋,并應(yīng)用透視變換獲取90度俯視效果蹲缠。

下面要對(duì)題目進(jìn)行判斷了。
這一步開(kāi)始于二值化悠垛,或者說(shuō)是圖像的前景和后景的分離/閾值處理线定。

# 對(duì)灰度圖應(yīng)用大津二值化算法
thresh = cv2.threshold(warped, 0, 255,
    cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]

補(bǔ)充:大津二值化法

現(xiàn)在,我們的圖像是一個(gè)純粹二值圖像了确买。

二值圖像

圖像的背景是黑色的斤讥,而前景是白色的。
這二值化使得我們能夠再次應(yīng)用輪廓提取技術(shù)湾趾,以找到每個(gè)題目中的氣泡選項(xiàng)芭商。

# 在二值圖像中查找輪廓,然后初始化題目對(duì)應(yīng)的輪廓列表
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
    cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if imutils.is_cv2() else cnts[1]
questionCnts = []
 
# 對(duì)每一個(gè)輪廓進(jìn)行循環(huán)處理
for c in cnts:
    # 計(jì)算輪廓的邊界框撑帖,然后利用邊界框數(shù)據(jù)計(jì)算寬高比
    (x, y, w, h) = cv2.boundingRect(c)
    ar = w / float(h)
 
    # 為了辨別一個(gè)輪廓是一個(gè)氣泡蓉坎,要求它的邊界框不能太小,在這里邊至少是20個(gè)像素胡嘿,而且它的寬高比要近似于1
    if w >= 20 and h >= 20 and ar >= 0.9 and ar <= 1.1:
        questionCnts.append(c)

我們由二值圖像中的輪廓蛉艾,獲取輪廓邊界框,利用邊界框數(shù)據(jù)來(lái)判定每一個(gè)輪廓是否是一個(gè)氣泡衷敌,如果是勿侯,將它加入題目列表questionCnts
將我們得到的題目列表中的輪廓在圖像中畫(huà)出缴罗,得到下圖:

檢測(cè)出所有氣泡

只有題目氣泡區(qū)域被圈出來(lái)了助琐,而其它地方?jīng)]有。

接下來(lái)就是閱卷了:

# 以從頂部到底部的方法將我們的氣泡輪廓進(jìn)行排序面氓,然后初始化正確答案數(shù)的變量兵钮。
questionCnts = contours.sort_contours(questionCnts,
    method="top-to-bottom")[0]
correct = 0
 
# 每個(gè)題目有5個(gè)選項(xiàng),所以5個(gè)氣泡一組循環(huán)處理
for (q, i) in enumerate(np.arange(0, len(questionCnts), 5)):
    # 從左到右為當(dāng)前題目的氣泡輪廓排序舌界,然后初始化被涂畫(huà)的氣泡變量
    cnts = contours.sort_contours(questionCnts[i:i + 5])[0]
    bubbled = None

首先掘譬,我們對(duì)questionCnts進(jìn)行從上到下的排序,使得靠近頂部的一行氣泡在列表中最先出現(xiàn)呻拌。然后對(duì)每行氣泡應(yīng)用從左到右的排序葱轩,使左邊的氣泡在隊(duì)列中先出現(xiàn)。解釋下,就是氣泡輪廓按縱坐標(biāo)先排序靴拱,并排的5個(gè)氣泡輪廓縱坐標(biāo)相差不大垃喊,總會(huì)被排在一起,而且每組氣泡之間按從上到下的順序排列袜炕,然后再將每組輪廓按橫坐標(biāo)分出先后本谜。

每行一組對(duì)應(yīng)一個(gè)題目

第二步,我們需要判斷哪個(gè)氣泡被填充了妇蛀。我們可以利用二值圖像中每個(gè)氣泡區(qū)域內(nèi)的非零像素點(diǎn)數(shù)量來(lái)進(jìn)行判斷耕突。

    # 對(duì)一行從左到右排列好的氣泡輪廓進(jìn)行遍歷
    for (j, c) in enumerate(cnts):
        # 構(gòu)造只有當(dāng)前氣泡輪廓區(qū)域的掩模圖像
        mask = np.zeros(thresh.shape, dtype="uint8")
        cv2.drawContours(mask, [c], -1, 255, -1)
 
        # 對(duì)二值圖像應(yīng)用掩模圖像,然后就可以計(jì)算氣泡區(qū)域內(nèi)的非零像素點(diǎn)评架。
        mask = cv2.bitwise_and(thresh, thresh, mask=mask)
        total = cv2.countNonZero(mask)
 
        # 如果像素點(diǎn)數(shù)最大眷茁,就連同氣泡選項(xiàng)序號(hào)一起記錄下來(lái)
        if bubbled is None or total > bubbled[0]:
            bubbled = (total, j)

下圖是對(duì)一行每一個(gè)氣泡利用掩模和原二值圖像合成的結(jié)果,顯然B是超過(guò)閾值的像素點(diǎn)最多的纵诞,從而也就是答題者選中的答案上祈。


掩模圖像合成效果

接著就是查找答案字典,判斷正誤了浙芙。

    # 初始化輪廓顏色為紅色登刺,獲取正確答案序號(hào)
    color = (0, 0, 255)
    k = ANSWER_KEY[q]
 
    # 檢查由填充氣泡獲得的答案是否正確,正確則將輪廓顏色設(shè)置為綠色嗡呼。
    if k == bubbled[1]:
        color = (0, 255, 0)
        correct += 1
 
    # 畫(huà)出正確答案的輪廓線纸俭。
    cv2.drawContours(paper, [cnts[k]], -1, color, 3)

如果氣泡作答是對(duì)的,則用綠色圈起來(lái)南窗,如果不對(duì)揍很,就用紅色圈出正確答案:


批閱答卷

最后,我們計(jì)算分?jǐn)?shù)并展示結(jié)果万伤。

# 計(jì)算分?jǐn)?shù)并打分
score = (correct / 5.0) * 100
print("[INFO] score: {:.2f}%".format(score))
cv2.putText(paper, "{:.2f}%".format(score), (10, 30),
    cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 0, 255), 2)
cv2.imshow("Original", image)
cv2.imshow("Exam", paper)
cv2.waitKey(0)
結(jié)果

其它

  • 為什么不使用圓形檢測(cè)窒悔?
    圖中的圓形氣泡可以使用Hough circles檢測(cè)方法。但是
    1. Hough circles的參數(shù)不好調(diào)
    2. 更重要的是避免用戶使用錯(cuò)誤造成的bug敌买,填寫(xiě)答題卡時(shí)不時(shí)會(huì)有填涂超出圓形邊界的現(xiàn)象简珠。

拓展和改進(jìn)

  • 需要改進(jìn)的是未填充氣泡的處理邏輯,當(dāng)前我們假設(shè)每行有且僅有一個(gè)填充氣泡虹钮。進(jìn)一步聋庵,要考慮如果答題者沒(méi)有涂寫(xiě)答案或者涂寫(xiě)了多個(gè)選項(xiàng)的情況,這里的邏輯并不復(fù)雜芙粱。

全部源碼和測(cè)試圖片

  • test_grader.py
# USAGE
# python test_grader.py --image test_01.png

# import the necessary packages
from imutils.perspective import four_point_transform
from imutils import contours
import numpy as np
import argparse
import imutils
import cv2

# construct the argument parse and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", required=True,
    help="path to the input image")
args = vars(ap.parse_args())

# define the answer key which maps the question number
# to the correct answer
ANSWER_KEY = {0: 1, 1: 4, 2: 0, 3: 3, 4: 1}

# load the image, convert it to grayscale, blur it
# slightly, then find edges
image = cv2.imread(args["image"])
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
edged = cv2.Canny(blurred, 75, 200)

# find contours in the edge map, then initialize
# the contour that corresponds to the document
cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL,
    cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if imutils.is_cv2() else cnts[1]
docCnt = None

# ensure that at least one contour was found
if len(cnts) > 0:
    # sort the contours according to their size in
    # descending order
    cnts = sorted(cnts, key=cv2.contourArea, reverse=True)

    # loop over the sorted contours
    for c in cnts:
        # approximate the contour
        peri = cv2.arcLength(c, True)
        approx = cv2.approxPolyDP(c, 0.02 * peri, True)

        # if our approximated contour has four points,
        # then we can assume we have found the paper
        if len(approx) == 4:
            docCnt = approx
            break

# apply a four point perspective transform to both the
# original image and grayscale image to obtain a top-down
# birds eye view of the paper
paper = four_point_transform(image, docCnt.reshape(4, 2))
warped = four_point_transform(gray, docCnt.reshape(4, 2))

# apply Otsu's thresholding method to binarize the warped
# piece of paper
thresh = cv2.threshold(warped, 0, 255,
    cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]

# find contours in the thresholded image, then initialize
# the list of contours that correspond to questions
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
    cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if imutils.is_cv2() else cnts[1]
questionCnts = []

# loop over the contours
for c in cnts:
    # compute the bounding box of the contour, then use the
    # bounding box to derive the aspect ratio
    (x, y, w, h) = cv2.boundingRect(c)
    ar = w / float(h)

    # in order to label the contour as a question, region
    # should be sufficiently wide, sufficiently tall, and
    # have an aspect ratio approximately equal to 1
    if w >= 20 and h >= 20 and ar >= 0.9 and ar <= 1.1:
        questionCnts.append(c)

# sort the question contours top-to-bottom, then initialize
# the total number of correct answers
questionCnts = contours.sort_contours(questionCnts,
    method="top-to-bottom")[0]
correct = 0

# each question has 5 possible answers, to loop over the
# question in batches of 5
for (q, i) in enumerate(np.arange(0, len(questionCnts), 5)):
    # sort the contours for the current question from
    # left to right, then initialize the index of the
    # bubbled answer
    cnts = contours.sort_contours(questionCnts[i:i + 5])[0]
    bubbled = None

    # loop over the sorted contours
    for (j, c) in enumerate(cnts):
        # construct a mask that reveals only the current
        # "bubble" for the question
        mask = np.zeros(thresh.shape, dtype="uint8")
        cv2.drawContours(mask, [c], -1, 255, -1)

        # apply the mask to the thresholded image, then
        # count the number of non-zero pixels in the
        # bubble area
        mask = cv2.bitwise_and(thresh, thresh, mask=mask)
        total = cv2.countNonZero(mask)

        # if the current total has a larger number of total
        # non-zero pixels, then we are examining the currently
        # bubbled-in answer
        if bubbled is None or total > bubbled[0]:
            bubbled = (total, j)

    # initialize the contour color and the index of the
    # *correct* answer
    color = (0, 0, 255)
    k = ANSWER_KEY[q]

    # check to see if the bubbled answer is correct
    if k == bubbled[1]:
        color = (0, 255, 0)
        correct += 1

    # draw the outline of the correct answer on the test
    cv2.drawContours(paper, [cnts[k]], -1, color, 3)

# grab the test taker
score = (correct / 5.0) * 100
print("[INFO] score: {:.2f}%".format(score))
cv2.putText(paper, "{:.2f}%".format(score), (10, 30),
    cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 0, 255), 2)
cv2.imshow("Original", image)
cv2.imshow("Exam", paper)
cv2.waitKey(0)
  • 測(cè)試圖片
test_01.png
test_02.png
test_03.png
test_04.png
test_05.png

供修改的空答題卡圖


example_test.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末祭玉,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子宅倒,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,695評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拐迁,死亡現(xiàn)場(chǎng)離奇詭異蹭劈,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)线召,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)铺韧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人缓淹,你說(shuō)我怎么就攤上這事哈打。” “怎么了讯壶?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,130評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵料仗,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我伏蚊,道長(zhǎng)立轧,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,648評(píng)論 1 297
  • 正文 為了忘掉前任躏吊,我火速辦了婚禮氛改,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘比伏。我一直安慰自己胜卤,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布赁项。 她就那樣靜靜地躺著葛躏,像睡著了一般。 火紅的嫁衣襯著肌膚如雪肤舞。 梳的紋絲不亂的頭發(fā)上紫新,一...
    開(kāi)封第一講書(shū)人閱讀 52,268評(píng)論 1 309
  • 那天,我揣著相機(jī)與錄音李剖,去河邊找鬼芒率。 笑死,一個(gè)胖子當(dāng)著我的面吹牛篙顺,可吹牛的內(nèi)容都是我干的偶芍。 我是一名探鬼主播,決...
    沈念sama閱讀 40,835評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼德玫,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼匪蟀!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起宰僧,我...
    開(kāi)封第一講書(shū)人閱讀 39,740評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤材彪,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體段化,經(jīng)...
    沈念sama閱讀 46,286評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡嘁捷,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了显熏。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片雄嚣。...
    茶點(diǎn)故事閱讀 40,505評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖喘蟆,靈堂內(nèi)的尸體忽然破棺而出缓升,到底是詐尸還是另有隱情,我是刑警寧澤蕴轨,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布港谊,位于F島的核電站,受9級(jí)特大地震影響尺棋,放射性物質(zhì)發(fā)生泄漏封锉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評(píng)論 3 333
  • 文/蒙蒙 一膘螟、第九天 我趴在偏房一處隱蔽的房頂上張望成福。 院中可真熱鬧,春花似錦荆残、人聲如沸奴艾。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,357評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)蕴潦。三九已至,卻和暖如春俘闯,著一層夾襖步出監(jiān)牢的瞬間潭苞,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,466評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工真朗, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留此疹,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,921評(píng)論 3 376
  • 正文 我出身青樓遮婶,卻偏偏與公主長(zhǎng)得像蝗碎,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子旗扑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容