監視カメラの映像を画像で連続保存する
実行画面

タイマー機能を利用して連続保存します。
Form1




Form1.cs
using Microsoft.WindowsAPICodePack.Dialogs;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Runtime.InteropServices.ComTypes;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using static System.Net.Mime.MediaTypeNames;
namespace FrmGetCameraJpg
{
public partial class Form1 : Form
{
string IpCamera;
private Rectangle cropArea; // 選択された範囲を保持
private Point startPoint; // マウスを押し下げた位置
private bool isSelecting = false; // 選択中かどうか
// --- 追加変数 ---
private Timer monitoringTimer;
private bool isBusy = false; // 二重実行防止フラグ
// ----------------
//control clsResize 画面表示を拡大縮小します。
clsResize _form_resize;
public Form1()
{
InitializeComponent();
//clsResize
_form_resize = new clsResize(this); //I put this after the initialize event to be sure that all controls are initialized properly
this.Load += new EventHandler(_Load); //This will be called after the initialization // form_load
this.Resize += new EventHandler(_Resize); //form_resize
//
cmbCamera.Items.Add("192.168.xx.xx0");
cmbCamera.Items.Add("192.168.xx.xx1");
cmbCamera.Items.Add("192.168.xx.xx2");
cmbCamera.Items.Add("192.168.xx.xx3");
cmbCamera.SelectedIndex = 1;
IpCamera = cmbCamera.GetItemText(cmbCamera.SelectedItem);
//Console.WriteLine(IpCamera);
this.Text = @"FrmRiverFromCamera: { " + IpCamera + @" }";
txtOutDir.Text = @"C:\Users\ssk-m720\Pictures";
string configPath = AppDomain.CurrentDomain.BaseDirectory + "Teisu.txt";
int interval = 2000;
if (File.Exists(configPath))
{
string[] lines = File.ReadAllLines(configPath);
if (lines.Length >= 4)
{
cmbCamera.Text = lines[0];
this.IpCamera = lines[0]; // 192.168.xx.xx1
this.txtOutDir.Text = lines[1]; // 保存先パス
interval = int.Parse(lines[2]); // 2000
string cameraName = lines[3]; // 川上流
this.Text = $"監視:{cameraName} ({this.IpCamera})";
}
}
// タイマーの初期設定
monitoringTimer = new Timer();
monitoringTimer.Interval = interval; // 2秒間隔(環境に合わせて調整してください)
monitoringTimer.Tick += MonitoringTimer_Tick;
}
//clsResize _Load
private void _Load(object sender, EventArgs e)
{
_form_resize._get_initial_size();
}
//clsResize _Resize
private void _Resize(object sender, EventArgs e)
{
_form_resize._resize();
}
private void 終了ToolStripMenuItem1_Click(object sender, EventArgs e)
{
this.Close();
}
private void 終了ToolStripMenuItem_Click(object sender, EventArgs e)
{
this.Close();
}
private void btnEnd_Click(object sender, EventArgs e)
{
this.Close();
}
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
IpCamera = IpCamera = cmbCamera.GetItemText(cmbCamera.SelectedItem);
this.Text = @"FrmRiverFromCamera: { " + IpCamera + @" }";
}
private async void btnGet_Click(object sender, EventArgs e)
{
await GetCameraJpg();
}
// タイマーイベント(ここでGetCameraJpgを呼ぶ)
private async void MonitoringTimer_Tick(object sender, EventArgs e)
{
if (isBusy) return; // 前の処理中ならスキップ
try
{
isBusy = true;
// --- ランプを点灯(明るい緑) ---
lblStatus.BackColor = Color.LawnGreen;
await GetCameraJpg(); // 既存のメソッドを呼び出す
// 成功したら少し待ってから消灯(暗い緑)させると「チカッ」と見えます
await Task.Delay(200);
lblStatus.BackColor = SystemColors.Control;//Color.LightGray;
}catch
{
// 失敗した時は「赤」にすると異常がすぐわかります
lblStatus.BackColor = Color.Red;
}
finally
{
isBusy = false; // 終わったらフラグを下ろす
}
}
// モニタリング開始/停止ボタン(新設する場合)
private void btnStart_Click(object sender, EventArgs e)
{
if (!monitoringTimer.Enabled)
{
monitoringTimer.Start();
btnStart.Text = "停止";
btnStart.BackColor = Color.Red; // 動作中を強調
}
else
{
monitoringTimer.Stop();
btnStart.Text = "モニタリング開始";
btnStart.BackColor = SystemColors.ActiveCaptionText;
}
}
// GetCameraJpg 内の注意点:
// 既存のコードにある「client.DefaultRequestHeaders.ConnectionClose = true;」は
// 連続取得時の「セッション詰まり」を防ぐために非常に有効です。そのまま維持してください。
private async Task GetCameraJpg()
{
string cameraUrl = $@"http://{IpCamera}/tmpfs/auto.jpg";
// 毎回新しくハンドラーとクライアントを作る(250番のセッションをリセットするため)
var handler = new HttpClientHandler()
{
Credentials = new System.Net.NetworkCredential("userxxxxxx", "passwordyyyyyyyy"),
PreAuthenticate = true
};
try
{
using (var client = new HttpClient(handler))
{
// ブラウザのふり
client.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36");
// 接続を維持せず、終わったらすぐ切る
client.DefaultRequestHeaders.ConnectionClose = true;
client.Timeout = TimeSpan.FromSeconds(5);
using (var stream = await client.GetStreamAsync(cameraUrl))
using (var ms = new MemoryStream())
{
try
{
// 4KBずつ「手動」でコピーする
byte[] buffer = new byte[4096];
int read;
// ここでエラーが出ても catch に飛ばず、読み込めた分だけで処理を続行
while ((read = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
ms.Write(buffer, 0, read);
}
}
catch (Exception copyEx)
{
// 「コピー中にエラー」が出ても無視!
Console.WriteLine("サイズオーバー?:コピー中断。強行します: " + copyEx.Message);
}
// ★ ここで rawBytes を作成!
// ms に入っている現在のデータをすべてバイト配列として取り出す
byte[] rawBytes = ms.ToArray();
// 1バイトでも届いていれば画像化を試みる
if (ms.Length > 0)
{
ms.Position = 0;
using (var img = System.Drawing.Image.FromStream(ms))
{
pictureBox1.Invoke(new Action(() => {
if (pictureBox1.Image != null) pictureBox1.Image.Dispose();
pictureBox1.Image = (System.Drawing.Image)img.Clone();
pictureBox1.SizeMode = PictureBoxSizeMode.Zoom;
}));
lblStatus.BackColor = Color.LawnGreen;
// ファイルとして保存(ここではJPEGで保存)
if (chkSave.Checked == true)
{
// 保存先のパスを日付フォルダ付きにする例
string dateDir = Path.Combine(txtOutDir.Text, DateTime.Now.ToString("yyyyMMdd"));
if (!Directory.Exists(dateDir)) Directory.CreateDirectory(dateDir);
// 現在時刻
string timeText = DateTime.Now.ToString("yyyyMMdd_HHmmss");
string filePath = dateDir + @"\" + IpCamera + @"_" + timeText + @".jpg";
img.Save(filePath, System.Drawing.Imaging.ImageFormat.Jpeg);
}
// 2. 写真用トリミング
if (rawBytes.Length > 0)
{
byte[] photoBytes = GetCroppedImageBytes(rawBytes);
using (var msCrop = new MemoryStream(photoBytes))
{
System.Drawing.Image img2 = System.Drawing.Image.FromStream(msCrop);
if (chkSave.Checked == true)
{
// 保存先のパスを日付フォルダ付きにする例
string dateDir = Path.Combine(txtOutDir.Text, DateTime.Now.ToString("yyyyMMdd"));
if (!Directory.Exists(dateDir)) Directory.CreateDirectory(dateDir);
// 現在時刻
string timeText = DateTime.Now.ToString("yyyyMMdd_HHmmss");
string filePath = dateDir + @"\" + IpCamera + @"_" + timeText + "_crop.jpg";
img2.Save(filePath, System.Drawing.Imaging.ImageFormat.Jpeg);
}
}
}
}
}
}
}
}
catch (Exception ex)
{
// ログイン自体に失敗した場合などはここ
Console.WriteLine("致命的エラー: " + ex.Message);
lblStatus.BackColor = Color.Red;
}
}
private byte[] GetCroppedImageBytes(byte[] originalBytes)
{
// 【重要】PCで別作業中にウィンドウが最小化されると、サイズが0になり計算が狂います。
// その場合は、座標計算をせず、前回の正常な設定で無理やり切り出すか、処理をスキップします。
if (pictureBox1.ClientSize.Width <= 0 || pictureBox1.ClientSize.Height <= 0)
{
// もしサイズが取れないなら、今回はアップロードを諦めて終了する
return null;
}
using (var ms = new MemoryStream(originalBytes))
using (var originalBitmap = new Bitmap(ms))
{
if (cropArea.Width <= 0 || cropArea.Height <= 0) return originalBytes;
// 1. 座標計算(以前のロジック維持)
float pbRatio = (float)pictureBox1.ClientSize.Width / pictureBox1.ClientSize.Height;
float imgRatio = (float)originalBitmap.Width / originalBitmap.Height;
float displayWidth, displayHeight, offsetX = 0, offsetY = 0;
if (imgRatio > pbRatio)
{
displayWidth = pictureBox1.ClientSize.Width;
displayHeight = displayWidth / imgRatio;
offsetY = (pictureBox1.ClientSize.Height - displayHeight) / 2;
}
else
{
displayHeight = pictureBox1.ClientSize.Height;
displayWidth = displayHeight * imgRatio;
offsetX = (pictureBox1.ClientSize.Width - displayWidth) / 2;
}
double ratio = (double)originalBitmap.Width / displayWidth;
Rectangle realArea = new Rectangle(
(int)((cropArea.X - offsetX) * ratio),
(int)((cropArea.Y - offsetY) * ratio),
(int)(cropArea.Width * ratio),
(int)(cropArea.Height * ratio)
);
realArea.Intersect(new Rectangle(0, 0, originalBitmap.Width, originalBitmap.Height));
if (realArea.Width <= 0 || realArea.Height <= 0) return originalBytes;
// 追加:計算後の切り出し範囲が異常(例えば全画面の真ん中など)になっていないかチェック
if (realArea.Width <= 10 || realArea.Height <= 10)
{
// 異常に小さい場合は、あえて転送せずにログを残す
return null;
}
// --- ここから描画処理を追加 ---
using (Bitmap cropped = originalBitmap.Clone(realArea, originalBitmap.PixelFormat))
{
using (Graphics g = Graphics.FromImage(cropped))
{
// アンチエイリアス設定(文字を滑らかにする)
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
string timeText = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
// フォントサイズは画像の高さに合わせて自動調整(画像の高さの約10%)
float fontSize = cropped.Height * 0.1f;
if (fontSize < 12) fontSize = 12; // 小さすぎ防止
if (fontSize > 24) fontSize = 24; // 大きすぎ防止
using (Font drawFont = new Font("Arial", fontSize, FontStyle.Bold))
{
// 右上に配置するための座標計算
SizeF textSize = g.MeasureString(timeText, drawFont);
//float x = cropped.Width - textSize.Width - 10;
//float y = 10;
// 【右下】に配置するための座標計算
// 右下の配置計算(マージンを少し多めの20pxに設定)
float marginx = 20;//<--- 20
float marginy = 20;//<---20
// 計算したx, yがマイナスにならないようにMath.Maxでガード
float x = Math.Max(5, (cropped.Width - textSize.Width) / 2 - marginx);
float y = Math.Max(5, cropped.Height - textSize.Height - marginy);
//日付追加
// 1. 影(黒)を描画して視認性を確保
//g.DrawString(timeText, drawFont, Brushes.Black, new PointF(x + 1, y + 1));
// 2. 本文(黄色)を描画
//g.DrawString(timeText, drawFont, Brushes.Yellow, new PointF(x, y));
}
}
using (var msOutput = new MemoryStream())
{
cropped.Save(msOutput, System.Drawing.Imaging.ImageFormat.Jpeg);
return msOutput.ToArray();
}
}
// --- 描画処理ここまで ---
}
}
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
isSelecting = true;
startPoint = e.Location;
}
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
if (isSelecting)
{
int x = Math.Min(startPoint.X, e.X);
int y = Math.Min(startPoint.Y, e.Y);
int width = Math.Abs(startPoint.X - e.X);
int height = Math.Abs(startPoint.Y - e.Y);
cropArea = new Rectangle(x, y, width, height);
pictureBox1.Invalidate(); // 再描画を指示
}
}
private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
{
// MouseUpイベントなどでチェック
if (cropArea.Width < 100 || cropArea.Height < 50)
{
MessageBox.Show("選択範囲が小さすぎます。もう少し広く囲んでください。");
cropArea = Rectangle.Empty; // リセット
}
isSelecting = false;
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
if (cropArea != null && cropArea.Width > 0 && cropArea.Height > 0)
{
using (Pen pen = new Pen(Color.Red, 1))
{
e.Graphics.DrawRectangle(pen, cropArea);
}
}
}
private void pictureBox1_MouseDoubleClick(object sender, MouseEventArgs e)
{
// PictureBox上の描画を消去して再描画
pictureBox1.Invalidate();
}
private void btnOutDir_Click(object sender, EventArgs e)
{
using (CommonOpenFileDialog cofd = new CommonOpenFileDialog())
{
cofd.Title = "保存用フォルダーを選択。";
cofd.InitialDirectory = @"c:\";
// フォルダを選択できるようにする
cofd.IsFolderPicker = true;
if (cofd.ShowDialog() == CommonFileDialogResult.Ok)
{
//選択されたフォルダを表示する
txtOutDir.Text = cofd.FileName;
}
}
}
}
}