自治会役員選挙の開票作業の一部を、Python で自動化した試み
この記事の趣旨
わたしが暮らしていた自治会Aでは、次年度の役員選出の選挙開票作業に、かなり時間がかかっていることが課題となっていました。このため、「氏名記入の投票用紙」から「マークシート記入の投票用紙」に変更し、開票作業の一部を自動化したところ、1 時間程度が 15 分程度になりました。
翌年、近隣の自治会Bで同様の仕組みを採用されたい旨ご相談があり、対応しました。時間短縮・工数削減になりました。
その際の試みを書きます。
自治会Aの当時の状況
自治会Aは、高齢化率が高めの自治会です。自治会Aの地区役員(執行部)は 8 名で構成されています。区長・副区長・会計の 3 人については、多くの場合、推薦立候補者が一名なので信任投票を行っています。信任投票は、信任に対して 〇 を書くだけの信任投票用紙なので、開票・集計作業は早く終わります。
一方、その他の 5 人については、「氏名記入の投票用紙」により、投票を実施しています。以下、この投票の仕組みを「投票形式A」とします。
以下に、投票形式Aを図で示します。

投票形式Aは、切れ目を入れた 5 名連記の投票用紙を用いています。
- 投票者(自治会の各世帯代表者 約 150 人)は、投票用紙に投票する対象となる人物のフルネームを手書きします。投票用紙は、切れ目の入ったまま回収します。
- 開票者(およそ 10 名程度)は、5 名連記の投票用紙に、同じ名前を重複して書かれていないか確認します。確認後、切れ目に沿って投票用紙を切り離し、短冊状態にします(同じ名前を重複して書いている場合は、1 枚のみ有効とし、2 枚目以降は無効とします)。
- 開票者は、投票用紙短冊の枚数を手作業で数えて、開票結果を集計します。
- 投票者は、開票結果が判明するまで、公民館で待機しています。
この作業の課題は、以下の通りです。
- 切れ目を入れた投票用紙作成に手間がかかること
- 5名連記の投票表紙に、同じ名前が重複して書かれていないか、確認する必要があること
- 投票用紙短冊の枚数の集計作業に時間がかかること
- 投票者が、信任投票含めて 2 時間程度の待ち状態になること
「投票形式A」の一部を自動化できれば、投票者・開票者の皆が早く帰れて幸せになれると考えた次第です。
過去に一度、A4用紙に変更しQRコードによる読み込み投票用紙でトライしたことがありました。この時は、私の作り方が悪く不評だったのか、次年度に引き継がれず「A4用紙の投票候補者名一覧の名前の横に、○ 印を書く」投票用紙になりました。それでも、開票に時間がかかることには、変わりありません。
やりたいこと
「5名の役員選出選挙」を迅速に終えたい。目標は 15 分以内の終了(スキャナー読み込み 7 分+Python 処理 5 分+結果表示準備 3 分)。
実施方法
- 投票用紙 (A4) に、マークシートで投票内容を記載する
- 投票用紙を回収し、スキャナーで TIFF または JPEG 形式で読み込む
- 読み込んだ TIFF または JPEG ファイル 約 150 件について、OMR にかけてマークシート位置を認識する
- 認識した結果を CSV 形式または Excel 形式で出力する
- 出力された結果を、開票結果表示準備の基データとして使い、投票結果を表示させる
事前の情報収集
選挙に情報技術を導入した場合の課題調査
Dai MIKURUBEさんの zenn 記事「オンライン投票はなぜ『難しい』のか」を読みました。
自動化の話を進める際に他の人から出たのは「オンライン投票」の話ですが、これは記事を読んだ際に感じた課題の大きさもあり、避けました。
同時に、「電子投票」も避けました。(どこもそうだと思うのですが)高齢者が多い自治会なので、タブレット端末を置いて投票する方式をとると、操作が分からない等の課題が起こることが想像できました。その対応で時間がかかるのでは、自動化で時間短縮を図ったとて、その効果は限定的になります。こうした理由により、電子投票は見送りました。
実現するための技術調査
マークシートで実施するために、以下のサイトを読みました。
- ビビンビーフさん「Pythonでマークシートリーダ作成のハンズオン忘備録」
- @sbtseiji(Seiji Shibata)さん「PythonとOpenCVで簡易OMR(マークシートリーダ)を作る」
- SINさん「[OpenCV] ArUcoマーカーを使用して、安定した商品の監視映像を撮影してみました。」
- @koichi_baseballさん【OpenCV】4.6.0から4.7.0でのArUcoモジュールの更新箇所抜粋
ArUco マーカーを用いてスキャナー読み込み時の傾斜を直して、マークの抽出範囲を絞るのがよさそうだと思いました。このため、Python と OpenCV での OMR を作ることにしました。これで、個別投票結果は読み取りで解決しそうだと、めどを付けました。
集計そのものは、Excel の COUNTIF() や RANK() などを使えばよいかなと思っていたので、あまり深く考えていませんでした。個別投票結果を吐き出してもらえば、それを投票用紙を作っている Excel のワークシートとして追加してしまえばよいな、なとど思っていました。
懸念していたこと
- 投票用紙 (A4) をマークするには、作業スペースが狭い?
用紙を更に小さくした場合、文字が小さくて読めないという別の問題が発生する - 投票用紙のマーク塗りつぶしサイズが小さすぎて、塗りにくい?
印刷して実際に試す - 投票用紙にマークしても、マークが薄くて OMR で読み取れない?
閾値を調整することで解決する - 投票用紙をスキャナーで読み取る際に、紙が斜めになり読み取り位置がずれる?
ArUco マーカーを使って傾斜を確認し、回転補正をする - 投票用紙を約 150 枚読み込むのに時間がかかり過ぎて、目標タイムでの処理ができない?
枚数用意して、テストする
実現までの流れ
新投票用紙の作成とスキャナー読み込み
「氏名記入の投票用紙」から「マークシート記入の投票用紙」に変更しました。
用紙サイズは A4。公民館に約 150 人集まる中、A4 用紙にマークを入れるのは大変かもしれない、と思ったのですが、A4 より小さな用紙にすると、高齢者にとって読むのがつらい文字ポイント数になります。このため、A4 サイズが限度だと考えました。
読み取り画像の回転補正テスト
以下は、回転前の新投票用紙の画像です。スキャナーのオートフィーダー[ADF] を使うと、読み取り時に若干傾斜が発生します。
傾いている新投票用紙
この傾斜が補正できるのか、まず試しました。以下は回転後の新投票用紙の画像です。
傾きを補正した新投票用紙
ArUco マーカーを使用して、マークを基準に水平を見ることにしました。Pillow ライブラリの Image モジュールを使い、回転補正をしました。これにより、天地がひっくり返っていても補正されるので、開票作業時に紙の天地を整える必要が無くなったので、良いなと思いました。
1枚だけ、マークの読み取りテスト
取り急ぎ新投票用紙にマークを塗ったら読み取れるのか、テストを行いました。以下が、その読み取りテスト時の画像です。
新投票用紙の読み取りの様子
新投票用紙の読み取り結果
思っていた以上に読み取れていることが確認できましたが、塗っていないインデックス 15 が、カウントされていました。原因を調査するため、新投票用紙の読み取り結果を拡大したものが、以下の画像です。
新投票用紙の画像拡大図
読み取り状態1
読み取り状態2
マークシート枠は 100px × 100px に整えていますが枠外にはみ出た部分が、リサイズ時に次のインデックスのものに引っかかっているため、隣の 15 をマークした扱いになっていたのでした。
新投票用紙には、投票の仕方を明記すること/本選挙前に、マークシート方式のテストを案内して不安のある人に事前の投票練習を促す必要があること などを考えました。
複数枚の投票用紙のスキャナー読み込み
投票用紙マークシート A4 を紙で 20 枚印刷し、マークを手書きしました。自治会に設置されているプリンター複合機に合わせて読み取りテストを実施したところ、いろんなことが分かりましたので、以下のように整理・対応しました。
- 150dpi, 縦置き, 読み取りデータの回転 270° で読み取りを複数回実施し、読み取った投票用紙を、計 160 枚の扱いにした
- 読み取り時の dpi が 100 を切ると、マーカー読み取りによる読み取り範囲座標が確定できず、例外が発生した
- 例外処理は Python で書いたが、「解像度を高くし過ぎず読み取りが早く終わり、かつ例外が発生しにくい」辺りを狙い、150 dpi を設定した
- 自治会のプリンター複合機では、160 枚読み取りに約 6 分かかる
- 読み取りによって作成された JPEG ファイル名は、規定値では日付・時刻で一意に名前が付けられて保存される(これはスキャナソフトーの機能による)
- JPEG ファイルに Marksheet_001.jpg ~ Marksheet_150.jpg とリネームする処理は、Python で実施した
読み込んだ投票用紙のリネーム
複数枚の読み取りについて
読み取った投票結果の全体は、2 次元リストにしました。また、以下のような対応を実施しました。
- マーカーの読み取り不良が発生した場合に備えた、例外処理をいれた
- 1 枚あたりの投票結果は、要素 12 の 1 次元リストにした
- 誤認識を後追いできるように file_name を含むリストとした
- file_name, vote_01, vote_02, vote_03, vote_04, vote_05 ならば要素数 6 だが、誤って枠をはみ出て塗られた場合はそれも含めてカウントされるため、その分 例外枠を増やした
- 要素 12 番目にコメント枠を設けて、投票数が 6 以上の場合や、マーカー読み取り不良の場合、その旨を示す記述をいれた
投票数が 5 を超えた場合にコメントを付与した例
- 1枚あたりの投票結果 1次元リストを、2次元リストにした
- Numpy : the absolute basics of beginners にある Can you reshape an array? を参考にした
- https://numpy.org/doc/stable/user/absolute_beginners.html#can-you-reshape-an-array
- 投票結果全体の 2次元リストの終端に行追加した
- Numpy : the absolute basics of beginners にある Adding, removing, and sorting elements を参考にした
- https://numpy.org/doc/stable/user/absolute_beginners.html#adding-removing-and-sorting-elements
- 1 次元リストのままでは追加ができないので、.reshape() を用いて 2 次元リストに変換した。 変換後のリストを、全投票結果 2 次元リストの末尾に Numpy.concatenate() で追加した。
2次元リストに変換した例
各投票用紙を 1 レコードとして、投票者数 約 160 レコード分がまとまった 2 次元リストが出来上がったので、これを「個別投票結果」と呼ぶことにしました。
個別投票結果の Excel ファイル出力
読み取り誤認識が想定されるので、投票結果を Excel 形式ファイルに出力することとしました。
「投票数 6 以上」や「マーカー読み取り不良」とされた投票結果については、PCによるプログラム実行者と、開票者の最低 2 名以上が関わり、原紙との突合によって、Excel ファイル側を直接編集する形をとれば、開票作業での不正は防げると考えました。
- 投票結果全体の 2 次元リストを、Excel 形式ファイルに貼り付ける
- Pandas ライブラリを使用した
- 投票結果全体を Data Frame とする
- 投票結果番号は、全て Numeric に型を変換した
先頭行に項目を追加する
出力された Excel シート
出力結果は良好でした。ただ、コピー時に付いたシミや、(紙は普通のコピー用紙なので)紙そのものに含まれるわずかな物を、マークとして拾ってしまいます。これは、開票者立ち合いのもと、原紙との突合によって判定する仕組みにすれば、回避できるかなと考えました。
個別投票結果 Excel シートの取り込み
新投票用紙の Excel ファイルの別シートとして、取り込むのが良いと考えました。ここも Python で自動化しても良かったのですが、別件で ExcelVBA を書いていたのでそれをそのまま使うことにしました。
取り込んだ個別投票結果 Excel シートにオートフィルタを設定して、後で、不適切な投票をしているレコードを抽出して確認できるようにしました。
集計方法の方針決め
前述の通り、集計は Excel がもともと持っている基本的な機能と関数で、実施するのが最も手っ取り早いと思いました。例えば、開票結果 Excel シートを用意して、以下の対応をしようと考えました。
- COUNTIF()
- 指定した投票 No. が、投票結果 Excel ファイル内に幾つ有るのか、カウントする(対象者の得票数がわかる)
- RANK()
- 個々の対象者の得票数について、全員の中での順位を知る
- オートフィルタ設定と、並べ替え
- 得票数について、並べ替え(降順)を実施する
- オートフィルタで、結果のより分け
開票結果 Excel シートについて
このシートはあらかじめ作ります。この際に、RANK 関数の記述[数値(投票 No)・参照(列「得票数」)・順序(降順 0)]を、対象者全員について予め入れておくことにしました(参照がこの時点では無いので #N/A と出ますが、あまり気にしないことにしました(いい加減だなあ))。
後からシートを取り込めば成立する関数を、予め書いた例
個別投票結果 Excel シートの取り込み時に、得票数を求める処理を ExcelVBA に入れました。個別投票結果 Excel シートでの集計範囲を指定した COUNTIF 関数の引数定義を、Excel VBA を使って開票結果 Excel シートに書けると考えたためです。
個別投票結果内の得票数を数えたことで、RANK() が集計できた例
個別投票結果の手動確認
不適切な投票をしているレコードを抽出し、目視で投票用紙との突合を行うことにしました。JPEG ファイル名から、不具合のあった投票用紙を確認しますが、この作業は自動化せずに、人がやったほうがよいと考えました。
これは、「作業としての正確であること」よりも「複数人が関わることで不正が無かったことを担保すること」の方が、この規模の選挙においては重要かなと思ったためです。
確認した上で、個別投票結果 Excel シートの投票結果を変更すれば、開票結果 Excel シートの結果にも反映されました(Excel の通常通りの動きです)。
開票結果の表示
得票順に並べ替えを実施し、以下の通り開票結果がまとまりました。

通しで実施した結果
「160 枚の新投票用紙の読み込み」から「開票結果の表示まで」を通しでやって 10 分程度(目視での確認は無しで)になりました。
自治会Aでの本番実施
これまで約 10 人程の開票者が必要でしたが、5名の役員を選出にかかった時間は 15 分でした。目標としていた時間には近づけたかなと考えています。
A事例のその後
翌年には、役員選出の選挙実施日と、自治会内の世帯代表を集めて行う総会の実施日を、別日にすることで解決が図られました。このためA事例は、単年で終了しました。私の解決の仕方が間違っていたなと思いました。
そのようなわけで、冒頭の注意事項に書いた通り、この記事はお焚き上げだと思ってもらって…。
B事例の展開
B事例は、或る年の早い段階からご相談をいただきました。投票方式や投票用紙は、A事例と異なりますので少し変更を加えました。こちらは無事に総会を乗り切り、翌年度以降も続いていきそうだとのことになりました。
所感
選挙に関していろんな資料を読むことができ、大変勉強になりました。特に、個人情報保護法については、詳しく調べることが多かったです。以下のリンクにある通り、自治会組織も個人情報取扱事業者です。
個人情報保護委員会 広報資料 「自治会・同窓会等向け会員名簿を作るときの注意事項ハンドブック(令和5年12月)」
最後に、現在何らかの役を務めている皆さんへ、心からのエールを送りたいと思います。
Discussion