Skip to content

Instantly share code, notes, and snippets.

@mitakeck
Last active August 29, 2015 14:04
Show Gist options
  • Save mitakeck/e13247b827f688e5f6fe to your computer and use it in GitHub Desktop.
Save mitakeck/e13247b827f688e5f6fe to your computer and use it in GitHub Desktop.
K-means でラインをクラスタリングした時のコードメモ
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace LineTraceDemo
{
/// <summary>
/// 特徴点を管理するためのクラス
/// </summary>
class FeaturePoint
{
// 重さ
public int Weight;
// 位置情報
public int Point;
// 重みつき位置情報
private int PointWithWeight;
// 所属クラスタ情報
public int ClusterLabel;
/// <summary>
/// コンストラクタ
/// </summary>
/// <param name="w">Weight</param>
/// <param name="p">Point</param>
/// <param name="cl">ClusterLabel</param>
public FeaturePoint(int w, int p, int cl=-1)
{
this.Weight = w;
this.Point = p;
this.ClusterLabel = cl;
this.PointWithWeight = this.Point * this.Weight;
}
/// <summary>
/// クラスタの重さを設定する
/// </summary>
/// <param name="w">Weight</param>
/// <returns></returns>
public int SetWeight(int w)
{
this.Weight = w;
this.PointWithWeight = this.Point * this.Weight;
return this.Weight;
}
/// <summary>
/// クラスタの位置情報を設定する
/// </summary>
/// <param name="point">Point</param>
/// <returns></returns>
public int SetPoint(int point)
{
this.Point = point;
this.PointWithWeight = this.Point * this.Weight;
return this.Point;
}
/// <summary>
/// 重みつきの位置情報データを返却する
/// </summary>
/// <returns></returns>
public int GetPointWithWeight()
{
return this.PointWithWeight;
}
/// <summary>
/// Debug 出力用
/// </summary>
/// <returns></returns>
public String ToString()
{
return "[FeaturePoint] Weight:" + this.Weight + ", Point:" + this.Point + ", ClusterLabel:" + this.ClusterLabel + "";
}
}
}
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Collections.Generic;
namespace LineTraceDemo
{
/// <summary>
/// 擬似ライントレーサークラス
/// </summary>
public class LineTracer
{
// 画面幅
private int PixWidth;
// 現在トレースしているラインの位置
private float PreX;
// 現在認識しているラインの情報
private List<float> ClusterPoints;
// 現在トレースしているラインのインデックス
private int CurrentClusterIndex;
// 直前に認識された 2 本のライン幅
private int LineDist;
// 直前に認識されたライン本数
private int LineCount;
/// <summary>
/// コンストラクタ
/// </summary>
/// <param name="pw"></param>
public LineTracer(int pw)
{
this.PixWidth = pw;
}
/// <summary>
/// コンストラクタ
/// </summary>
public LineTracer()
{
this.PixWidth = 0;
}
/// <summary>
/// 内部変数の初期化用関数
/// </summary>
private void init()
{
this.PreX = -1;
this.CurrentClusterIndex = -1;
this.LineDist = -100;
this.LineCount = 0;
}
/// <summary>
/// 画面幅を指定する
/// </summary>
/// <param name="pw"></param>
/// <returns></returns>
public int SetPixWidth(int pw)
{
this.PixWidth = pw;
return this.PixWidth;
}
/// <summary>
/// 現在トレースしているラインインデックスを返却する
/// </summary>
/// <returns></returns>
public int GetCurrentClusterIndex()
{
return this.CurrentClusterIndex;
}
/// <summary>
/// 新たにライン情報をセットする
/// </summary>
/// <param name="cp"></param>
/// <returns></returns>
public String SetClusterPoints(List<float> cp)
{
this.ClusterPoints = cp;
this.GetNearyCluster();
return this.ToString();
}
/// <summary>
/// 現在認識しているライン情報から、今トレースしているラインのインデックスを返却する
/// </summary>
/// <returns></returns>
private int GetNearyCluster()
{
int mostNearyClusterIndex;
// ラインは認識されている
if (this.ClusterPoints.Count > 0)
{
// 認識されたラインは 2 本
if (this.ClusterPoints.Count == 2)
{
// 直前の状態の時はラインが検出されていなかった場合
if (this.LineCount == 0)
{
// 瞬間的にラインが二本検出された場合の処理
// より画面中央に近い方のラインを currentLine にする
mostNearyClusterIndex = 0;
for (int i = 0; i < this.ClusterPoints.Count; i++)
{
if (Math.Abs(this.PixWidth / 2 - this.ClusterPoints[i]) < Math.Abs(this.PixWidth / 2 - this.ClusterPoints[mostNearyClusterIndex]))
{
mostNearyClusterIndex = i;
}
}
}
// 直前の状態の時にはラインは 1 本だった場合の処理
/* else if (this.LineCount == 1)
{
}
*/
else
{
if (Math.Abs((int)Math.Abs(this.ClusterPoints[0] - this.ClusterPoints[1]) - this.LineDist) < 5)
{
// 検出された 2 本のラインの間隔が直前の状態とほぼ同じ場合、直前の currenIndex を受け継ぐ
mostNearyClusterIndex = this.CurrentClusterIndex;
}
else
{
// 検出された 2 本のラインのうち、curreIndex に近いほうの値を新たな currentIndex にする
mostNearyClusterIndex = 0;
for (int i = 0; i < this.ClusterPoints.Count; i++)
{
if (Math.Abs(this.PreX - this.ClusterPoints[i]) < Math.Abs(this.PreX - this.ClusterPoints[mostNearyClusterIndex]))
{
mostNearyClusterIndex = i;
}
}
this.PreX = this.ClusterPoints[mostNearyClusterIndex];
}
}
}
else
{
// 認識されているライン本数は 1 本なのでそのラインを currentLine にする
mostNearyClusterIndex = 0;
}
}
else
{
mostNearyClusterIndex = -1;
this.PreX = -1;
}
// 次回の処理の為に現在の情報を記録する
if (this.ClusterPoints.Count == 2)
{
this.LineDist = (int)Math.Abs(this.ClusterPoints[0] - this.ClusterPoints[1]);
}
else
{
this.LineDist = -100;
}
this.LineCount = this.ClusterPoints.Count;
// 現在トレースしているライン情報を更新する
this.CurrentClusterIndex = mostNearyClusterIndex;
if (this.CurrentClusterIndex != -1)
{
this.PreX = this.ClusterPoints[this.CurrentClusterIndex];
}
return mostNearyClusterIndex;
}
/// <summary>
/// 入力されたライン情報から現在トレースしているラインにより近いラインインデックスを返却する
/// </summary>
/// <param name="cp"></param>
/// <returns></returns>
private int GetNearyCluster(List<float> cp)
{
int mostNearyClusterIndex;
if (cp.Count > 0)
{
mostNearyClusterIndex = 0;
for (int i = 0; i < cp.Count; i++)
{
if (Math.Abs(this.PreX-cp[i]) < Math.Abs(this.PreX-cp[mostNearyClusterIndex]))
{
mostNearyClusterIndex = i;
}
}
}
else
{
mostNearyClusterIndex = -1;
}
return mostNearyClusterIndex;
}
/// <summary>
/// デバッグ出力用
/// </summary>
/// <returns></returns>
public String ToString()
{
return "[LineTracer] CurrentLine:" + this.CurrentClusterIndex + "";
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Media.Imaging;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Automation;
using System.Net.Sockets;
using System.Text;
using System.Diagnostics;
namespace LineTraceDemo
{
public partial class MainPage : UserControl
{
CaptureSource captureSource = new CaptureSource();
VideoCaptureDevice webcam = CaptureDeviceConfiguration.GetDefaultVideoCaptureDevice();
//AudioCaptureDevice audio = CaptureDeviceConfiguration.GetDefaultAudioCaptureDevice();
// Create the ImageBrush to store the captured image.
ImageBrush capturedImage = new ImageBrush();
List<TimeSpan> list = new List<TimeSpan>();
List<string> listString = new List<string>();
DateTime dt = DateTime.Now;
string[] ribbonColors =
{
"赤",
"青",
"緑"
};
string[] lostConfigs =
{
"そのまま",
"直進する"
};
float scalFactor = 1.5F;
int radiusRange = 10;
public MainPage()
{
InitializeComponent();
}
LineTracer lineTracer = new LineTracer();
/// <summary>
/// Loaded
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
/*
scaleFactorText.Text = scalFactor.ToString();
radiusRangeText.Text = radiusRange.ToString();
captureSource.VideoCaptureDevice = webcam;
//captureSource.AudioCaptureDevice = audio;
Devices.ItemsSource = CaptureDeviceConfiguration.GetAvailableAudioCaptureDevices();
if (Devices.Items.Count > 0)
{
Devices.SelectedIndex = 0;
}
foreach (var ribbonColor in ribbonColors)
{
RibbonColorsComboBox.Items.Add(ribbonColor);
}
RibbonColorsComboBox.SelectedIndex = 0;
foreach (var lostConfig in lostConfigs)
{
LostConfigsComboBox.Items.Add(lostConfig);
}
LostConfigsComboBox.SelectedIndex = 0;
*/
VideoBrush webcamBrush = new VideoBrush();
// Set the source on the VideoBrush used to display the video.
webcamBrush.SetSource(captureSource);
// Set the Fill property on the Rectangle to the VideoBrush.
webCameraRect1.Fill = webcamBrush;
// Paint the Rectangle with the ImageBrush.
capCameraRect2.Fill = capturedImage;
captureSource.CaptureImageCompleted += new EventHandler<CaptureImageCompletedEventArgs>(captureSource_CaptureImageCompleted);
// Request access to the device and verify the VideoCaptureDevice is not null.
if (CaptureDeviceConfiguration.RequestDeviceAccess() && captureSource.VideoCaptureDevice != null)
{
try
{
captureSource.Start();
}
catch (InvalidOperationException ex)
{
// Notify user that the webcam could not be started.
MessageBox.Show("There was a problem starting the webcam " +
"If using a Mac, verify default devices are set correctly. " +
"Right click on a Silverlight app to access the Configuration setings.");
}
}
if (CaptureDeviceConfiguration.RequestDeviceAccess() && captureSource.VideoCaptureDevice != null)
{
try
{
captureSource.Start();
}
catch (InvalidOperationException ex)
{
// Verify the VideoCaptureDevice is not null.
if (captureSource.VideoCaptureDevice != null)
{
captureSource.Stop();
}
}
}
CompositionTarget.Rendering += new EventHandler(CompositionTarget_Rendering);
}
# region webCamera
private void StartButton_Click(object sender, RoutedEventArgs e)
{
// Request access to the device and verify the VideoCaptureDevice is not null.
if (CaptureDeviceConfiguration.RequestDeviceAccess() && captureSource.VideoCaptureDevice != null)
{
try
{
captureSource.Start();
}
catch (InvalidOperationException ex)
{
// Notify user that the webcam could not be started.
MessageBox.Show("There was a problem starting the webcam " +
"If using a Mac, verify default devices are set correctly. " +
"Right click on a Silverlight app to access the Configuration setings.");
}
}
if (CaptureDeviceConfiguration.RequestDeviceAccess() && captureSource.VideoCaptureDevice != null)
{
try
{
captureSource.Start();
}
catch (InvalidOperationException ex)
{
// Verify the VideoCaptureDevice is not null.
if (captureSource.VideoCaptureDevice != null)
{
captureSource.Stop();
}
}
}
}
private void StopButton_Click(object sender, RoutedEventArgs e)
{
// Verify the VideoCaptureDevice is not null.
if (captureSource.VideoCaptureDevice != null)
{
captureSource.Stop();
}
}
private void CapButton_Click(object sender, RoutedEventArgs e)
{
// Verify the VideoCaptureDevice is not null and the device is started.
if (captureSource.VideoCaptureDevice != null && captureSource.State == CaptureState.Started)
{
captureSource.CaptureImageAsync();
}
}
void CompositionTarget_Rendering(object sender, EventArgs e)
{
if (captureSource.VideoCaptureDevice != null && captureSource.State == CaptureState.Started)
{
captureSource.CaptureImageAsync();
}
}
void captureSource_CaptureImageCompleted(object sender, CaptureImageCompletedEventArgs e)
{
WriteableBitmap resultBitmap;
resultBitmap = modifyRed(e.Result);
WriteableBitmap result = findCenterPoints(resultBitmap);
capturedImage.ImageSource = result;
lineTracer.SetPixWidth(result.PixelWidth);
}
# endregion
# region 画処理
private WriteableBitmap modifyBlue(WriteableBitmap baseImage)
{
WriteableBitmap blueImage = new WriteableBitmap(baseImage.PixelWidth, baseImage.PixelHeight);
int rgb, r, g, b;
//PixelsというInt型の配列を順番に処理
for (int i = (blueImage.PixelWidth * (blueImage.PixelHeight - 5)); i < blueImage.Pixels.Length; i++)
{
//wbitmap.Pixels[i] = ((wbitmap.Pixels[i] >> 24) & 0xff) << 24 | //A(アルファ)
// 255 - ((wbitmap.Pixels[i] >> 16) & 0xff) << 16 | //R(レッド)
// 255 - ((wbitmap.Pixels[i] >> 8) & 0xff) << 8 | //G(グリーン)
// 255 - wbitmap.Pixels[i] & 0xff; //B(ブルー)
rgb = baseImage.Pixels[i];
r = (rgb >> 16) & 0xff;
g = (rgb >> 8) & 0xff;
b = rgb & 0xff;
if (b > 40 && b > 2 * r && b > 2 * g)
{
blueImage.Pixels[i] = int.MinValue;
}
else
{
blueImage.Pixels[i] = int.MaxValue;
}
}
//ビットマップの描画を要求
blueImage.Invalidate();
capturedImage.ImageSource = blueImage;
return blueImage;
}
private WriteableBitmap modifyGreen(WriteableBitmap baseImage)
{
WriteableBitmap greenImage = new WriteableBitmap(baseImage.PixelWidth, baseImage.PixelHeight);
int rgb, r, g, b;
//PixelsというInt型の配列を順番に処理
for (int i = (greenImage.PixelWidth * (greenImage.PixelHeight - 5)); i < greenImage.Pixels.Length; i++)
{
//wbitmap.Pixels[i] = ((wbitmap.Pixels[i] >> 24) & 0xff) << 24 | //A(アルファ)
// 255 - ((wbitmap.Pixels[i] >> 16) & 0xff) << 16 | //R(レッド)
// 255 - ((wbitmap.Pixels[i] >> 8) & 0xff) << 8 | //G(グリーン)
// 255 - wbitmap.Pixels[i] & 0xff; //B(ブルー)
rgb = baseImage.Pixels[i];
r = (rgb >> 16) & 0xff;
g = (rgb >> 8) & 0xff;
b = rgb & 0xff;
if (g > 40 && g > 2 * r && g > 2 * b)
{
greenImage.Pixels[i] = int.MinValue;
}
else
{
greenImage.Pixels[i] = int.MaxValue;
}
}
//ビットマップの描画を要求
greenImage.Invalidate();
capturedImage.ImageSource = greenImage;
return greenImage;
}
private WriteableBitmap modifyRed(WriteableBitmap baseImage)
{
WriteableBitmap redImage = new WriteableBitmap(baseImage.PixelWidth, baseImage.PixelHeight);
int rgb, r, g, b;
//PixelsというInt型の配列を順番に処理
for (int i = (redImage.PixelWidth * (redImage.PixelHeight - 5)); i < redImage.Pixels.Length; i++)
{
//wbitmap.Pixels[i] = ((wbitmap.Pixels[i] >> 24) & 0xff) << 24 | //A(アルファ)
// 255 - ((wbitmap.Pixels[i] >> 16) & 0xff) << 16 | //R(レッド)
// 255 - ((wbitmap.Pixels[i] >> 8) & 0xff) << 8 | //G(グリーン)
// 255 - wbitmap.Pixels[i] & 0xff; //B(ブルー)
rgb = baseImage.Pixels[i];
r = (rgb >> 16) & 0xff;
g = (rgb >> 8) & 0xff;
b = rgb & 0xff;
if (r > 40 && r > 2 * g && r > 2 * b)
{
redImage.Pixels[i] = int.MinValue;
}
else
{
redImage.Pixels[i] = int.MaxValue;
}
}
//ビットマップの描画を要求
redImage.Invalidate();
// capturedImage.ImageSource = redImage;
return redImage;
}
/// <summary>
/// 画像の指定行に含まれる特徴点を検出する
/// </summary>
/// <param name="filterdBitmapData"></param>
/// <param name="offsetBottom"></param>
/// <returns>特徴点のリスト</returns>
private List<FeaturePoint> getFeaturePonits(WriteableBitmap filterdBitmapData, int offsetBottom=0)
{
if (filterdBitmapData == null)
{
return null;
}
const int LABEL_WIDTH = 10; // ラベルの幅
const int thresholdPixels = LABEL_WIDTH; // いくつの有色ピクセルが連続したら領域とみなすかの閾値
//int rgb;
int startPoint = -1, endPoint = -1;
// 特徴点を格納する List
List<FeaturePoint> featurePoints = new List<FeaturePoint>();
int pixelCounter = 0;
// 画像の対象の行だけ走査を行う
int targetLine = filterdBitmapData.PixelHeight - 2 - offsetBottom;
bool foundStartPoint = false;
// 対象行までのオフセットを事前に算出
int targetLinePixOffsets = targetLine * filterdBitmapData.PixelWidth;
for (int j = 0; j < filterdBitmapData.PixelWidth; j++)
{
// 注目画素の値を確認
if (filterdBitmapData.Pixels[j + targetLinePixOffsets] == int.MinValue)
{
pixelCounter++;
// 既に開始位置が存在しているか否か
if (startPoint == -1)
{
startPoint = j;
foundStartPoint = true;
}
}
else
{
if (foundStartPoint && pixelCounter >= thresholdPixels)
{
endPoint = j - 1;
// 開始座標と終了座標から中心座標を算出
FeaturePoint fp = new FeaturePoint(pixelCounter, (int)((endPoint + startPoint) / 2));
featurePoints.Add(fp); // スタック
// 各種値を初期化
startPoint = -1;
endPoint = -1;
}
pixelCounter = 0;
}
}
return featurePoints;
}
/// <summary>
/// 特徴点情報からクラスタ中点の情報を返却する
/// </summary>
/// <param name="featurePoints"></param>
/// <param name="pixelWidth"></param>
/// <returns></returns>
private List<float> getClusterPoints(List<FeaturePoint> featurePoints, int pixelWidth)
{
int featureCount = featurePoints.Count();
Debug.WriteLine("featurePoint.Count: {0}", featureCount);
List<float> clusterPoints = new List<float>();
if (featureCount > 0)
{
// そもそも featrePoint が一点しかない場合
if (featureCount == 1)
{
//
// Debug.WriteLine("featurePoint が一点しかない場合");
clusterPoints.Add(featurePoints[0].Point);
}
// featurePoint が二点しかない場合
else if (featureCount == 2)
{
// Debug.WriteLine("featurePoint が二点の場合");
// その二点がクラスタ中心
clusterPoints.Add(featurePoints[0].Point);
clusterPoints.Add(featurePoints[1].Point);
}
// featurePoint が三点以上ある場合
else
{
// Debug.WriteLine("featurePoint が三点以上ある場合");
// 走査は両端に最初のクラスタ中心を設定してから行う。
clusterPoints.Add(0);
clusterPoints.Add(pixelWidth);
// 現在のクラスタ中点が安定していしたものか否か
bool isStableCluster;
// クラスタに所属する特徴点の数を格納する
int[] clusterCounts = new int[2];
do
{
// 各種初期化
isStableCluster = true;
clusterCounts[0] = 0;
clusterCounts[1] = 0;
for (int i = 0; i < featureCount; i++)
{
int clusterLabel = featurePoints[i].ClusterLabel;
// 特徴点の所属クラスタを確定する
featurePoints[i].ClusterLabel = Math.Abs(featurePoints[i].Point - clusterPoints[0]) < Math.Abs(featurePoints[i].Point - clusterPoints[1]) ? 0 : 1;
// 処理高速化のためにクラスタに所属する特徴点数を前もってカウントアップしていく
clusterCounts[featurePoints[i].ClusterLabel] += featurePoints[i].Weight;
Debug.WriteLine("Move {0}: {1} -> {2}", i, clusterLabel, featurePoints[i].ClusterLabel);
if (clusterLabel != featurePoints[i].ClusterLabel)
{
// ひとつでもクラスタが変更された特徴点があれば、ステイブルフラグを折る
isStableCluster = false;
}
}
// クラスタ中点再計算のため、値をリセットする
clusterPoints[0] = 0;
clusterPoints[1] = 0;
for (int i = 0; i < featureCount; i++)
{
// クラスタラベルを取得
int cl = featurePoints[i].ClusterLabel;
if (clusterCounts[cl] != 0)
{
// 特徴点の重みを考慮してクラスタ中点を再計算する
clusterPoints[cl] += (featurePoints[i].GetPointWithWeight() / clusterCounts[cl]);
}
}
// ステイブルフラグで再度クラスタ計算処理を行うか否かを判断する
} while (!isStableCluster);
}
}
return clusterPoints;
}
private WriteableBitmap findCenterPoints(WriteableBitmap filterdBitmapData)
{
if (filterdBitmapData == null)
{
return null;
}
// 特徴点情報取得
List<FeaturePoint> featurePoints = getFeaturePonits(filterdBitmapData);
// クラスタ中点取得
List<float> clusterPoints = getClusterPoints(featurePoints, filterdBitmapData.PixelWidth);
// LineTracer にクラスタ情報をセットする
lineTracer.SetClusterPoints(clusterPoints);
// Debug.WriteLine(lineTracer.ToString());
Debug.WriteLine("send data: {currentLine:" + lineTracer.GetCurrentClusterIndex() + ", foundLines:{" + string.Join(",", clusterPoints.ToArray()) + "}");
//* 最終的なクラスタ中点の値を出力
for (int i = 0; i < clusterPoints.Count(); i++)
{
Debug.WriteLine("クラスタ中心 {0}:{1}", i, clusterPoints[i]);
}
//*/
for (int i = 0; i < filterdBitmapData.Pixels.Length; i++)
{
filterdBitmapData.Pixels[i] = int.MaxValue;
}
/* 特徴点を描写
if (featureCount > 0)
{
for (int target = 0; target < featureCount; target++)
{
Debug.WriteLine("TargetX: {0}", featurePoints[target].Point);
int targetX = (int)featurePoints[target].Point;
for (int i = 0; i < filterdBitmapData.PixelHeight; i++)
{
filterdBitmapData.Pixels[targetX + (i * filterdBitmapData.PixelWidth)] = int.MinValue;
}
}
}
//*/
//* クラスタ中点を描写
for (int i = 0; i < clusterPoints.Count(); i++)
{
int targetX = (int)clusterPoints[i];
int targetLinePixOffset = 0;
for (int j = 0; j < filterdBitmapData.PixelHeight; j++)
{
filterdBitmapData.Pixels[targetX + targetLinePixOffset] = int.MinValue;
targetLinePixOffset += filterdBitmapData.PixelWidth;
}
}
//*/
return filterdBitmapData;
}
# endregion
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment