1
1

More than 1 year has passed since last update.

マークリーダー(Fortranを使用しない最新版あり)

Last updated at Posted at 2021-02-19

#環境
macOS Cataina
10.15.7

python 3.8.1
fortran 95

スキャナ
canon
ソフト
CaptureOnTouch
読み取り
グレースケール
200dpi
jpeg
3標準

スクリーンショット 2021-06-08 13.12.06.png

スクリーンショット 2021-06-08 13.12.17.png

#必要なライブラリ
cv2

$ pip install cv2

#必要なもの
・記入済みマークシート用紙を含むディレクトリ(student_sheet
・マーカー(maker.jpg)←スキャナで読み取った画像から作成
・本プログラム(mark30.sh
・主に画像認証用(mark_f30.py
・正答率の計算用(rate_windows.f95
・空のディレクトリ(answersheet
・結果出力用のディレクトリ(answersheet_rate

これを同一ディレクトリに入れて実行

$ ./mark30.sh

すると,
結果出力用のディレクトリ(answersheet_rate)に
答案用紙が作成されます.

image.png
答案用紙

#各プログラムソースコード

本プログラム

mark30.sh
#!/bin/sh

for number in {1..9}
do
    echo "■■■■■■■■■■■■■■■■■■■■■■■■"
    echo "student_"00${number}".jpg"  
    python mark_f30.py 00${number}
done

for number in {10..45}
do
    echo "■■■■■■■■■■■■■■■■■■■■■■■■"
    echo "student_"0${number}".jpg"  
    python mark_f30.py 0${number}
done

#for number in {100..120}
#do
#    echo "■■■■■■■■■■■■■■■■■■■■■■■■"
#    echo "student_"${number}".jpg"  
#    python mark_f30.py ${number}
#done

echo "■■■■■■■■■■■■■■■■■■■■■■■■"
echo "■■■■■■■■■■■■■■■■■■■■■■■■"
echo "■■■■■■■■■■■■■■■■■■■■■■■■"
echo "全てのマークシートの読取完了"
ls answersheet > list.txt
gfortran -o rate rate_windows.f95
./rate

画像認証用プログラム

mark_f30.py
import numpy as np
import cv2
#import pandas as pd
import sys

#print("##################")
#正解,配点記入欄#
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] #番号
eanswer = [6, 9, 3, 2, 2, 1, 2, 3, 5,  4,  2,  6,  6,  6,  5,  3,  5,  4,  4,  6,  2,  3,  6,  2,  1,  4,  4,  9,  6,  8] #正解
allo    = [2, 3, 3, 3, 2, 3, 3, 3, 4,  3,  2,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3] #配点
#score = 0
T = "1"
F = "0"
E = "20"#マークミスエラー用

####################################################################################
n_col = 11 # マークの列数
n_row = 35 # マークの行数
margin_top = 3 # 上余白行数
margin_bottom = 0 # 下余白行数
n_row_total = n_row + margin_top + margin_bottom # 行数 (マーク行  + 上余白  + 下余白 )
start = 5 #出席番号の分の列#百の位〜解答欄までの列の総計

#グレースケール (mode = 0)でファイルを読み込む
marker=cv2.imread('marker.jpg',0) 

#コマンドラインから引数#########
#例 >python mark.py 001#####
args = sys.argv
n = str(args[1])
##########################################

#スキャン画像を読み込む
img = cv2.imread('student_sheet/student_%s.jpg' %n, 0)

#マーカー検出
res = cv2.matchTemplate(img, marker, cv2.TM_CCOEFF_NORMED)
threshold = 0.5#閾値
loc = np.where( res >= threshold)
#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(255*300))#閾値
    
#ここから番号の読み取り#########
#組番号の読み取り
cl = np.where(Result[0]==True)[0]#+1
if len(cl)>1:
      print("ダブルマーク")
      print("読取失敗")
      sys.exit( )
elif len(cl)==1:
      cla = str(cl[0])
else:
      print("ノーマーク")
      print("読取失敗")
      sys.exit( )

#cla = str(cl[0])

#出席番号の読み取り
num = []
for y in range(1,3):
    re = np.where(Result[y]==True)[0]#+1
    if len(re)>1:
      print("ダブルマーク")
      print("読取失敗")
      sys.exit( )
    elif len(re)==1:
      num.append(int(re))
    else:
     print("ノーマーク")
     print("読取失敗")
     sys.exit( )
nu = str(num[0])+ str(num[1])
code = str(cla) + str(nu)
print("出席番号%s" % code)#ターミナルに表示

#出力ファイル作成#生徒に配るやつ##################################
with open('answersheet/%s.txt' % code,  'w') as f:#出力ファイル作成

#回答番号の読み取り
 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)#リスト化

#ファイルに出力##
# print("\n出席番号%s" % code, file=f)
# print("\n", file=f)
# print("番号", "正解",  "回答", "正誤", "配点", file=f)

 for nu, an, ea, al in zip(number, answer, eanswer, allo):
    if str(an) == str(20):
        print(str(nu).rjust(3), str(ea).rjust(3), E.rjust(3), F.rjust(3), str(al).rjust(3), file=f)
    elif str(an) == str(ea):
        print(str(nu).rjust(3), str(ea).rjust(3), str(an).rjust(3), T.rjust(3), str(al).rjust(3), file=f)
#        score = score + al
    else:
        print(str(nu).rjust(3), str(ea).rjust(3), str(an).rjust(3), F.rjust(3), str(al).rjust(3), file=f)
# print("\n合計 %d 点" % score, file=f)

f.close()

print("読取完了")
#おわり

正答率計算用

rate_windows.f95
program main
  
  implicit none
  integer, parameter :: n = 1000
  integer i, j, nd, ndd, x(n,n), y(n,n), z(n,n), v(n,n), w(n,n), total(n), totals(n), k, totalm
  integer f0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10
  character :: a(n)*7, b*12, bb*17, c(n)*19, cc(n)*24
  character t*1, f*1, e*1
  real r(n), av, s, ss(n), ssm
  b = 'answersheet/'
  bb = 'answersheet_rate/'
  t = 'o'
  f = 'x'
  e = '?'

 !リストからファイル読み込み
  open(60,file='list.txt')
  do i = 1, n
     read(60,*,end=100) a(i)
     c(i) = b//a(i)
     cc(i)= bb//a(i)
  end do

100 close(60)
  nd = i - 1

 !初期値リセット  
  total = 0
  r = 0
  av = 0
  s = 0
  
  do i = 1, nd !ファイル(40人学級なら40列)
     open(20,file=c(i))
     do j = 1, n !紙(問題数が20なら20列)
        read(20,*,end=101) x(j,i), y(j,i), z(j,i), v(j,i), w(j,i)
     end do
101  close(20)
  end do
  
  ndd = j - 1


 !合計点の計算
  do i = 1, nd
     do j = 1, ndd
        if  (y(j,i) ==  z(j,i)) then
           total(i) = total(i) + w(j,i)
        else
        endif
     end do
  end do
  
 !平均点の計算
  do i = 1, nd
     av = av + total(i)
  end do
  av = av/nd

 !標準偏差の計算
  do i = 1, nd
     s = s + (total(i) - av)**2
  end do
  s = s/nd
  s = sqrt(s)
  
  !偏差値の計算
 do i = 1, nd 
    ss(i) = ((total(i) - av)/s)*10 + 50
 end do

 !正答率の計算  
  do j = 1, ndd
     do i = 1, nd
        r(j) = r(j) + v(j,i)
     end do
     r(j) = (r(j)/nd)*100 !正答率の計算
  end do

  !度数分布作成
  f0 = 0
  f1 = 0
  f2 = 0
  f3 = 0
  f4 = 0
  f5 = 0
  f6 = 0
  f7 = 0
  f8 = 0
  f9 = 0
  f10 = 0
  
  do  i = 1, nd
   if (total(i) >= 0 .and. total(i) < 10) then
        f0 = f0 + 1
     else if (total(i) >= 10 .and. total(i) < 20) then
        f1 = f1 + 1
     else if (total(i) >= 20 .and. total(i) < 30) then 
        f2 = f2 + 1
     else if (total(i) >= 30 .and. total(i) < 40) then
        f3 = f3 + 1
     else if (total(i) >= 40 .and. total(i) < 50) then 
        f4 = f4 + 1
     else if (total(i) >= 50 .and. total(i) < 60) then 
        f5 = f5 + 1
     else if (total(i) >= 60 .and. total(i) < 70) then 
        f6 = f6 + 1
     else if (total(i) >= 70 .and. total(i) < 80) then
        f7 = f7 + 1
     else if (total(i) >= 80 .and. total(i) < 90) then 
        f8 = f8 + 1
     else if (total(i) >= 90 .and. total(i) < 100) then 
        f9 = f9 + 1
     else if (total(i) == 100) then
        f10 = f10 + 1
     endif
  end do

 !結果出力
  do i = 1, nd
     open(30,file=cc(i))
     write(30,*)
     write(30,*) ' 出席番号 ', a(i)(1:3)
     write(30,*)
     write(30,*)
     write(30,*) '  番号', ' 正解', ' 回答', ' 正誤', ' 配点', ' 正答率'
     do j = 1, ndd
        if (z(j,i) == 20) then
           write(30,'(2i5,2a5,i6,f8.1)') x(j,i), y(j,i), e, e,  w(j,i), r(j)
        else
           if (v(j,i) == 1) then
              write(30,'(3i5,a5,i6,f8.1)') x(j,i), y(j,i), z(j,i), t,  w(j,i), r(j)
           else if (v(j,i) == 0) then
              write(30,'(3i5,a5,i6,f8.1)') x(j,i), y(j,i), z(j,i), f,  w(j,i), r(j)
           endif
        endif
     end do
     write(30,*)
     write(30,*)' 合計点', '  偏差値'
     write(30,'(i5,f8.1)') total(i), ss(i)
     write(30,*)'----------------------------------'
     write(30,*)' 平均点', '  標準偏差'
     write(30,'(f7.1,f6.1)') av, s
     write(30,*)
     write(30,*)' 度数分布'
     write(30,*)' 100', '  ', f10
     write(30,*)' 90~99', f9
     write(30,*)' 80~89', f8
     write(30,*)' 70~79', f7
     write(30,*)' 60~69', f6
     write(30,*)' 50~59', f5
     write(30,*)' 40~49', f4 
     write(30,*)' 30~39', f3
     write(30,*)' 20~29', f2
     write(30,*)' 10~19', f1
     write(30,*)' 0 ~ 9', f0
     close(30)
  end do



!模範解答
   i = 1
     open(40,file='answersheet_rate/model_answer.txt')
     write(40,*)
     write(40,*) ' 模範解答 '
     write(40,*)
     write(40,*)
     write(40,*) '  番号', ' 正解', ' 回答', ' 正誤', ' 配点', ' 正答率'
     do j = 1, ndd
              write(40,'(3i5,a5,i6,f8.1)') x(j,i), y(j,i), y(j,i), t,  w(j,i), r(j)
           end do
           totalm = 0
           do j = 1, ndd
              totalm = totalm + w(j,i)
           end do

             !偏差値の計算
           do i = 1, nd 
              ssm = ((totalm - av)/s)*10 + 50
           end do 
     write(40,*)
     write(40,*)' 合計点', '  偏差値'
     write(40,'(i5,f8.1)') totalm, ssm
     write(40,*)'----------------------------------'
     write(40,*)' 平均点', '  標準偏差'
     write(40,'(f7.1,f6.1)') av, s
     write(40,*)
     write(40,*)' 度数分布'
     write(40,*)' 100', '  ', f10
     write(40,*)' 90~99', f9
     write(40,*)' 80~89', f8
     write(40,*)' 70~79', f7
     write(40,*)' 60~69', f6
     write(40,*)' 50~59', f5
     write(40,*)' 40~49', f4 
     write(40,*)' 30~39', f3
     write(40,*)' 20~29', f2
     write(40,*)' 10~19', f1
     write(40,*)' 0 ~ 9', f0
     close(40)
  
endprogram main

参考
https://qiita.com/sbtseiji/items/6438ec2bf970d63817b8

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1