環境
PCというかOS
macOS Cataina 10.15.7
(pythonやOpen CVが使えれば何でもいい)
python
python 3.8.1
スキャナー
canonの自動送り(中古で2,3万?)
スキャナーソフト
CaptureOnTouch
設定
・グレースケール
・200dpi
・jpeg
・3標準
この名前の付け方がおかしいと
プログラムをまわしたとき,エラーになります.
読み取ったやつ
必要なライブラリ
cv2
numpy
statistics
glob
もしなければ
$ pip install cv2
で適宜インストール
必要なもの
・記入済みマークシート用紙を含むディレクトリ(student
)
・マーカー(maker.jpg
)
(スキャナで読み取ったマークシートの黒四角を切り抜いたもの)
・プログラム(mark35.py
)
・空のディレクトリ(result
)
これを同一ディレクトリ(上の図の場合l1
)に入れて実行
$ python mark35.py
エラーがなければ,
結果出力用のディレクトリ(result
)に
答案用紙が作成される.
あとはこいつらをまとめてコピーして配布
プログラムソースコード
mark35.py
#ライブラリを取得
import numpy as np
import cv2
import statistics
import glob
#正解,配点記入欄
number = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35] #番号
eanswer = [8, 4, 7, 2, 1, 5, 3, 2, 2, 7, 5, 2, 3, 4, 4, 4, 1, 6, 4, 4, 4, 1, 2, 1, 1, 1, 9, 2, 4, 3, 2, 1, 1, 1, 1] #正解
allo = [3, 4, 3, 2, 2, 2, 4, 3, 2, 2, 3, 2, 3, 3, 3, 2, 3, 3, 3, 3, 3, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3] #配点
#正解は1,不正解は2
T = "1"
F = "0"
E = "20" #マークミスエラー用
#画像認識の閾値
threshold1 = 0.5 #閾値
threshold2 = 255*500 #閾値
#マークシートの形式
n_col = 11 # マークの列数(左黒四角の左辺〜右黒四角の左辺)
n_row = 40 # マークの行数(マークする番号の開始〜終了までの行数)
margin_top = 3 # 上余白行数(上黒四角の上辺〜組番号までの行数)
margin_bottom = 0 # 下余白行数(下黒四角の上辺〜最後の問までの行数)
start = 5 #出席番号の分の列#百の位〜解答欄までの列の総計
n_row_total = n_row + margin_top + margin_bottom #行数(マーク行 + 上余白 + 下余白)
#グレースケール (mode = 0)でファイルを読み込む
marker=cv2.imread('marker.jpg',0)
#ここまでは自分で調節
########################################################################
#スキャナーで読み取ったシートのファイル数を取得
jpgcount = len(glob.glob1('student','*.jpg'))
print('読み込みファイル数',jpgcount)
matrix = [] #生徒x問題x各項目(正誤,正答率等)のリスト作成
studentnamelist = [] #出席番号のリスト
studentnamelist3 = [] #数字のみのリスト
#######################################
#ここから各生徒の回答を読み込むループ
for k in range(jpgcount):
print('####################')
#スキャン画像を読み込む
img = cv2.imread('student/student_%s.jpg' %str(k+1), 0)
#マーカー検出
res = cv2.matchTemplate(img, marker, cv2.TM_CCOEFF_NORMED)
loc = np.where( res >= threshold1)
#cv2.imwrite('resimg.jpg',img)
#マークシート部分の切り抜き
mark_area={}
mark_area['top_x']= min(loc[1])
mark_area['top_y']= min(loc[0])
mark_area['bottom_x']= max(loc[1])
mark_area['bottom_y']= max(loc[0])
img = img[mark_area['top_y']:mark_area['bottom_y'],\
mark_area['top_x']:mark_area['bottom_x']]
#cv2.imwrite('kiri.jpg',img)
#リサイズ
#Answer = cv2.imread('kiri.jpg', 0)
Answer_Resize = cv2.resize(img, (n_col*100, n_row_total*100))
#cv2.imwrite('kiri.jpg', Answer_Resize)
#ぼかし
Answer_Blur = cv2.GaussianBlur(Answer_Resize, (5,5), 0)
#cv2.imwrite('Answer_Blur.jpg',Answer_Blur)
#2値化
RetVal, Answer_Binarization = \
cv2.threshold(Answer_Blur, 50, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
#cv2.imwrite('Answer_Binarization.jpg',Answer_Binarization)
#白黒反転
Answer_Reverse = 255 - Answer_Binarization
#cv2.imwrite('Answer_Reverse.jpg',Answer_Reverse)
#行と列に分解して各場所の階調の合計値を計算
Result = []
for row in range(margin_top+1, margin_top+1+n_row):
tmp_Answer = Answer_Reverse [(row-1)*100:row*100,]#
cv2.imwrite("PNG/Answer_Tmp%d.png" % row, tmp_Answer)#
tmp_Answer = Answer_Reverse [(row-1)*100:row*100,]
Area_sum = []
for col in range(n_col):
Area_sum.append(np.sum(tmp_Answer[:,col*100:(col+1)*100]))
Result.append(Area_sum > np.median(threshold2))#閾値
#ここから番号の読み取り#########
#組番号の読み取り
cl = np.where(Result[0]==True)[0]#+1
if len(cl)>1:
print("ダブルマーク")
print("組番号読取失敗x")
continue
elif len(cl)==1:
cla = str(cl[0])
else:
print("ノーマーク")
print("組番号読取失敗x")
continue
#出席番号の読み取り
num = []
#一桁目読み取り
re = np.where(Result[1]==True)[0]#+1
if len(re)>1:
print("ダブルマーク")
print("出席番号読取失敗x")
continue
elif len(re)==1:
num.append(int(re))
else:
print("ノーマーク")
print("出席番号読取失敗x")
continue
#二桁目読み取り
re = np.where(Result[2]==True)[0]#+1
if len(re)>1:
print("ダブルマーク")
print("出席番号読取失敗x")
continue
elif len(re)==1:
num.append(int(re))
else:
print("ノーマーク")
print("出席番号読取失敗x")
continue
#出席番号読み取り成功後
nu = str(num[0])+ str(num[1])
code = str(cla) + str(nu)
#ターミナルに表示
print('出席番号%s' % code)
#各生徒の空の配列をつくる
exec_hako = 'student_' + str(code) + '=[]'
exec(exec_hako)
#出力ファイル作成(生徒に配るやつ)##################################
#回答番号の読み取り
answer = []
for x in range(start,len(Result)):
res = np.where(Result[x]==True)[0]#+1
if len(res)>1:
res = 20
print("問%sダブルマーク" % (x-start+1))
answer.append(res)
elif len(res)==1:
answer.append(int(res[0]))#リスト化
else:
res = 20
print("問%sノーマーク" % (x-start+1))
answer.append(res)#リスト化
s_answer=[]
r_or_w = []
for nu, an, ea, al in zip(number, answer, eanswer, allo):
if str(an) == str(20):
s_answer.append(int(E))
r_or_w.append(int(F))
elif str(an) == str(ea):
s_answer.append(an)
r_or_w.append(int(T))
else:
s_answer.append(an)
r_or_w.append(int(F))
exec_append = 'student_' + str(code) + '.append(number)'
exec(exec_append)
exec_append = 'student_' + str(code) + '.append(eanswer)'
exec(exec_append)
exec_append = 'student_' + str(code) + '.append(s_answer)'
exec(exec_append)
exec_append = 'student_' + str(code) + '.append(r_or_w)'
exec(exec_append)
exec_append = 'student_' + str(code) + '.append(allo)'
exec(exec_append)
print("読取完了o")
#リスト作成
studentnamelist.append('%s.txt' % code)
studentnamelist3.append(code)
#おわり
#正答率などの計算#####################
#各生徒の問題x各項目(二次元)を連結して生徒x問題x各項目(三次元)
for i in range(len(studentnamelist)):
exec_append = 'matrix.append(student_' + str(studentnamelist3[i]) + ')'
exec(exec_append)
#合計点
total=[]
#生徒方向のループ
for i in range(len(studentnamelist)):
#配列の作成
exec_append = 'qu' + str(i+1) + '=[]'
exec(exec_append)
#問方向のループ
for j in range(len(matrix[0][0])):
exec_append = 'qu' + str(i+1) + '.append((matrix['+ str(i) + '][3][' + str(j) + '])\
*(matrix[' + str(i) + '][4][' + str(j) + ']))'
exec(exec_append)
exec_append = 'total.append(sum(qu' + str(i+1) + '))'
exec(exec_append)
#平均点
av = sum(total)/len(studentnamelist)
#標準偏差
std = statistics.stdev(total)
#中央値
med = statistics.median(total)
#偏差値
dv = []
for i in range(len(studentnamelist)):
dvi = round(50 + ((total[i]-av)/std)*10,1)
dv.append(dvi)
#度数分布
f00_04 = 0
f05_09 = 0
f10_14 = 0
f15_19 = 0
f20_24 = 0
f25_29 = 0
f30_34 = 0
f35_39 = 0
f40_44 = 0
f45_49 = 0
f50_54 = 0
f55_59 = 0
f60_64 = 0
f65_69 = 0
f70_74 = 0
f75_79 = 0
f80_84 = 0
f85_89 = 0
f90_94 = 0
f95_99 = 0
f100 = 0
for i in range(len(total)):
if total[i] >= 0 and total[i] < 5:
f00_04 = f00_04 + 1
elif total[i] >= 5 and total[i] < 10:
f05_09 = f05_09 + 1
elif total[i] >= 10 and total[i] < 15:
f10_14 = f10_14 + 1
elif total[i] >= 15 and total[i] < 20:
f15_19 = f15_19 + 1
elif total[i] >= 20 and total[i] < 25:
f20_24 = f20_24 + 1
elif total[i] >= 25 and total[i] < 30:
f25_29 = f25_29 + 1
elif total[i] >= 30 and total[i] < 35:
f30_34 = f30_34 + 1
elif total[i] >= 35 and total[i] < 40:
f35_39 = f35_39 + 1
elif total[i] >= 40 and total[i] < 45:
f40_44 = f40_44 + 1
elif total[i] >= 45 and total[i] < 50:
f45_49 = f45_49 + 1
elif total[i] >= 50 and total[i] < 55:
f50_54 = f50_54 + 1
elif total[i] >= 55 and total[i] < 60:
f55_59 = f55_59 + 1
elif total[i] >= 60 and total[i] < 65:
f60_64 = f60_64 + 1
elif total[i] >= 65 and total[i] < 70:
f65_69 = f65_69 + 1
elif total[i] >= 70 and total[i] < 75:
f70_74 = f70_74 + 1
elif total[i] >= 75 and total[i] < 80:
f75_79 = f75_79 + 1
elif total[i] >= 80 and total[i] < 85:
f80_84 = f80_84 + 1
elif total[i] >= 85 and total[i] < 90:
f85_89 = f85_89 + 1
elif total[i] >= 90 and total[i] < 95:
f90_94 = f90_94 + 1
elif total[i] >= 95 and total[i] < 100:
f95_99 = f95_99 + 1
elif total[i] == 100:
f100 = f100 + 1
#正答率の計算
#matrixからの切り出しright and wrong(raw)
for i in range(len(matrix[0][0])):
exec_append = 'raw' + str(i+1) + '=[]'
exec(exec_append)
#生徒方向へのループ
for i in range(len(studentnamelist)):
#問題方向へのループ
for j in range(len(matrix[0][0])):
exec_append2 = 'raw' + str(j+1) + '.append(matrix[' + str(i) + \
'][3][' + str(j) + '])'#[3]は正答率を書き込む列によって変更
exec(exec_append2)
#Correct answer rate(car)
car = []
for i in range(len(matrix[0][0])):
exec_append = 'car.append(sum(raw' + str(i+1) + ')/len(raw' + str(i+1) + ')*100)'#正答率
exec(exec_append)
#少数第二位まで四捨五入
car = [round(car[n], 1) for n in range(len(car))]
#正答率をmatrixの列の端っこにくっつける
for row in matrix:
row.append(car)
#正誤をoとxに置き換え
for i in range(len(studentnamelist)):
matrix[i][3] = ['o' if j == 1 else j for j in matrix[i][3]]
matrix[i][3] = ['x' if j == 0 else j for j in matrix[i][3]]
matrix[i][2] = ['?' if j == 20 else j for j in matrix[i][2]]
#平均,標準偏差を四捨五入
av=round(av,1)
std=round(std,1)
for i in range(len(studentnamelist)):
temp = 0
temp = 'result/' + str(studentnamelist[i])
with open(temp, 'w') as f:
print('出席番号%s' % (studentnamelist[i])[0:3], file=f)
# print(' ',file=f)
print('\n番号'.rjust(2),'正解'.rjust(2),'回答'.rjust(2),'正誤'.rjust(2),'配点'.rjust(2),'正答率'.rjust(2),file=f)
for j in range(len(matrix[0][0])):
print(str(matrix[i][0][j]).rjust(3),str(matrix[i][1][j]).rjust(3),\
str(matrix[i][2][j]).rjust(3),str(matrix[i][3][j]).rjust(3),\
str(matrix[i][4][j]).rjust(3),' ',str(matrix[i][5][j]).rjust(4),file=f)
# print('\n',file=f)
# print(' ',file=f)
print('\n合計点',str(total[i]).rjust(3),' 偏差値',str(dv[i]).rjust(3),file=f)
print('------------------------------',file=f)
print('平均点',str(av),' 標準偏差',str(std),file=f)
# print(' ',file=f)
print('\n度数分布',file=f)
print('100 ',f100, ' | ' ,file=f)
print('95 ~ 99 ',f95_99,' | 45 ~ 49 ',f45_49,file=f)
print('90 ~ 94 ',f90_94,' | 40 ~ 44 ',f40_44,file=f)
print('85 ~ 89 ',f85_89,' | 35 ~ 39 ',f35_39,file=f)
print('80 ~ 84 ',f80_84,' | 30 ~ 34 ',f30_34,file=f)
print('75 ~ 79 ',f75_79,' | 25 ~ 29 ',f25_29,file=f)
print('70 ~ 74 ',f70_74,' | 20 ~ 24 ',f20_24,file=f)
print('65 ~ 99 ',f65_69,' | 15 ~ 19 ',f15_19,file=f)
print('60 ~ 64 ',f60_64,' | 10 ~ 14 ',f10_14,file=f)
print('55 ~ 59 ',f55_59,' | 5 ~ 9 ',f05_09,file=f)
print('50 ~ 54 ',f50_54,' | 0 ~ 4 ',f00_04,file=f)
#模範解答作成
#偏差値
model_dv = round(50 + ((sum(matrix[0][4])-av)/std)*10,1)
model_total = round(sum(matrix[0][4]),1)
with open('result/model_answer.txt', 'w') as f:
print('模範解答', file=f)
print('\n番号'.rjust(2),'正解'.rjust(2),'回答'.rjust(2),'正誤'.rjust(2),'配点'.rjust(2),'正答率'.rjust(2),file=f)
for j in range(len(matrix[0][0])):
print(str(matrix[i][0][j]).rjust(3),str(matrix[i][1][j]).rjust(3),str(matrix[i][1][j]).rjust(3),'o'.rjust(3),\
str(matrix[i][4][j]).rjust(3),' ',str(matrix[i][5][j]).rjust(4),file=f)
# print('\n',file=f)
# print(' ',file=f)
print('\n合計点',str(model_total).rjust(3),' 偏差値',str(model_dv).rjust(3),file=f)
print('------------------------------',file=f)
print('平均点',str(av),' 標準偏差',str(std),file=f)
# print(' ',file=f)
print('\n度数分布',file=f)
print('100 ',f100, ' | ' ,file=f)
print('95 ~ 99 ',f95_99,' | 45 ~ 49 ',f45_49,file=f)
print('90 ~ 94 ',f90_94,' | 40 ~ 44 ',f40_44,file=f)
print('85 ~ 89 ',f85_89,' | 35 ~ 39 ',f35_39,file=f)
print('80 ~ 84 ',f80_84,' | 30 ~ 34 ',f30_34,file=f)
print('75 ~ 79 ',f75_79,' | 25 ~ 29 ',f25_29,file=f)
print('70 ~ 74 ',f70_74,' | 20 ~ 24 ',f20_24,file=f)
print('65 ~ 99 ',f65_69,' | 15 ~ 19 ',f15_19,file=f)
print('60 ~ 64 ',f60_64,' | 10 ~ 14 ',f10_14,file=f)
print('55 ~ 59 ',f55_59,' | 5 ~ 9 ',f05_09,file=f)
print('50 ~ 54 ',f50_54,' | 0 ~ 4 ',f00_04,file=f)
print('####################')
print('読み込みファイル数',jpgcount)
print('正常に読み込めた人数',len(studentnamelist))