<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Software Engineer</title>
    <link>https://s-engineer.tistory.com/</link>
    <description>English &amp;amp; Software Engineering Blog - Sean</description>
    <language>ko</language>
    <pubDate>Mon, 13 Apr 2026 17:45:02 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>J-sean</managingEditor>
    <image>
      <title>Software Engineer</title>
      <url>https://tistory1.daumcdn.net/tistory/2983016/attach/de381f1a7fbc408183cc2309d781486d</url>
      <link>https://s-engineer.tistory.com</link>
    </image>
    <item>
      <title>[NVR] 하나의 랜 카드에 두 개의 네트워크 IP 지정하기</title>
      <link>https://s-engineer.tistory.com/675</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;IP 카메라, NVR 등을 연결할 때, 네트워크 IP가 달라 원하는 기기에 직접 접근할 수 없는 경우가 있다. IP 주소를 바꿀 수 없는 기기라면 매번 네트워크 IP를 다시 지정하긴 귀찮으므로 하나의 랜 카드에 두 개의 네트워크 IP를 지정해 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;1.png&quot; data-origin-width=&quot;631&quot; data-origin-height=&quot;799&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cRbMtx/dJMcajoeBsI/wIeQ1ubtK0ANh9CWzKZAYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cRbMtx/dJMcajoeBsI/wIeQ1ubtK0ANh9CWzKZAYk/img.png&quot; data-alt=&quot;랜 카드 이더넷 속성 - 인터넷 프로토콜 버전 4(TCP/IPv4) - 속성을 클릭한다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cRbMtx/dJMcajoeBsI/wIeQ1ubtK0ANh9CWzKZAYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcRbMtx%2FdJMcajoeBsI%2FwIeQ1ubtK0ANh9CWzKZAYk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;631&quot; height=&quot;799&quot; data-filename=&quot;1.png&quot; data-origin-width=&quot;631&quot; data-origin-height=&quot;799&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;랜 카드 이더넷 속성 - 인터넷 프로토콜 버전 4(TCP/IPv4) - 속성을 클릭한다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2.png&quot; data-origin-width=&quot;697&quot; data-origin-height=&quot;777&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/B3P8I/dJMcajoeBsH/EKkVUYIKJ5RtTei2foej0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/B3P8I/dJMcajoeBsH/EKkVUYIKJ5RtTei2foej0K/img.png&quot; data-alt=&quot;원하는 고정 IP 주소를 지정한다. DNS 서버는 설정하지 않는다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/B3P8I/dJMcajoeBsH/EKkVUYIKJ5RtTei2foej0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FB3P8I%2FdJMcajoeBsH%2FEKkVUYIKJ5RtTei2foej0K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;697&quot; height=&quot;777&quot; data-filename=&quot;2.png&quot; data-origin-width=&quot;697&quot; data-origin-height=&quot;777&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;원하는 고정 IP 주소를 지정한다. DNS 서버는 설정하지 않는다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 사용하는 네트워크 IP는 192.168.0이다. 호스트 IP는 원하는 주소(100)를 지정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DNS 서버를 설정하지 않았기 때문에 url로 인터넷 사이트에 접속할 수 없게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 '고급(V)...' 버튼을 클릭한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;3.png&quot; data-origin-width=&quot;697&quot; data-origin-height=&quot;832&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZ63S4/dJMcagd4afw/1n8JAHsCgSDjUsio8PSxj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZ63S4/dJMcagd4afw/1n8JAHsCgSDjUsio8PSxj0/img.png&quot; data-alt=&quot;IP 주소에서 추가 버튼을 클릭하고 원하는 네트워크 주소(192.168.1)와 호스트 주소(82)를 지정한다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZ63S4/dJMcagd4afw/1n8JAHsCgSDjUsio8PSxj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZ63S4%2FdJMcagd4afw%2F1n8JAHsCgSDjUsio8PSxj0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;697&quot; height=&quot;832&quot; data-filename=&quot;3.png&quot; data-origin-width=&quot;697&quot; data-origin-height=&quot;832&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;IP 주소에서 추가 버튼을 클릭하고 원하는 네트워크 주소(192.168.1)와 호스트 주소(82)를 지정한다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;4.png&quot; data-origin-width=&quot;1537&quot; data-origin-height=&quot;1068&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/spZPo/dJMcajoeBsJ/Kp3UbvuokGgcZJoyvLctt0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/spZPo/dJMcajoeBsJ/Kp3UbvuokGgcZJoyvLctt0/img.png&quot; data-alt=&quot;이제 다른 네트워크 주소(192.168.1)를 사용하는 NVR(100)에 접속할 수 있다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/spZPo/dJMcajoeBsJ/Kp3UbvuokGgcZJoyvLctt0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FspZPo%2FdJMcajoeBsJ%2FKp3UbvuokGgcZJoyvLctt0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1537&quot; height=&quot;1068&quot; data-filename=&quot;4.png&quot; data-origin-width=&quot;1537&quot; data-origin-height=&quot;1068&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이제 다른 네트워크 주소(192.168.1)를 사용하는 NVR(100)에 접속할 수 있다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1427&quot; data-origin-height=&quot;900&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sZihM/dJMcabcIui0/HU4wKKtL4SI22uiNW6NSs1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sZihM/dJMcabcIui0/HU4wKKtL4SI22uiNW6NSs1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sZihM/dJMcabcIui0/HU4wKKtL4SI22uiNW6NSs1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsZihM%2FdJMcabcIui0%2FHU4wKKtL4SI22uiNW6NSs1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1427&quot; height=&quot;900&quot; data-origin-width=&quot;1427&quot; data-origin-height=&quot;900&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 다른 인터넷 사이트에 접속하기가 어려우므로 가능하다면 NVR의 네트워크 IP(192.168.1)를 내가 사용하는 네트워크 IP(192.168.0)로 바꾸는게 편하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1257&quot; data-origin-height=&quot;1055&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ecESa0/dJMcacW0AsP/66Ld7F0ln2tFqDdhySKVak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ecESa0/dJMcacW0AsP/66Ld7F0ln2tFqDdhySKVak/img.png&quot; data-alt=&quot;NVR 네트워크 설정에서 DHCP를 사용하도록 설정한다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ecESa0/dJMcacW0AsP/66Ld7F0ln2tFqDdhySKVak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FecESa0%2FdJMcacW0AsP%2F66Ld7F0ln2tFqDdhySKVak%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1257&quot; height=&quot;1055&quot; data-origin-width=&quot;1257&quot; data-origin-height=&quot;1055&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;NVR 네트워크 설정에서 DHCP를 사용하도록 설정한다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네트워크 IP가 192.168.0으로 바뀐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1429&quot; data-origin-height=&quot;902&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cOc7oo/dJMcafsFvl8/LOmLqoAtz7ewVFJzFhREo1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cOc7oo/dJMcafsFvl8/LOmLqoAtz7ewVFJzFhREo1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cOc7oo/dJMcafsFvl8/LOmLqoAtz7ewVFJzFhREo1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcOc7oo%2FdJMcafsFvl8%2FLOmLqoAtz7ewVFJzFhREo1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1429&quot; height=&quot;902&quot; data-origin-width=&quot;1429&quot; data-origin-height=&quot;902&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Computer Vision</category>
      <category>Camera</category>
      <category>IP</category>
      <category>Network</category>
      <category>NVR</category>
      <category>네트워크</category>
      <category>랜카드</category>
      <category>아이피</category>
      <category>이더넷</category>
      <category>카메라</category>
      <author>J-sean</author>
      <guid isPermaLink="true">https://s-engineer.tistory.com/675</guid>
      <comments>https://s-engineer.tistory.com/675#entry675comment</comments>
      <pubDate>Sat, 11 Apr 2026 23:07:57 +0900</pubDate>
    </item>
    <item>
      <title>[C#] DateTimePicker DateTime Class</title>
      <link>https://s-engineer.tistory.com/672</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;DateTimePicker로 날짜와 시간을 선택하고 DateTime 클래스를 사용해 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;859&quot; data-origin-height=&quot;542&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEeNox/dJMb99MFXwv/zH0UtpunCK0SlkH1ZHbtl0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEeNox/dJMb99MFXwv/zH0UtpunCK0SlkH1ZHbtl0/img.png&quot; data-alt=&quot;Form에 DateTimePicker, TextBox, Button을 적당히 배치한다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEeNox/dJMb99MFXwv/zH0UtpunCK0SlkH1ZHbtl0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEeNox%2FdJMb99MFXwv%2FzH0UtpunCK0SlkH1ZHbtl0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;859&quot; height=&quot;542&quot; data-origin-width=&quot;859&quot; data-origin-height=&quot;542&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Form에 DateTimePicker, TextBox, Button을 적당히 배치한다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1775536699028&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;using System;
using System.IO;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        string date;
        string time;
        string dateTime;
        string[] mkvFiles;
        DateTime[] mkvFilesInDateTime;

        public Form1()
        {
            InitializeComponent();

            // 현재 날짜와 시간 초기화
            date = DateTime.Now.ToString(&quot;yyyyMMdd&quot;);
            time = DateTime.Now.ToString(&quot;HHmmss&quot;);
            dateTime = date + &quot;-&quot; + time;

            // 이벤트 핸들러 등록
            dateTimePicker1.ValueChanged += dateTimePicker1_ValueChanged;
            dateTimePicker2.ValueChanged += dateTimePicker2_ValueChanged;

            // DateTimePicker1 설정, 이렇게 하면 날짜와 시간 모두 선택할 수 있다.
            //dateTimePicker1.Format = DateTimePickerFormat.Custom;
            //dateTimePicker1.CustomFormat = &quot;yyyy-MM-dd HH:mm:ss&quot;;

            // DateTimePicker2 설정, 시간과 분만 선택할 수 있도록 설정한다.
            dateTimePicker2.Format = DateTimePickerFormat.Custom;
            dateTimePicker2.CustomFormat = &quot;HH:mm&quot;; // 시간과 분만 표시하여 초 선택을 없앰
            dateTimePicker2.ShowUpDown = true; // 시간 선택을 위한 UpDown 컨트롤 표시

            // TextBox 설정
            textBox1.Multiline = true;
            textBox1.Height = textBox1.Font.Height * 6;

            // Button 이벤트 핸들러 등록
            button1.Click += button1_Click;

            // MKV 파일 검색 및 TextBox에 출력
            GetMkvFiles();
        }

        private void dateTimePicker1_ValueChanged(object sender, EventArgs e)
        {
            DateTime selectedDate = dateTimePicker1.Value;

            date = selectedDate.ToString(&quot;yyyyMMdd&quot;);
            dateTime = date + &quot;-&quot; + time;
        }

        private void dateTimePicker2_ValueChanged(object sender, EventArgs e)
        {
            DateTime selectedTime = dateTimePicker2.Value;
            // 초 선택이 없으므로 &quot;HHmm00&quot;으로 초를 항상 00으로 고정
            time = selectedTime.ToString(&quot;HHmm00&quot;);
            dateTime = date + &quot;-&quot; + time;
        }

        private void button1_Click(object sender, EventArgs e)
        {
            // 버튼 클릭 시 dateTime이 비어있지 않으면 작업 수행
            if (!string.IsNullOrEmpty(dateTime))
            {
                DateTime selectedDateTime = DateTime.ParseExact(dateTime, &quot;yyyyMMdd-HHmmss&quot;, null);
                DateTime closestDateTime = DateTime.MinValue;
                string closestFile = string.Empty;

                // mkvFilesInDateTime 배열의 각 요소를 순회하면서 selectedDateTime과 비교하여 이전 시점의 가장 가까운 파일을 찾는다.
                foreach (DateTime fileDateTime in mkvFilesInDateTime)
                {
                    if (fileDateTime &amp;lt;= selectedDateTime)
                    {
                        if (fileDateTime &amp;gt; closestDateTime)
                        {
                            closestDateTime = fileDateTime;
                            closestFile = fileDateTime.ToString(&quot;yyyyMMdd-HHmmss&quot;) + &quot;.mkv&quot;;
                        }
                    }
                }

                if (!string.IsNullOrEmpty(closestFile))
                    MessageBox.Show(&quot;가장 가까운 파일: &quot; + closestFile);
                else
                    MessageBox.Show(&quot;선택한 날짜와 시간보다 이전에 생성된 파일이 없습니다.&quot;);
            }
            else
            {
                MessageBox.Show(&quot;날짜와 시간을 선택해주세요.&quot;);
            }
        }

        private void GetMkvFiles()
        {
            // 검색할 대상 폴더 경로 지정
            string directoryPath = @&quot;.\Video&quot;;
            // 문자열 앞에 @를 붙이면 이스케이프 시퀀스를 무시하고 문자열을 그대로 사용할 수 있다.
            // 1. 이스케이프 시퀀스(\) 무시 (파일 경로에 유용)
            //  보통 C# 문자열 안에서 백슬래시(\)는 이스케이프 문자로 쓰이기 때문에(\n, \t 등), 파일 경로를 적을 때 백슬래시를
            //  두 번씩 적어줘야 한다. 하지만 @를 붙이면 백슬래시를 있는 그대로 인식한다.
            // 2. 여러 줄(Multi-line) 문자열 작성
            //  @를 사용하면 \n이나 Environment.NewLine을 쓰지 않고도 엔터를 쳐서 여러 줄의 문자열을 그대로 작성할 수 있다.

            //  경로가 존재하는지 확인
            if (Directory.Exists(directoryPath))
            {
                // 지정된 폴더에서 .mkv 확장자를 가진 모든 파일의 전체 경로 배열을 가져온다.
                mkvFiles = Directory.GetFiles(directoryPath, &quot;*.mkv&quot;);

                // 파일 목록을 TextBox에 출력
                textBox1.Clear();
                mkvFilesInDateTime = new DateTime[mkvFiles.Length];

                for (int i = 0; i &amp;lt; mkvFiles.Length; i++)
                {
                    // Path.GetFileName(file)를 사용하면 파일 이름(확장자 포함)을 가져올 수 있다.
                    mkvFiles[i] = Path.GetFileNameWithoutExtension(mkvFiles[i]);

                    // 파일 이름에서 날짜와 시간 부분을 추출하여 mkvFilesInDateTime 배열에 저장
                    mkvFilesInDateTime[i] = DateTime.ParseExact(mkvFiles[i], &quot;yyyyMMdd-HHmmss&quot;, null);

                    textBox1.AppendText(mkvFiles[i] + Environment.NewLine);
                }
            }
            else
            {
                MessageBox.Show(&quot;지정된 폴더를 찾을 수 없습니다.&quot;);
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드를 빌드하고 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1118&quot; data-origin-height=&quot;416&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oR50A/dJMcabcE2tm/pki0zwNm099emAqXAup441/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oR50A/dJMcabcE2tm/pki0zwNm099emAqXAup441/img.png&quot; data-alt=&quot;Video 폴더에는 위와 같은 파일이 있다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oR50A/dJMcabcE2tm/pki0zwNm099emAqXAup441/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoR50A%2FdJMcabcE2tm%2Fpki0zwNm099emAqXAup441%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1118&quot; height=&quot;416&quot; data-origin-width=&quot;1118&quot; data-origin-height=&quot;416&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Video 폴더에는 위와 같은 파일이 있다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;842&quot; data-origin-height=&quot;498&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dmAqQ5/dJMcagLQUuv/xukPPpVbzmVYPVfSZFv1n1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dmAqQ5/dJMcagLQUuv/xukPPpVbzmVYPVfSZFv1n1/img.png&quot; data-alt=&quot;적당한 날짜(26/03/27)와 시간(09:36)을 선택하고 버튼을 클릭한다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dmAqQ5/dJMcagLQUuv/xukPPpVbzmVYPVfSZFv1n1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdmAqQ5%2FdJMcagLQUuv%2FxukPPpVbzmVYPVfSZFv1n1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;842&quot; height=&quot;498&quot; data-origin-width=&quot;842&quot; data-origin-height=&quot;498&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;적당한 날짜(26/03/27)와 시간(09:36)을 선택하고 버튼을 클릭한다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;842&quot; data-origin-height=&quot;498&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brLkqF/dJMcajhrzWE/v9ejjswidvx0urTXXFYC91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brLkqF/dJMcajhrzWE/v9ejjswidvx0urTXXFYC91/img.png&quot; data-alt=&quot;26/03/27 09:36보다 작으면서 가장 큰 시간의 파일은 20260327-091500.mkv이다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brLkqF/dJMcajhrzWE/v9ejjswidvx0urTXXFYC91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrLkqF%2FdJMcajhrzWE%2Fv9ejjswidvx0urTXXFYC91%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;842&quot; height=&quot;498&quot; data-origin-width=&quot;842&quot; data-origin-height=&quot;498&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;26/03/27 09:36보다 작으면서 가장 큰 시간의 파일은 20260327-091500.mkv이다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>C#</category>
      <category>c#</category>
      <category>date</category>
      <category>Datetime</category>
      <category>DateTimePicker</category>
      <category>picker</category>
      <category>TIME</category>
      <author>J-sean</author>
      <guid isPermaLink="true">https://s-engineer.tistory.com/672</guid>
      <comments>https://s-engineer.tistory.com/672#entry672comment</comments>
      <pubDate>Tue, 7 Apr 2026 13:43:48 +0900</pubDate>
    </item>
    <item>
      <title>[C#] Asynchronous Threading 비동기 스레딩</title>
      <link>https://s-engineer.tistory.com/667</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;여러 가지 비동기 스레딩을 구현해 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 인수와 리턴값이 없는 스레드다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 3가지 코드는 모두 같은 내용이지만 다른 방식으로 작성되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1774917843653&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    internal class Program
    {
        static void Counter()
        {
            int subCounter = 0;
            for (int i = 0; i &amp;lt; 5; i++)
            {
                Thread.Sleep(1000);
                Console.WriteLine($&quot;Sub Thread counting... {subCounter++}&quot;);
            }
        }

        static void Main(string[] args)
        {
            Task task = new Task(Counter);
            task.Start();
                        
            int mainCounter = 0;
            for (int i = 0; i &amp;lt; 5; i++)
            {
                Thread.Sleep(1000);
                Console.WriteLine($&quot;Main Thread counting... {mainCounter++}&quot;);                
            }

            task.Wait();
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1774918159232&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Action counter = () =&amp;gt;
            {
                int subCounter = 0;
                for (int i = 0; i &amp;lt; 5; i++)
                {
                    Thread.Sleep(1000);
                    Console.WriteLine($&quot;Sub Thread counting... {subCounter++}&quot;);
                }
            };

            Task task = new Task(counter);
            task.Start();
                        
            int mainCounter = 0;
            for (int i = 0; i &amp;lt; 5; i++)
            {
                Thread.Sleep(1000);
                Console.WriteLine($&quot;Main Thread counting... {mainCounter++}&quot;);                
            }

            task.Wait();
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1774918376271&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Task task = Task.Run(() =&amp;gt;
            {
                int subCounter = 0;
                for (int i = 0; i &amp;lt; 5; i++)
                {
                    Thread.Sleep(1000);
                    Console.WriteLine($&quot;Task counting... {subCounter++}&quot;);
                }
            });

            int mainCounter = 0;
            for (int i = 0; i &amp;lt; 5; i++)
            {
                Thread.Sleep(1000);
                Console.WriteLine($&quot;Main Thread counting... {mainCounter++}&quot;);
            }

            task.Wait();
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;734&quot; data-origin-height=&quot;476&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tmVmz/dJMcabRbzPN/ApX1ylLeAA8tAKN6hF3w20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tmVmz/dJMcabRbzPN/ApX1ylLeAA8tAKN6hF3w20/img.png&quot; data-alt=&quot;순서는 약간 달라질 수 있다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tmVmz/dJMcabRbzPN/ApX1ylLeAA8tAKN6hF3w20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtmVmz%2FdJMcabRbzPN%2FApX1ylLeAA8tAKN6hF3w20%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;734&quot; height=&quot;476&quot; data-origin-width=&quot;734&quot; data-origin-height=&quot;476&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;순서는 약간 달라질 수 있다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;center&gt;
&lt;script src=&quot;https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js&quot;&gt;&lt;/script&gt;
&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block; text-align: center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;ca-pub-7506419418365672&quot; data-ad-slot=&quot;5217671153&quot;&gt;&lt;/ins&gt;
&lt;script&gt;     (adsbygoogle = window.adsbygoogle || []).push({});&lt;/script&gt;
&lt;/center&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번엔 인수를 하나 받는 스레드를 만들어 보자. 아래 2가지 코드는 모두 같은 내용이지만 다른 방식으로 작성되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1774919005523&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    internal class Program
    {
        static void Counter(object to)
        {
            int subCounter = 0;
            for (int i = 0; i &amp;lt; (int)to; i++)
            {
                Thread.Sleep(1000);
                Console.WriteLine($&quot;Sub Thread counting... {subCounter++}&quot;);
            }
        }

        static void Main(string[] args)
        {
            Task task = new Task(Counter, 5);
            task.Start();

            int mainCounter = 0;
            for (int i = 0; i &amp;lt; 5; i++)
            {
                Thread.Sleep(1000);
                Console.WriteLine($&quot;Main Thread counting... {mainCounter++}&quot;);
            }

            task.Wait();
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1774919304423&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Action&amp;lt;object&amp;gt; Counter = (object to) =&amp;gt;
            {
                int subCounter = 0;
                for (int i = 0; i &amp;lt; (int)to; i++)
                {
                    Thread.Sleep(1000);
                    Console.WriteLine($&quot;Sub Thread counting... {subCounter++}&quot;);
                }
            };

            Task task = new Task(Counter, 5);
            task.Start();

            int mainCounter = 0;
            for (int i = 0; i &amp;lt; 5; i++)
            {
                Thread.Sleep(1000);
                Console.WriteLine($&quot;Main Thread counting... {mainCounter++}&quot;);
            }

            task.Wait();
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인수가 있는 경우, Task.Run() 메서드에서 람다식으로 코드를 만드는 건 의미가 없는 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과는 위와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;center&gt;
&lt;script src=&quot;https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js&quot;&gt;&lt;/script&gt;
&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block; text-align: center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;ca-pub-7506419418365672&quot; data-ad-slot=&quot;5217671153&quot;&gt;&lt;/ins&gt;
&lt;script&gt;     (adsbygoogle = window.adsbygoogle || []).push({});&lt;/script&gt;
&lt;/center&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번엔 리턴값이 있는 스레드를 만들어 보자. 아래 2가지 코드는 모두 같은 내용이지만 다른 방식으로 작성되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1774920492436&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    internal class Program
    {
        static int Counter()
        {
            int subCounter = 0;
            for (int i = 0; i &amp;lt; 5; i++)
            {
                Thread.Sleep(1000);
                Console.WriteLine($&quot;Sub Thread counting... {subCounter++}&quot;);
            }

            return subCounter;
        }

        static void Main(string[] args)
        {
            Task&amp;lt;int&amp;gt; task = new Task&amp;lt;int&amp;gt;(Counter);
            task.Start();

            int mainCounter = 0;
            for (int i = 0; i &amp;lt; 5; i++)
            {
                Thread.Sleep(1000);
                Console.WriteLine($&quot;Main Thread counting... {mainCounter++}&quot;);
            }

            task.Wait();
            Console.WriteLine($&quot;Sub Thread finished with count: {task.Result}&quot;);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1774920868763&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Task&amp;lt;int&amp;gt; task = Task&amp;lt;int&amp;gt;.Run(() =&amp;gt;
            {
                int subCounter = 0;
                for (int i = 0; i &amp;lt; 5; i++)
                {
                    Thread.Sleep(1000);
                    Console.WriteLine($&quot;Sub Thread counting... {subCounter++}&quot;);
                }

                return subCounter;
            });

            int mainCounter = 0;
            for (int i = 0; i &amp;lt; 5; i++)
            {
                Thread.Sleep(1000);
                Console.WriteLine($&quot;Main Thread counting... {mainCounter++}&quot;);
            }

            task.Wait();
            Console.WriteLine($&quot;Sub Thread finished with count: {task.Result}&quot;);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Action은 리턴값이 없는 메서드 전용이기 때문에 이 경우 Action을 사용할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;692&quot; data-origin-height=&quot;532&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ssCHj/dJMcaibJXMx/ubqQyhxUItiBHsFUBgTXqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ssCHj/dJMcaibJXMx/ubqQyhxUItiBHsFUBgTXqk/img.png&quot; data-alt=&quot;순서는 약간 달라질 수 있다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ssCHj/dJMcaibJXMx/ubqQyhxUItiBHsFUBgTXqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FssCHj%2FdJMcaibJXMx%2FubqQyhxUItiBHsFUBgTXqk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;692&quot; height=&quot;532&quot; data-origin-width=&quot;692&quot; data-origin-height=&quot;532&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;순서는 약간 달라질 수 있다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;center&gt;
&lt;script src=&quot;https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js&quot;&gt;&lt;/script&gt;
&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block; text-align: center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;ca-pub-7506419418365672&quot; data-ad-slot=&quot;5217671153&quot;&gt;&lt;/ins&gt;
&lt;script&gt;     (adsbygoogle = window.adsbygoogle || []).push({});&lt;/script&gt;
&lt;/center&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번엔 인수도 있고 리턴값도 있는 스레드를 만들어 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 2가지 코드는 모두 같은 내용이지만 다른 방식으로 작성되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1774921899261&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    internal class Program
    {
        static int Counter(object to)
        {
            int subCounter = 0;
            for (int i = 0; i &amp;lt; (int)to; i++)
            {
                Thread.Sleep(1000);
                Console.WriteLine($&quot;Sub Thread counting... {subCounter++}&quot;);
            }

            return subCounter;
        }

        static void Main(string[] args)
        {
            Task&amp;lt;int&amp;gt; task = new Task&amp;lt;int&amp;gt;(Counter, 5);
            task.Start();

            int mainCounter = 0;
            for (int i = 0; i &amp;lt; 5; i++)
            {
                Thread.Sleep(1000);
                Console.WriteLine($&quot;Main Thread counting... {mainCounter++}&quot;);
            }

            task.Wait();
            Console.WriteLine($&quot;Sub Thread finished with count: {task.Result}&quot;);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1774922316054&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Func&amp;lt;object, int&amp;gt; Counter = (object to) =&amp;gt;
            {
                int subCounter = 0;
                for (int i = 0; i &amp;lt; (int)to; i++)
                {
                    Thread.Sleep(1000);
                    Console.WriteLine($&quot;Sub Thread counting... {subCounter++}&quot;);
                }

                return subCounter;
            };

            Task&amp;lt;int&amp;gt; task = new Task&amp;lt;int&amp;gt;(Counter, 5);
            task.Start();

            int mainCounter = 0;
            for (int i = 0; i &amp;lt; 5; i++)
            {
                Thread.Sleep(1000);
                Console.WriteLine($&quot;Main Thread counting... {mainCounter++}&quot;);
            }

            task.Wait();
            Console.WriteLine($&quot;Sub Thread finished with count: {task.Result}&quot;);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;692&quot; data-origin-height=&quot;532&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpnE8o/dJMcaf0ljXe/m2iAByBu4mvbxm0dXmptY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpnE8o/dJMcaf0ljXe/m2iAByBu4mvbxm0dXmptY0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpnE8o/dJMcaf0ljXe/m2iAByBu4mvbxm0dXmptY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpnE8o%2FdJMcaf0ljXe%2Fm2iAByBu4mvbxm0dXmptY0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;692&quot; height=&quot;532&quot; data-origin-width=&quot;692&quot; data-origin-height=&quot;532&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;center&gt;
&lt;script src=&quot;https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js&quot;&gt;&lt;/script&gt;
&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block; text-align: center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;ca-pub-7506419418365672&quot; data-ad-slot=&quot;5217671153&quot;&gt;&lt;/ins&gt;
&lt;script&gt;     (adsbygoogle = window.adsbygoogle || []).push({});&lt;/script&gt;
&lt;/center&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;async, await를 사용해 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드는 컴파일 에러가 발생한다. Main 함수에는 async가 단순하게 그냥 붙을 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1775048763443&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    internal class Program
    {
    	// 컴파일 에러 발생
        static async void Main(string[] args)
        {
            await Task.Run(() =&amp;gt; Count());

            int mainCounter = 0;
            for (int i = 0; i &amp;lt; 5; i++)
            {
                Thread.Sleep(1000);
                Console.WriteLine($&quot;Main Thread counting... {mainCounter++}&quot;);
            }
        }

        static void Count()
        {
            int subCounter = 0;
            for (int i = 0; i &amp;lt; 5; i++)
            {
                Thread.Sleep(1000);
                Console.WriteLine($&quot;Sub Thread counting... {subCounter++}&quot;);
            }

        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Main 함수에 단순하게 async가 붙으면 다음과 같은 에러가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;u&gt;Program&lt;/u&gt;&amp;nbsp;does&amp;nbsp;not&amp;nbsp;contain&amp;nbsp;a&amp;nbsp;static&amp;nbsp;'Main'&amp;nbsp;method&amp;nbsp;suitable&amp;nbsp;for&amp;nbsp;an&amp;nbsp;entry&amp;nbsp;point&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또, 아래와 같이 바꾸면 단일 스레드처럼 동작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1775136338639&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    internal class Program
    {
        static async Task Main(string[] args)
        {
            await Task.Run(() =&amp;gt; Count());

            int mainCounter = 0;
            for (int i = 0; i &amp;lt; 5; i++)
            {
                Thread.Sleep(1000);
                Console.WriteLine($&quot;Main Thread counting... {mainCounter++}&quot;);
            }
        }

        static void Count()
        {
            int subCounter = 0;
            for (int i = 0; i &amp;lt; 5; i++)
            {
                Thread.Sleep(1000);
                Console.WriteLine($&quot;Sub Thread counting... {subCounter++}&quot;);
            }

        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;846&quot; data-origin-height=&quot;476&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/evCtGO/dJMcaakuFKq/QcpqCpKToAHf3dhC3RpKrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/evCtGO/dJMcaakuFKq/QcpqCpKToAHf3dhC3RpKrk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/evCtGO/dJMcaakuFKq/QcpqCpKToAHf3dhC3RpKrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FevCtGO%2FdJMcaakuFKq%2FQcpqCpKToAHf3dhC3RpKrk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;846&quot; height=&quot;476&quot; data-origin-width=&quot;846&quot; data-origin-height=&quot;476&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;await 키워드는 이 비동기 작업(서브 스레드)이 완전히 끝날 때까지 여기서 기다리겠다는 의미이기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 바꿔보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1775136707020&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    internal class Program
    {
        static async Task Main(string[] args)
        {
            // await 없이 Task를 실행(시작)만 하고 변수에 담아둔다.
            // 이 시점부터 서브 스레드가 백그라운드에서 동작한다.
            Task countTask = Task.Run(() =&amp;gt; Count());

            // 메인 스레드는 멈추지 않고 자신의 루프 작업을 동시에 수행한다.
            int mainCounter = 0;
            for (int i = 0; i &amp;lt; 5; i++)
            {
                // 참고: 비동기 메서드(async) 내에서는 Thread.Sleep 대신 await Task.Delay를 사용하는 것을 권장.
                await Task.Delay(1000);
                Console.WriteLine($&quot;Main Thread counting... {mainCounter++}&quot;);
            }

            // 메인 스레드의 작업이 다 끝나면, 메인 프로세스가 종료되기 전에
            // 서브 스레드의 작업이 모두 완료되었는지 마지막으로 확인(대기).
            await countTask;
        }

        static void Count()
        {
            int subCounter = 0;
            for (int i = 0; i &amp;lt; 5; i++)
            {
                Thread.Sleep(1000);
                Console.WriteLine($&quot;Sub Thread counting... {subCounter++}&quot;);
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아니면 아래와 같이 다른 함수에서 async, await를 사용하도록 코드를 바꾼다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1775137018363&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Count();

            int mainCounter = 0;
            for (int i = 0; i &amp;lt; 5; i++)
            {
                Thread.Sleep(1000);
                Console.WriteLine($&quot;Main Thread counting... {mainCounter++}&quot;);
            }
        }

        static async void Count()
        {
            await Task.Run(async () =&amp;gt;
            {
                int subCounter = 0;
                for (int i = 0; i &amp;lt; 5; i++)
                {
                    // Use Task.Delay instead of Thread.Sleep to avoid blocking the thread
                    await Task.Delay(1000);
                    Console.WriteLine($&quot;Sub Thread counting... {subCounter++}&quot;);
                }
            });
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;846&quot; data-origin-height=&quot;476&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mgIL4/dJMcahw9BOS/7TPEWPvAxDH0DUt9P32NZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mgIL4/dJMcahw9BOS/7TPEWPvAxDH0DUt9P32NZ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mgIL4/dJMcahw9BOS/7TPEWPvAxDH0DUt9P32NZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmgIL4%2FdJMcahw9BOS%2F7TPEWPvAxDH0DUt9P32NZ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;846&quot; height=&quot;476&quot; data-origin-width=&quot;846&quot; data-origin-height=&quot;476&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;center&gt;
&lt;script src=&quot;https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js&quot;&gt;&lt;/script&gt;
&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block; text-align: center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;ca-pub-7506419418365672&quot; data-ad-slot=&quot;5217671153&quot;&gt;&lt;/ins&gt;
&lt;script&gt;     (adsbygoogle = window.adsbygoogle || []).push({});&lt;/script&gt;
&lt;/center&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번엔 반환값이 있는 함수를 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1775051093133&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Task&amp;lt;int&amp;gt; taskResult = Count();

            int mainCounter = 0;
            for (int i = 0; i &amp;lt; 5; i++)
            {
                Thread.Sleep(1000);
                Console.WriteLine($&quot;Main Thread counting... {mainCounter++}&quot;);
            }

            int result = taskResult.Result;
            Console.WriteLine($&quot;Result from Sub Thread: {result}&quot;);
        }

        static async Task&amp;lt;int&amp;gt; Count()
        {
            return await Task.Run(() =&amp;gt;
            {
                int subCounter = 0;
                for (int i = 0; i &amp;lt; 5; i++)
                {
                    Thread.Sleep(1000);
                    Console.WriteLine($&quot;Sub Thread counting... {subCounter++}&quot;);
                }

                return subCounter;
            });
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;846&quot; data-origin-height=&quot;504&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vlW9M/dJMcagrsdGg/xD876c5sG6DSjFqZCqMHB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vlW9M/dJMcagrsdGg/xD876c5sG6DSjFqZCqMHB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vlW9M/dJMcagrsdGg/xD876c5sG6DSjFqZCqMHB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvlW9M%2FdJMcagrsdGg%2FxD876c5sG6DSjFqZCqMHB1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;846&quot; height=&quot;504&quot; data-origin-width=&quot;846&quot; data-origin-height=&quot;504&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드를 아래와 같이 바꾸면 단일 스레드처럼 작동하게 된다. 조심하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1775051225772&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            int taskResult = Count().Result;
            // Task가 시작되자마자 바로 Result를 요구하고 있다.
            // 메인 스레드가 진행하지 못하게 된다.
            Console.WriteLine($&quot;Result from Sub Thread: {taskResult}&quot;);

            int mainCounter = 0;
            for (int i = 0; i &amp;lt; 5; i++)
            {
                Thread.Sleep(1000);
                Console.WriteLine($&quot;Main Thread counting... {mainCounter++}&quot;);
            }
        }

        static async Task&amp;lt;int&amp;gt; Count()
        {
            return await Task.Run(() =&amp;gt;
            {
                int subCounter = 0;
                for (int i = 0; i &amp;lt; 5; i++)
                {
                    Thread.Sleep(1000);
                    Console.WriteLine($&quot;Sub Thread counting... {subCounter++}&quot;);
                }

                return subCounter;
            });
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;846&quot; data-origin-height=&quot;504&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c4g2ww/dJMcadIeju8/VT5dkgm4S7NczDgbQ2mdl0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c4g2ww/dJMcadIeju8/VT5dkgm4S7NczDgbQ2mdl0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c4g2ww/dJMcadIeju8/VT5dkgm4S7NczDgbQ2mdl0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc4g2ww%2FdJMcadIeju8%2FVT5dkgm4S7NczDgbQ2mdl0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;846&quot; height=&quot;504&quot; data-origin-width=&quot;846&quot; data-origin-height=&quot;504&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;center&gt;
&lt;script src=&quot;https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js&quot;&gt;&lt;/script&gt;
&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block; text-align: center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;ca-pub-7506419418365672&quot; data-ad-slot=&quot;5217671153&quot;&gt;&lt;/ins&gt;
&lt;script&gt;     (adsbygoogle = window.adsbygoogle || []).push({});&lt;/script&gt;
&lt;/center&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Parallel 클래스를 사용해서 간편하게 스레드를 사용해 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1775090428923&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Parallel.For(0, 10, DoSomething);

            // Alternatively, you can use a lambda expression:
            //Parallel.For(0, 10, i =&amp;gt;
            //{
            //    DoSomething(i);
            //});
        }

        static void DoSomething(int i)
        {
            Thread.Sleep(1000);
            Console.WriteLine($&quot;Job {i} completed.&quot;);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;846&quot; data-origin-height=&quot;476&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cYitXB/dJMcafe3YqT/hfiXy6fCUcxveW1HuwEAE0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cYitXB/dJMcafe3YqT/hfiXy6fCUcxveW1HuwEAE0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cYitXB/dJMcafe3YqT/hfiXy6fCUcxveW1HuwEAE0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcYitXB%2FdJMcafe3YqT%2FhfiXy6fCUcxveW1HuwEAE0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;846&quot; height=&quot;476&quot; data-origin-width=&quot;846&quot; data-origin-height=&quot;476&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나씩 처리하면 10초가 걸리는 작업이 10개의 스레드가 생성되어 1초 만에 끝난다. (스레드는 CPU 코어의 갯수에 따라 다르게 생성된다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>C#</category>
      <category>async</category>
      <category>asynchronous</category>
      <category>await</category>
      <category>c#</category>
      <category>multi</category>
      <category>thread</category>
      <category>멀티</category>
      <category>비동기</category>
      <category>스레드</category>
      <author>J-sean</author>
      <guid isPermaLink="true">https://s-engineer.tistory.com/667</guid>
      <comments>https://s-engineer.tistory.com/667#entry667comment</comments>
      <pubDate>Tue, 31 Mar 2026 09:53:01 +0900</pubDate>
    </item>
    <item>
      <title>[C#] 콘솔 프로그램 실행하고 결과 확인하기</title>
      <link>https://s-engineer.tistory.com/663</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;다른 콘솔 프로그램을 실행하고 결과를 확인해 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1774659298234&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;using System;
using System.Diagnostics;
using System.IO;

namespace ConsoleApp1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            // ProcessStartInfo 설정
            ProcessStartInfo startInfo = new ProcessStartInfo();
            //startInfo.FileName = &quot;cmd.exe&quot;; // 실행할 프로그램 (예: cmd)
            //startInfo.Arguments = &quot;/c dir&quot;; // 프로그램 인자 (예: 현재 디렉토리 파일 목록)
            startInfo.FileName = &quot;cmd.exe&quot;;
            startInfo.Arguments = &quot;/c dir&quot;;
            startInfo.UseShellExecute = false; // 쉘 사용 안 함 (리다이렉션 필수 설정)
            startInfo.RedirectStandardOutput = true; // 표준 출력 리다이렉션
            startInfo.CreateNoWindow = true; // 콘솔 창 띄우지 않음

            // 프로세스 실행
            using (Process process = Process.Start(startInfo))
            {
                // 3. 출력 내용 읽기
                using (StreamReader reader = process.StandardOutput)
                {
                    // 전체 결과 가져오기. 너무 길면 메모리 문제 발생 가능
                    //string result = reader.ReadToEnd();
                    //Console.WriteLine(&quot;--- 외부 프로그램 결과 ---&quot;);
                    //Console.WriteLine(result);
                    //Console.WriteLine(&quot;--------------------------&quot;);

                    // 한 줄씩 읽기
                    string line;
                    Console.WriteLine(&quot;--- 외부 프로그램 결과 ---&quot;);
                    while ((line = reader.ReadLine()) != null)
                    {
                        Console.WriteLine(line);
                        //Console.Write(line + Environment.NewLine);
                    }
                    Console.WriteLine(&quot;--------------------------&quot;);
                }
                process.WaitForExit(); // 프로그램 종료 대기
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1406&quot; data-origin-height=&quot;560&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4Do0n/dJMcaco0LE2/1BTmfmZpiKRqdPmWwHOj01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4Do0n/dJMcaco0LE2/1BTmfmZpiKRqdPmWwHOj01/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4Do0n/dJMcaco0LE2/1BTmfmZpiKRqdPmWwHOj01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4Do0n%2FdJMcaco0LE2%2F1BTmfmZpiKRqdPmWwHOj01%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1406&quot; height=&quot;560&quot; data-origin-width=&quot;1406&quot; data-origin-height=&quot;560&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://s-engineer.tistory.com/570&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;2025.04.14 - [C, C++] - 명령창(cmd)을 열지 않고 명령 실행하고 결과 받아오기 popen(pipe open), pclose(pipe close)&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>C#</category>
      <category>c#</category>
      <category>Console</category>
      <category>결과</category>
      <category>실행</category>
      <category>콘솔</category>
      <author>J-sean</author>
      <guid isPermaLink="true">https://s-engineer.tistory.com/663</guid>
      <comments>https://s-engineer.tistory.com/663#entry663comment</comments>
      <pubDate>Sat, 28 Mar 2026 09:56:46 +0900</pubDate>
    </item>
    <item>
      <title>[OpenCV] 다수의 영상 파일 재생</title>
      <link>https://s-engineer.tistory.com/662</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;1개 이상의 동영상 파일을 로드하고 재생해 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드는 각각의 영상을 독립적으로 처리하기 때문에 시간 표시도 개별적으로 되고 앞뒤 이동도 제한된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1774576651409&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;vector&amp;gt;
#include &amp;lt;filesystem&amp;gt;
#include &amp;lt;opencv2/opencv.hpp&amp;gt;

int main()
{
	std::vector&amp;lt;std::string&amp;gt; videoFiles;
	std::string folderPath = &quot;./&quot;;

	for (const std::filesystem::directory_entry&amp;amp; entry : std::filesystem::directory_iterator(folderPath))
	{
		if (entry.is_regular_file() &amp;amp;&amp;amp; entry.path().extension() == &quot;.mkv&quot;)
		{
			videoFiles.push_back(entry.path().string());
		}
	}

	std::cout &amp;lt;&amp;lt; &quot;Found &quot; &amp;lt;&amp;lt; videoFiles.size() &amp;lt;&amp;lt; &quot; .mkv files in the folder:&quot; &amp;lt;&amp;lt; std::endl;
	for (const std::string&amp;amp; file : videoFiles)
	{
		std::cout &amp;lt;&amp;lt; file &amp;lt;&amp;lt; std::endl;
	}

	std::vector&amp;lt;cv::VideoCapture&amp;gt; videoCaptures;
	for (const std::string&amp;amp; file : videoFiles)
	{
		cv::VideoCapture cap(file);
		if (!cap.isOpened())
		{
			std::cerr &amp;lt;&amp;lt; &quot;Error opening video file: &quot; &amp;lt;&amp;lt; file &amp;lt;&amp;lt; std::endl;
			continue;
		}
		videoCaptures.push_back(std::move(cap)); // Move the VideoCapture object into the vector
	}

	std::cout &amp;lt;&amp;lt; &quot;Successfully opened &quot; &amp;lt;&amp;lt; videoCaptures.size() &amp;lt;&amp;lt; &quot; video files.&quot; &amp;lt;&amp;lt; std::endl;

	cv::Mat frame;
	double timestamp = 0.0;
	bool quit = false;
	std::string time;
	int key = 0;

	for (size_t i = 0; i &amp;lt; videoCaptures.size(); ++i)
	{
		std::cout &amp;lt;&amp;lt; &quot;Playing video: &quot; &amp;lt;&amp;lt; videoFiles[i] &amp;lt;&amp;lt; std::endl;
		videoCaptures[i] &amp;gt;&amp;gt; frame;

		while (!frame.empty())
		{
			timestamp = videoCaptures[i].get(cv::CAP_PROP_POS_MSEC); // Get the current timestamp
			time = std::to_string(timestamp / 1000.0);
			time = time.substr(0, time.find(&quot;.&quot;) + 3); // Keep only 2 decimal places

			cv::putText(frame, time, cv::Point(50, 50), cv::FONT_HERSHEY_SIMPLEX, 1.0, cv::Scalar(0, 255, 0), 2);
			cv::imshow(&quot;Video&quot;, frame);

			key = cv::waitKey(33);
			if (key == 27)
			{
				quit = true;
				break;
			}
			else if (key == 'f')
				// 5초 앞으로
				videoCaptures[i].set(cv::CAP_PROP_POS_MSEC, timestamp + 5.0 * 1000.0);
			else if (key == 'b')
				 // 5초 뒤로
				videoCaptures[i].set(cv::CAP_PROP_POS_MSEC, timestamp - 5.0 * 1000.0);

			videoCaptures[i] &amp;gt;&amp;gt; frame;
		}

		if (quit)
			break;
	}

	for (cv::VideoCapture&amp;amp; cap : videoCaptures)
		cap.release();

	cv::destroyAllWindows();

	return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;f: 5초 앞으로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;b: 5초 뒤로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;center&gt;
&lt;script src=&quot;https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js&quot;&gt;&lt;/script&gt;
&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block; text-align: center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;ca-pub-7506419418365672&quot; data-ad-slot=&quot;5217671153&quot;&gt;&lt;/ins&gt;
&lt;script&gt;     (adsbygoogle = window.adsbygoogle || []).push({});&lt;/script&gt;
&lt;/center&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드는 각각의 영상을 하나의 영상인 것처럼 시간을 표시하고 5초 앞뒤로 이동할 때도 각 영상을 자연스럽게 이동한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1774628420618&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;vector&amp;gt;
#include &amp;lt;filesystem&amp;gt;
#include &amp;lt;opencv2/opencv.hpp&amp;gt;

int main()
{
	std::vector&amp;lt;std::string&amp;gt; videoFiles;
	std::string folderPath = &quot;./&quot;;

	for (const std::filesystem::directory_entry&amp;amp; entry : std::filesystem::directory_iterator(folderPath))
	{
		if (entry.is_regular_file() &amp;amp;&amp;amp; entry.path().extension() == &quot;.mkv&quot;)
		{
			videoFiles.push_back(entry.path().string());
		}
	}

	std::cout &amp;lt;&amp;lt; &quot;Found &quot; &amp;lt;&amp;lt; videoFiles.size() &amp;lt;&amp;lt; &quot; .mkv files in the folder:&quot; &amp;lt;&amp;lt; std::endl;
	for (const std::string&amp;amp; file : videoFiles)
	{
		std::cout &amp;lt;&amp;lt; file &amp;lt;&amp;lt; std::endl;
	}

	std::vector&amp;lt;cv::VideoCapture&amp;gt; videoCaptures;
	for (const std::string&amp;amp; file : videoFiles)
	{
		cv::VideoCapture cap(file);
		if (!cap.isOpened())
		{
			std::cerr &amp;lt;&amp;lt; &quot;Error opening video file: &quot; &amp;lt;&amp;lt; file &amp;lt;&amp;lt; std::endl;
			continue;
		}
		videoCaptures.push_back(std::move(cap)); // Move the VideoCapture object into the vector
	}

	std::cout &amp;lt;&amp;lt; &quot;Successfully opened &quot; &amp;lt;&amp;lt; videoCaptures.size() &amp;lt;&amp;lt; &quot; video files.&quot; &amp;lt;&amp;lt; std::endl;

	cv::Mat frame;
	double timestamp = 0.0;
	bool quit = false;
	std::string time;
	double duration = 0.0;
	double move = 5000.0; // 5 seconds in milliseconds
	int key = 0;

	for (size_t i = 0; i &amp;lt; videoCaptures.size(); ++i)
	{
		std::cout &amp;lt;&amp;lt; &quot;Playing video: &quot; &amp;lt;&amp;lt; videoFiles[i] &amp;lt;&amp;lt; std::endl;
		videoCaptures[i] &amp;gt;&amp;gt; frame;

		duration = 0.0;
		for (int j = 0; j &amp;lt; i; j++) {
			duration += (videoCaptures[j].get(cv::CAP_PROP_FRAME_COUNT) / videoCaptures[j].get(cv::CAP_PROP_FPS));
		}
		duration *= 1000.0; // Convert to milliseconds

		while (!frame.empty())
		{
			timestamp = duration + videoCaptures[i].get(cv::CAP_PROP_POS_MSEC); // Get the current timestamp
			time = std::to_string(timestamp / 1000.0);
			time = time.substr(0, time.find(&quot;.&quot;) + 3); // Keep only 2 decimal places

			cv::putText(frame, time, cv::Point(50, 50), cv::FONT_HERSHEY_SIMPLEX, 1.0, cv::Scalar(0, 255, 0), 2);
			cv::imshow(&quot;Video&quot;, frame);

			key = cv::waitKey(33);
			if (key == 27)
			{
				quit = true;
				break;
			}
			else if (key == 'f')
			{
				// 현재 영상에서 5초 앞으로 가는 것이 가능한 경우, 5초 앞으로 이동
				if (videoCaptures[i].get(cv::CAP_PROP_POS_MSEC) + move &amp;lt; videoCaptures[i].get(cv::CAP_PROP_FRAME_COUNT) / videoCaptures[i].get(cv::CAP_PROP_FPS) * 1000.0)
					videoCaptures[i].set(cv::CAP_PROP_POS_MSEC, videoCaptures[i].get(cv::CAP_PROP_POS_MSEC) + move);
				// 현재 영상에서 5초 앞으로 가는 것이 불가능한 경우, 가능한 만큼만 이동하고 다음 영상으로 이동해서 남은 시간만큼 앞으로 이동
				else if (i &amp;lt; videoCaptures.size() - 1)
				{
					videoCaptures[i + 1].set(cv::CAP_PROP_POS_MSEC, move - (videoCaptures[i].get(cv::CAP_PROP_FRAME_COUNT) / videoCaptures[i].get(cv::CAP_PROP_FPS) * 1000.0 - videoCaptures[i].get(cv::CAP_PROP_POS_MSEC)));

					// 현재 영상(i)을 나중에 다시 재생할 때 처음부터 재생될 수 있도록 위치를 0으로 초기화하는건 불필요.
					//videoCaptures[i].set(cv::CAP_PROP_POS_MSEC, 0.0);

					break;
				}
				else
				{
					//videoCaptures[i].set(cv::CAP_PROP_POS_MSEC, videoCaptures[i].get(cv::CAP_PROP_FRAME_COUNT) / videoCaptures[i].get(cv::CAP_PROP_FPS) * 1000.0);
				}

			}
			else if (key == 'b')
			{
				// 현재 영상에서 5초 뒤로 가는 것이 가능한 경우, 5초 뒤로 이동
				if (videoCaptures[i].get(cv::CAP_PROP_POS_MSEC) &amp;gt; move)
					videoCaptures[i].set(cv::CAP_PROP_POS_MSEC, videoCaptures[i].get(cv::CAP_PROP_POS_MSEC) - move);
				// 현재 영상에서 5초 뒤로 가는 것이 불가능한 경우, 가능한 만큼만 이동하고 이전 영상으로 이동해서 남은 시간만큼 뒤로 이동
				else if (i &amp;gt; 0)
				{
					videoCaptures[i - 1].set(cv::CAP_PROP_POS_MSEC, videoCaptures[i - 1].get(cv::CAP_PROP_FRAME_COUNT) / videoCaptures[i - 1].get(cv::CAP_PROP_FPS) * 1000.0 - (move - videoCaptures[i].get(cv::CAP_PROP_POS_MSEC)));

					// 현재 영상(i)을 곧(5초 이내) 다시 재생할 때 처음부터 재생될 수 있도록 위치를 0으로 초기화.
					videoCaptures[i].set(cv::CAP_PROP_POS_MSEC, 0.0);

					i -= 2; // 다음 루프에서 i++ 되므로 -2

					break;
				}
				else
				{
					videoCaptures[i].set(cv::CAP_PROP_POS_MSEC, 0.0);
				}
			}

			videoCaptures[i] &amp;gt;&amp;gt; frame;
		}

		if (quit)
			break;
	}

	for (cv::VideoCapture&amp;amp; cap : videoCaptures)
		cap.release();

	cv::destroyAllWindows();

	return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Computer Vision</category>
      <category>opencv</category>
      <category>Video</category>
      <author>J-sean</author>
      <guid isPermaLink="true">https://s-engineer.tistory.com/662</guid>
      <comments>https://s-engineer.tistory.com/662#entry662comment</comments>
      <pubDate>Fri, 27 Mar 2026 11:08:00 +0900</pubDate>
    </item>
    <item>
      <title>[OpenCV] Polygon Mask 폴리곤 마스크 3</title>
      <link>https://s-engineer.tistory.com/658</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;마우스로 마스크 포인트의 삽입, 삭제, 이동을 구현해 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1773902202363&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;opencv2/opencv.hpp&amp;gt;

cv::Point nearPointOnSegment(cv::Point2f p, cv::Point2f a, cv::Point2f b) {
	float l2 = std::powf(a.x - b.x, 2) + std::powf(a.y - b.y, 2); // 선분의 길이 제곱
	if (l2 == 0.0f) // a와 b가 같은 경우
		return cv::Point();

	// 선분 위로 점 p의 사영(Projection) 위치 t 계산(투영된 지점의 비율)
	// t = [(p-a) dot (b-a)] / |b-a|^2
	float t = ((p.x - a.x) * (b.x - a.x) + (p.y - a.y) * (b.y - a.y)) / l2;

	// t가 0~1 사이면 선분 위(0이면 a, 1이면 b), 그렇지 않으면 선분 밖
	// 0보다 작으면 a쪽, 1보다 크면 b쪽
	// 선분 밖에 사영된 점은 무시
	if (t &amp;lt; 0.0f || t &amp;gt; 1.0f)
		return cv::Point();

	// 선분 위에 사영된 점 위치 계산
	cv::Point2f projection = a + t * (b - a);
	//std::cout &amp;lt;&amp;lt; &quot;Projection Point: (&quot; &amp;lt;&amp;lt; projection.x &amp;lt;&amp;lt; &quot;, &quot; &amp;lt;&amp;lt; projection.y &amp;lt;&amp;lt; &quot;)&quot; &amp;lt;&amp;lt; std::endl;

	// 사영된 점과 원래 점 p 사이의 거리가 10 픽셀 이상이면 무시
	if (std::sqrt(std::powf(p.x - projection.x, 2) + std::powf(p.y - projection.y, 2)) &amp;gt; 10)
		return cv::Point();

	// 선분 위의 사영된 점 반환
	return projection;
}

void onMouse(int event, int x, int y, int flags, void* userdata)
{
	static std::vector&amp;lt;cv::Point&amp;gt; points; // Static vector to store points across function calls
	cv::Scalar pointColor(0, 255, 0); // Green color for drawing points
	cv::Scalar lineColor(0, 0, 255); // Red color for drawing lines
	static cv::Mat* img = (cv::Mat*)userdata; // Cast the user data to a pointer to cv::Mat

	// 이미지 복사본 생성
	static cv::Mat originalimg = cv::imread(&quot;palvin1.png&quot;);

	// 마스크 생성
	static cv::Mat mask1 = cv::Mat::zeros((*img).size(), CV_8UC1);
	static cv::Mat mask2 = cv::Mat::zeros((*img).size(), CV_8UC1);
	static cv::Mat mask = cv::Mat::zeros((*img).size(), CV_8UC1);

	cv::Point newPoint(x, y);

	static bool clickedOnExistingPoint = false; // Flag to track if the click was on an existing point
	static int clickedExistingPointIndex = -1; // Index of the existing point that was clicked
	static bool clickHold = false;

	bool inserted = false;

	switch (event)
	{
	case cv::EVENT_LBUTTONDOWN:
		// 기존 점과 동일한 위치에 클릭된 경우 (범위는 8x8 픽셀)
		if (!points.empty())
			for (int i = 0; i &amp;lt; points.size(); i++)
				if (newPoint.x - 4 &amp;lt; points[i].x &amp;amp;&amp;amp; newPoint.x + 4 &amp;gt; points[i].x &amp;amp;&amp;amp;
					newPoint.y - 4 &amp;lt; points[i].y &amp;amp;&amp;amp; newPoint.y + 4 &amp;gt; points[i].y)
				{
					// 이미 존재하는 점을 클릭하면 우클릭으로 제거할 수 있도록 인덱스 저장
					clickedExistingPointIndex = i;
					clickedOnExistingPoint = true; // Set the flag to indicate that an existing point was clicked

					// 이동하려는 목적일 수 있으므로, 클릭이 유지되는 동안 점을 이동할 수 있도록 clickHold 플래그 설정
					clickHold = true;

					std::cout &amp;lt;&amp;lt; &quot;Clicked on existing point: &quot; &amp;lt;&amp;lt; i &amp;lt;&amp;lt; &quot; (&quot; &amp;lt;&amp;lt; points[i].x &amp;lt;&amp;lt; &quot;, &quot;
						&amp;lt;&amp;lt; points[i].y &amp;lt;&amp;lt; &quot;)&quot; &amp;lt;&amp;lt; std::endl;

					return;
				}


		// 마우스 왼쪽 버튼이 클릭되었을 때, 새로운 점을 추가하거나 선분 사이에 점을 삽입

		// 선분 사이에 점 추가
		// 최소 2개의 점이 있어야 선분이 형성되므로, 2개 이상의 점이 있을 때만 선분 사이에 점을 추가
		if (points.size() &amp;gt;= 2)
		{
			for (int i = 0; i &amp;lt; points.size() - 1; i++)
			{
				cv::Point2f a = points[i];
				cv::Point2f b = points[i + 1];
				cv::Point projection = nearPointOnSegment(newPoint, a, b);
				if (projection == cv::Point()) // 사영된 점이 선분 밖이거나 너무 멀리 떨어져 있으면
					continue;
				else
				{
					points.insert(points.begin() + 1 + i, projection); // 새로운 점을 선분 사이에 삽입
					std::cout &amp;lt;&amp;lt; &quot;Point inserted: (&quot; &amp;lt;&amp;lt; projection.x &amp;lt;&amp;lt; &quot;, &quot; &amp;lt;&amp;lt; projection.y &amp;lt;&amp;lt; &quot;)&quot; &amp;lt;&amp;lt; std::endl;

					originalimg.copyTo(*img); // 이미지 갱신
					inserted = true;

					break;
				}
			}
		}

		// 선분 사이에 점이 추가되지 않았다면, 새로운 점을 추가
		if (!inserted &amp;amp;&amp;amp; !points.empty() &amp;amp;&amp;amp; newPoint != points.back())
		{
			points.push_back(newPoint);
			std::cout &amp;lt;&amp;lt; &quot;Point added: (&quot; &amp;lt;&amp;lt; x &amp;lt;&amp;lt; &quot;, &quot; &amp;lt;&amp;lt; y &amp;lt;&amp;lt; &quot;)&quot; &amp;lt;&amp;lt; std::endl;

			// 이전에 클릭된 점에 대한 정보를 리셋, 삭제시 전에 클릭된 점이 삭제되지 않도록 한다
			// 이 부분이 없으면, 이미 존재하던 점을 클릭한 후 다른 곳을 클릭해 새로운 점을 생성하고 우클릭으로 삭제하면,
			// 새로운 점이 삭제되지 않고 이전에 클릭된 점이 삭제되는 문제가 발생한다
			clickedExistingPointIndex = -1;
			clickedOnExistingPoint = false;
		}
		// 첫 번째 점 추가
		else if (points.empty())
		{
			points.push_back(newPoint);
			std::cout &amp;lt;&amp;lt; &quot;First point added: (&quot; &amp;lt;&amp;lt; x &amp;lt;&amp;lt; &quot;, &quot; &amp;lt;&amp;lt; y &amp;lt;&amp;lt; &quot;)&quot; &amp;lt;&amp;lt; std::endl;
		}

		break;

	case cv::EVENT_LBUTTONUP:
		// 클릭이 끝났으므로, 클릭이 유지되는 동안 점을 이동할 수 있도록 설정한 clickHold 플래그를 false로 리셋
		clickHold = false;

		break;

	case cv::EVENT_MOUSEMOVE:
		// 클릭이 유지되는 동안 점을 이동할 수 있도록 설정한 clickHold 플래그가 true인 경우
		if (clickHold &amp;amp;&amp;amp; clickedOnExistingPoint &amp;amp;&amp;amp; clickedExistingPointIndex != -1)
		{
			points[clickedExistingPointIndex] = newPoint; // Move the existing point to the new location
			//std::cout &amp;lt;&amp;lt; &quot;Moving point: &quot; &amp;lt;&amp;lt; clickedExistingPointIndex &amp;lt;&amp;lt; &quot; (&quot; &amp;lt;&amp;lt; x &amp;lt;&amp;lt; &quot;, &quot; &amp;lt;&amp;lt; y &amp;lt;&amp;lt; &quot;)&quot; &amp;lt;&amp;lt; std::endl;

			originalimg.copyTo(*img, mask); // 마스크를 적용하여 원본 이미지에서 점과 선이 있는 부분만 갱신
			// *img = cv::imread(&quot;palvin1.png&quot;);
			// 이런 식으로 매번 이미지를 다시 로드하는게 오래 걸려서 점 이동시 버벅이는거 같아 마스크를 적용해
			// 원본 이미지에서 복사해 봤지만 별 효과는 없다.
			// cv::imshow()가 이런 작업에 최적화되어 있지 않은 것 같기도 하다. 점 이동시 버벅이는 문제는 해결되지 않았다.
		}
		break;

	case cv::EVENT_RBUTTONDOWN:
		if (!points.empty())
		{
			// 점이 삽입된 상태에서 우클릭이 발생하면 삽입된 점을 제거, 또는 기존 점을 클릭한 상태에서 우클릭이 발생하면
			// 클릭된 점을 제거
			if (clickedOnExistingPoint)
			{
				std::cout &amp;lt;&amp;lt; &quot;Clicked point removed: &quot; &amp;lt;&amp;lt; clickedExistingPointIndex &amp;lt;&amp;lt; &quot; (&quot; &amp;lt;&amp;lt;
					points[clickedExistingPointIndex].x &amp;lt;&amp;lt; &quot;, &quot; &amp;lt;&amp;lt; points[clickedExistingPointIndex].y &amp;lt;&amp;lt; &quot;)&quot; &amp;lt;&amp;lt;std::endl;
				points.erase(points.begin() + clickedExistingPointIndex); // Remove the last added point
				clickedExistingPointIndex = -1; // Reset the last added point index
				clickedOnExistingPoint = false; // Reset the added flag
			}
			// 아니면 마지막으로 추가된 점을 제거
			else
			{
				points.pop_back();
				std::cout &amp;lt;&amp;lt; &quot;Last point removed&quot; &amp;lt;&amp;lt; std::endl;
			}

			originalimg.copyTo(*img); // 이미지 갱신
		}
		break;
	}

	// Draw points
	for (const cv::Point&amp;amp; point : points)
		cv::circle(*(cv::Mat*)userdata, point, 4, pointColor, -1); // userdata로도 접근 가능

	// Draw lines between points
	if (points.size() &amp;gt;= 2)
		for (int i = 0; i &amp;lt; points.size() - 1; i++)
			cv::line(*img, points[i], points[i + 1], lineColor, 2);

	// 빨간색 점과 초록색 선분이 있는 부분을 흰색으로, 나머지 부분을 검은색으로 하는 마스크 생성
	cv::inRange(*img, cv::Scalar(0, 0, 255), cv::Scalar(0, 0, 255), mask1);
	cv::inRange(*img, cv::Scalar(0, 255, 0), cv::Scalar(0, 255, 0), mask2);
	mask = mask1 + mask2;

	//cv::imshow(&quot;mask&quot;, mask);
	//cv::imshow(&quot;originalimg&quot;, originalimg);
	cv::imshow(&quot;image&quot;, *(cv::Mat*)userdata);
}

int main()
{
	cv::Mat image = cv::imread(&quot;palvin1.png&quot;);
	cv::namedWindow(&quot;image&quot;);
	cv::setMouseCallback(&quot;image&quot;, onMouse, (void*)&amp;amp;image);

	cv::imshow(&quot;image&quot;, image);

	cv::waitKey(0);
	cv::destroyAllWindows();

	return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Computer Vision</category>
      <category>mask</category>
      <category>opencv</category>
      <category>plolygon</category>
      <category>마스크</category>
      <category>폴리곤</category>
      <author>J-sean</author>
      <guid isPermaLink="true">https://s-engineer.tistory.com/658</guid>
      <comments>https://s-engineer.tistory.com/658#entry658comment</comments>
      <pubDate>Thu, 19 Mar 2026 15:37:40 +0900</pubDate>
    </item>
    <item>
      <title>[OpenCV] Projection 선분 위 수선의 발</title>
      <link>https://s-engineer.tistory.com/657</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;두 점으로 이루어지는 선분과 한 점 사이의 거리 및 수선의 발을 찾아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1773882976334&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;opencv2/opencv.hpp&amp;gt;
#include &amp;lt;iostream&amp;gt;

float distPointToSegment(cv::Point2f p, cv::Point2f a, cv::Point2f b) {
	float l2 = std::powf(a.x - b.x, 2) + std::powf(a.y - b.y, 2); // 선분의 길이 제곱
	if (l2 == 0.0f) // a와 b가 같은 경우
		return std::sqrt(std::powf(p.x - a.x, 2) + std::powf(p.y - a.y, 2)); // 점과 a 사이 거리

	// 선분 위로 점 p의 사영(Projection) 위치 t 계산(투영된 지점의 비율)
	// t = [(p-a) dot (b-a)] / |b-a|^2
	float t = ((p.x - a.x) * (b.x - a.x) + (p.y - a.y) * (b.y - a.y)) / l2;

	// t가 0~1 사이면 선분 위(0이면 a, 1이면 b), 그렇지 않으면 선분 밖
	// 0보다 작으면 a쪽, 1보다 크면 b쪽
	if (t &amp;lt; 0.0f)
		return std::sqrt(std::powf(p.x - a.x, 2) + std::powf(p.y - a.y, 2));
	if (t &amp;gt; 1.0f)
		return std::sqrt(std::powf(p.x - b.x, 2) + std::powf(p.y - b.y, 2));

	// 선분 위에 사영된 점 위치 계산
	cv::Point2f projection = a + t * (b - a);
	std::cout &amp;lt;&amp;lt; &quot;Projection Point: (&quot; &amp;lt;&amp;lt; projection.x &amp;lt;&amp;lt; &quot;, &quot; &amp;lt;&amp;lt; projection.y &amp;lt;&amp;lt; &quot;)&quot; &amp;lt;&amp;lt; std::endl;

	// 점 p와 사영된 점 사이의 거리 계산
	return std::sqrt(std::powf(p.x - projection.x, 2) + std::powf(p.y - projection.y, 2));
}

int main() {
	cv::Point2f p(100, 0); // 점 p
	cv::Point2f a(0, 0);   // 선분 시작점 a
	cv::Point2f b(100, 100); // 선분 끝점 b

	float distance = distPointToSegment(p, a, b);
	std::cout &amp;lt;&amp;lt; &quot;Point to Segment Distance: &quot; &amp;lt;&amp;lt; distance &amp;lt;&amp;lt; std::endl;

	return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1098&quot; data-origin-height=&quot;280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ccBd5l/dJMcagrgOKT/HmnqUOe5NrnrHUfGkpHeY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ccBd5l/dJMcagrgOKT/HmnqUOe5NrnrHUfGkpHeY0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ccBd5l/dJMcagrgOKT/HmnqUOe5NrnrHUfGkpHeY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FccBd5l%2FdJMcagrgOKT%2FHmnqUOe5NrnrHUfGkpHeY0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1098&quot; height=&quot;280&quot; data-origin-width=&quot;1098&quot; data-origin-height=&quot;280&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Computer Vision</category>
      <category>opencv</category>
      <category>Projection</category>
      <category>사영</category>
      <category>선</category>
      <category>선분</category>
      <category>점</category>
      <category>직선</category>
      <author>J-sean</author>
      <guid isPermaLink="true">https://s-engineer.tistory.com/657</guid>
      <comments>https://s-engineer.tistory.com/657#entry657comment</comments>
      <pubDate>Thu, 19 Mar 2026 10:18:15 +0900</pubDate>
    </item>
    <item>
      <title>[OpenCV] Pixels on the line 라인 위의 모든 점 찾기</title>
      <link>https://s-engineer.tistory.com/656</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;라인 위의 모든 점의 좌표를 찾아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1773848639759&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;opencv2/opencv.hpp&amp;gt;

int main() {
	cv::Mat img = cv::Mat::zeros(480, 640, CV_8UC3);
	cv::Point p1(100, 100);
	cv::Point p2(500, 300);

	// LineIterator 생성 (이미지, 시작점, 끝점, 8-connectivity)
	cv::LineIterator it(img, p1, p2, 8);
	// connectivity: Pixel connectivity of the iterator.
	// Valid values are 4 (iterator can move up, down, left and right)
	// and 8 (iterator can also move diagonally).
	//
	// The number of pixels along the line is stored in LineIterator::count.
	// The method LineIterator::pos returns the current position in the image

	std::vector&amp;lt;cv::Point&amp;gt; linePoints;
	for (int i = 0; i &amp;lt; it.count; i++, ++it) {
		// 현재 위치의 좌표를 벡터에 저장
		linePoints.push_back(it.pos());

		// 선 그리기
		img.at&amp;lt;cv::Vec3b&amp;gt;(it.pos()) = cv::Vec3b(0, 255, 0); // 초록색
	}

	// cv::norm 함수는 벡터의 크기를 계산하는 함수로, 두 점 사이의 거리를 계산할 때 사용할 수 있다.
	//double distance = cv::norm(p2 - p1);
	// 또는 cv::sqrt 함수를 사용하여 직접 계산할 수도 있다.
	cv::Point2f diff = p2 - p1;
	double distance = cv::sqrt(diff.x * diff.x + diff.y * diff.y);
	std::cout &amp;lt;&amp;lt; &quot;p1과 p2 사이의 거리: &quot; &amp;lt;&amp;lt; distance &amp;lt;&amp;lt; std::endl;

	std::cout &amp;lt;&amp;lt; &quot;라인 위의 포인트 개수: &quot; &amp;lt;&amp;lt; linePoints.size() &amp;lt;&amp;lt; std::endl;
	std::cout &amp;lt;&amp;lt; linePoints &amp;lt;&amp;lt; std::endl;

	cv::imshow(&quot;Line&quot;, img);
	cv::waitKey(0);

	return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;924&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OlM9S/dJMcaibzMN2/0Mkx5OhVtGpETLRNZtXqkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OlM9S/dJMcaibzMN2/0Mkx5OhVtGpETLRNZtXqkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OlM9S/dJMcaibzMN2/0Mkx5OhVtGpETLRNZtXqkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOlM9S%2FdJMcaibzMN2%2F0Mkx5OhVtGpETLRNZtXqkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1000&quot; height=&quot;924&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;924&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;962&quot; data-origin-height=&quot;768&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVXOAM/dJMcacvFok3/IP1xt8Tacd5fgwJ68wdrmK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVXOAM/dJMcacvFok3/IP1xt8Tacd5fgwJ68wdrmK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVXOAM/dJMcacvFok3/IP1xt8Tacd5fgwJ68wdrmK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVXOAM%2FdJMcacvFok3%2FIP1xt8Tacd5fgwJ68wdrmK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;962&quot; height=&quot;768&quot; data-origin-width=&quot;962&quot; data-origin-height=&quot;768&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ 참고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;a style=&quot;color: #006dd7;&quot; href=&quot;https://docs.opencv.org/4.13.0/dc/dd2/classcv_1_1LineIterator.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;LineIterator Class&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Computer Vision</category>
      <category>Coordinate</category>
      <category>LiNE</category>
      <category>opencv</category>
      <category>Pixel</category>
      <category>라인</category>
      <category>선</category>
      <category>좌표</category>
      <category>픽셀</category>
      <author>J-sean</author>
      <guid isPermaLink="true">https://s-engineer.tistory.com/656</guid>
      <comments>https://s-engineer.tistory.com/656#entry656comment</comments>
      <pubDate>Thu, 19 Mar 2026 00:45:38 +0900</pubDate>
    </item>
    <item>
      <title>[OpenCV] Select Region of Interest ROI 선택</title>
      <link>https://s-engineer.tistory.com/653</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;ROI를 선택해 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1773328892781&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;opencv2/opencv.hpp&amp;gt;

int main()
{
	cv::Mat image = cv::imread(&quot;palvin1.png&quot;);
	cv::Mat roi;	
	
	cv::Rect rect = cv::selectROI(&quot;image&quot;, image);
	std::cout &amp;lt;&amp;lt; &quot;Selected rectangle: &quot; &amp;lt;&amp;lt; rect &amp;lt;&amp;lt; std::endl;
	image = image(rect);

	cv::imshow(&quot;Selected Region&quot;, image);
	cv::waitKey(0);
	cv::destroyAllWindows();

	return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1262&quot; data-origin-height=&quot;1938&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xTuQe/dJMcacoNwTk/KNgLGiZh7ffMAOPrf1mUp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xTuQe/dJMcacoNwTk/KNgLGiZh7ffMAOPrf1mUp0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xTuQe/dJMcacoNwTk/KNgLGiZh7ffMAOPrf1mUp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxTuQe%2FdJMcacoNwTk%2FKNgLGiZh7ffMAOPrf1mUp0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1262&quot; height=&quot;1938&quot; data-origin-width=&quot;1262&quot; data-origin-height=&quot;1938&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1730&quot; data-origin-height=&quot;924&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWNENf/dJMb99S8kMZ/Ip6nLwkfQbaFJ5bJmoL1ck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWNENf/dJMb99S8kMZ/Ip6nLwkfQbaFJ5bJmoL1ck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWNENf/dJMb99S8kMZ/Ip6nLwkfQbaFJ5bJmoL1ck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWNENf%2FdJMb99S8kMZ%2FIp6nLwkfQbaFJ5bJmoL1ck%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1730&quot; height=&quot;924&quot; data-origin-width=&quot;1730&quot; data-origin-height=&quot;924&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;251&quot; data-origin-height=&quot;325&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/B7Rto/dJMcahjm3E6/c7oGNFpWv7BCINtb8xayS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/B7Rto/dJMcahjm3E6/c7oGNFpWv7BCINtb8xayS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/B7Rto/dJMcahjm3E6/c7oGNFpWv7BCINtb8xayS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FB7Rto%2FdJMcahjm3E6%2Fc7oGNFpWv7BCINtb8xayS1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;251&quot; height=&quot;325&quot; data-origin-width=&quot;251&quot; data-origin-height=&quot;325&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번엔 여러 개의 관심 영역을 선택해 보자.&lt;/p&gt;
&lt;pre id=&quot;code_1773327953151&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;opencv2/opencv.hpp&amp;gt;

int main()
{
	cv::Mat image = cv::imread(&quot;palvin1.png&quot;);
	cv::Mat roi;

	std::vector&amp;lt;cv::Rect&amp;gt; rects;
	cv::selectROIs(&quot;image&quot;, image, rects);
	// roi 영역을 선택하면 선택된 roi 영역이 rects 벡터에 저장됨.
	// 하나의 roi 영역을 선택하고 space or enter 키를 누르면 선택이
	// 완료되고 다음 roi 영역을 선택할 수 있음.
	// 선택한 영역을 취소하려면 c 키를 누르면 됨.
	// 모든 선택이 완료되면 esc 키를 눌러 선택을 종료할 수 있음.

	for (const cv::Rect&amp;amp; rect : rects) {
		std::cout &amp;lt;&amp;lt; &quot;Selected rectangle: &quot; &amp;lt;&amp;lt; rect &amp;lt;&amp;lt; std::endl;
		roi = image(rect);
		cv::imshow(&quot;ROI&quot;, roi);
		cv::waitKey(0);
	}

	cv::destroyAllWindows();

	return 0;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1262&quot; data-origin-height=&quot;1938&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CELAK/dJMcafFPzmK/AMjPgH8HZMhnEaU64ukXx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CELAK/dJMcafFPzmK/AMjPgH8HZMhnEaU64ukXx0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CELAK/dJMcafFPzmK/AMjPgH8HZMhnEaU64ukXx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCELAK%2FdJMcafFPzmK%2FAMjPgH8HZMhnEaU64ukXx0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1262&quot; height=&quot;1938&quot; data-origin-width=&quot;1262&quot; data-origin-height=&quot;1938&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1262&quot; data-origin-height=&quot;1938&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/M3Bww/dJMcagEKW54/hWG7HLCCLixkrxB6bs0bCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/M3Bww/dJMcagEKW54/hWG7HLCCLixkrxB6bs0bCk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/M3Bww/dJMcagEKW54/hWG7HLCCLixkrxB6bs0bCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FM3Bww%2FdJMcagEKW54%2FhWG7HLCCLixkrxB6bs0bCk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1262&quot; height=&quot;1938&quot; data-origin-width=&quot;1262&quot; data-origin-height=&quot;1938&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1262&quot; data-origin-height=&quot;1938&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cpFW0I/dJMcabi9dzG/etC1Lri3kaTCoOXCgdk9yk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cpFW0I/dJMcabi9dzG/etC1Lri3kaTCoOXCgdk9yk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cpFW0I/dJMcabi9dzG/etC1Lri3kaTCoOXCgdk9yk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcpFW0I%2FdJMcabi9dzG%2FetC1Lri3kaTCoOXCgdk9yk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1262&quot; height=&quot;1938&quot; data-origin-width=&quot;1262&quot; data-origin-height=&quot;1938&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1730&quot; data-origin-height=&quot;924&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BGjQG/dJMb996GiiW/ywvbi6fKAyXzaizHENE0V0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BGjQG/dJMb996GiiW/ywvbi6fKAyXzaizHENE0V0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BGjQG/dJMb996GiiW/ywvbi6fKAyXzaizHENE0V0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBGjQG%2FdJMb996GiiW%2Fywvbi6fKAyXzaizHENE0V0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1730&quot; height=&quot;924&quot; data-origin-width=&quot;1730&quot; data-origin-height=&quot;924&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;217&quot; data-origin-height=&quot;321&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzuDBm/dJMb996Gii6/ramcah0TgF5rDvflfI41A0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzuDBm/dJMb996Gii6/ramcah0TgF5rDvflfI41A0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzuDBm/dJMb996Gii6/ramcah0TgF5rDvflfI41A0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzuDBm%2FdJMb996Gii6%2Framcah0TgF5rDvflfI41A0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;217&quot; height=&quot;321&quot; data-origin-width=&quot;217&quot; data-origin-height=&quot;321&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;473&quot; data-origin-height=&quot;664&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cyxENI/dJMcab4t8U4/GdLgdxbay1O7IaG7FoIN41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cyxENI/dJMcab4t8U4/GdLgdxbay1O7IaG7FoIN41/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cyxENI/dJMcab4t8U4/GdLgdxbay1O7IaG7FoIN41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcyxENI%2FdJMcab4t8U4%2FGdLgdxbay1O7IaG7FoIN41%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;473&quot; height=&quot;664&quot; data-origin-width=&quot;473&quot; data-origin-height=&quot;664&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;514&quot; data-origin-height=&quot;1062&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cerK2l/dJMcacoNwNT/akSsnjS7D1pH7lgIuPVLf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cerK2l/dJMcacoNwNT/akSsnjS7D1pH7lgIuPVLf0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cerK2l/dJMcacoNwNT/akSsnjS7D1pH7lgIuPVLf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcerK2l%2FdJMcacoNwNT%2FakSsnjS7D1pH7lgIuPVLf0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;514&quot; height=&quot;1062&quot; data-origin-width=&quot;514&quot; data-origin-height=&quot;1062&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1730&quot; data-origin-height=&quot;924&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cFKiDf/dJMcadupwOP/wbAZkrN4Iqu6tCDpdnqbbK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cFKiDf/dJMcadupwOP/wbAZkrN4Iqu6tCDpdnqbbK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cFKiDf/dJMcadupwOP/wbAZkrN4Iqu6tCDpdnqbbK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcFKiDf%2FdJMcadupwOP%2FwbAZkrN4Iqu6tCDpdnqbbK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1730&quot; height=&quot;924&quot; data-origin-width=&quot;1730&quot; data-origin-height=&quot;924&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ 참고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;a style=&quot;color: #006dd7;&quot; href=&quot;https://s-engineer.tistory.com/651&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2026.03.11 - [OpenCV] - [OpenCV] Polygon Mask 폴리곤 마스크 1&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;a style=&quot;color: #006dd7;&quot; href=&quot;https://s-engineer.tistory.com/652&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2026.03.11 - [OpenCV] - [OpenCV] Polygon Mask 폴리곤 마스크 2&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>interest</category>
      <category>opencv</category>
      <category>Region</category>
      <category>ROI</category>
      <category>관심</category>
      <category>영역</category>
      <author>J-sean</author>
      <guid isPermaLink="true">https://s-engineer.tistory.com/653</guid>
      <comments>https://s-engineer.tistory.com/653#entry653comment</comments>
      <pubDate>Fri, 13 Mar 2026 00:18:42 +0900</pubDate>
    </item>
    <item>
      <title>[OpenCV] Polygon Mask 폴리곤 마스크 2</title>
      <link>https://s-engineer.tistory.com/652</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;폴리곤 마스크를 생성하고 사용해 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1773239461700&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;opencv2/opencv.hpp&amp;gt;

void onMouse(int event, int x, int y, int flags, void* userdata)
{
	static std::vector&amp;lt;cv::Point&amp;gt; points; // Static vector to store points across function calls
	cv::Scalar color(0, 255, 0); // Green color for drawing
	cv::Mat* img = (cv::Mat*)userdata; // Cast the user data to a pointer to cv::Mat

	switch (event)
	{
	case cv::EVENT_LBUTTONDOWN:
		points.push_back(cv::Point(x, y));
		std::cout &amp;lt;&amp;lt; &quot;Point added: (&quot; &amp;lt;&amp;lt; x &amp;lt;&amp;lt; &quot;, &quot; &amp;lt;&amp;lt; y &amp;lt;&amp;lt; &quot;)&quot; &amp;lt;&amp;lt; std::endl;

		break;

	case cv::EVENT_RBUTTONDOWN:
		if (!points.empty())
		{
			points.pop_back(); // Remove the last point if right button is clicked
			std::cout &amp;lt;&amp;lt; &quot;Last point removed&quot; &amp;lt;&amp;lt; std::endl;
			*img = cv::imread(&quot;palvin1.png&quot;); // Reload the original image to clear drawings
		}
		break;
	}

	// Draw points
	for (const cv::Point&amp;amp; point : points)
		cv::circle(*(cv::Mat*)userdata, point, 5, color, -1);
	// userdata로도 접근 가능하지만, img 포인터로도 접근 가능

	// Draw lines between points
	if (points.size() &amp;gt;= 2)
		for (int i = 0; i &amp;lt; points.size() - 1; i++)
			cv::line(*img, points[i], points[i + 1], cv::Scalar(0, 0, 255), 2);

	cv::imshow(&quot;image&quot;, *(cv::Mat*)userdata);
}

int main()
{
	cv::Mat image = cv::imread(&quot;palvin1.png&quot;);
	cv::namedWindow(&quot;image&quot;);
	cv::setMouseCallback(&quot;image&quot;, onMouse, (void*)&amp;amp;image);

	cv::imshow(&quot;image&quot;, image);

	cv::waitKey(0);
	cv::destroyAllWindows();

	return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1730&quot; data-origin-height=&quot;924&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dUJlEL/dJMcacCkrOn/a6gaK09ZQ7aEfCD7gM6Ix0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dUJlEL/dJMcacCkrOn/a6gaK09ZQ7aEfCD7gM6Ix0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dUJlEL/dJMcacCkrOn/a6gaK09ZQ7aEfCD7gM6Ix0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdUJlEL%2FdJMcacCkrOn%2Fa6gaK09ZQ7aEfCD7gM6Ix0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1730&quot; height=&quot;924&quot; data-origin-width=&quot;1730&quot; data-origin-height=&quot;924&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1262&quot; data-origin-height=&quot;1938&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ndnHA/dJMb996FpBb/yvY7NYpUYxELaM7d6hMS01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ndnHA/dJMb996FpBb/yvY7NYpUYxELaM7d6hMS01/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ndnHA/dJMb996FpBb/yvY7NYpUYxELaM7d6hMS01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FndnHA%2FdJMb996FpBb%2FyvY7NYpUYxELaM7d6hMS01%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1262&quot; height=&quot;1938&quot; data-origin-width=&quot;1262&quot; data-origin-height=&quot;1938&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1773240538092&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;opencv2/opencv.hpp&amp;gt;

void onMouse(int event, int x, int y, int flags, void* userdata)
{
	static std::vector&amp;lt;std::pair&amp;lt;cv::Point, cv::Scalar&amp;gt;&amp;gt; points; // Store points along with their colors
	cv::Scalar color(0, 255, 0); // Default color is green
	cv::Mat* img = (cv::Mat*)userdata; // Cast the user data to a pointer to cv::Mat

	switch (event)
	{
	case cv::EVENT_LBUTTONDOWN:
		if (flags &amp;amp; cv::EVENT_FLAG_CTRLKEY) // Check if Ctrl key is pressed
			color = cv::Scalar(255, 0, 0); // Blue color if Ctrl is pressed

		points.push_back(std::make_pair(cv::Point(x, y), color));
		std::cout &amp;lt;&amp;lt; &quot;Point added: (&quot; &amp;lt;&amp;lt; x &amp;lt;&amp;lt; &quot;, &quot; &amp;lt;&amp;lt; y &amp;lt;&amp;lt; &quot;) with color &quot;
			&amp;lt;&amp;lt; (color == cv::Scalar(0, 255, 0) ? &quot;Green&quot; : &quot;Blue&quot;) &amp;lt;&amp;lt; std::endl;

		break;

	case cv::EVENT_RBUTTONDOWN:
		if (!points.empty())
		{
			points.pop_back(); // Remove the last point if right button is clicked
			std::cout &amp;lt;&amp;lt; &quot;Last point removed&quot; &amp;lt;&amp;lt; std::endl;
			*img = cv::imread(&quot;palvin1.png&quot;); // Reload the original image to clear drawings
		}
		break;
	}

	// Draw points
	for (const std::pair&amp;lt;cv::Point, cv::Scalar&amp;gt;&amp;amp; point_color_pair : points)
		cv::circle(*(cv::Mat*)userdata, point_color_pair.first, 5, point_color_pair.second, -1);
	// userdata로도 접근 가능하지만, img 포인터로도 접근 가능

	// Draw lines between points
	if (points.size() &amp;gt;= 2)
		for (int i = 0; i &amp;lt; points.size() - 1; i++)
			cv::line(*img, points[i].first, points[i + 1].first, cv::Scalar(0, 0, 255), 2);

	cv::imshow(&quot;image&quot;, *(cv::Mat*)userdata);
}

int main()
{
	cv::Mat image = cv::imread(&quot;palvin1.png&quot;);
	cv::namedWindow(&quot;image&quot;);
	cv::setMouseCallback(&quot;image&quot;, onMouse, (void*)&amp;amp;image);

	cv::imshow(&quot;image&quot;, image);

	cv::waitKey(0);
	cv::destroyAllWindows();

	return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1730&quot; data-origin-height=&quot;924&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Blu6g/dJMcaaxKmuc/twzr1e8A4mf4ukyt4J6jak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Blu6g/dJMcaaxKmuc/twzr1e8A4mf4ukyt4J6jak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Blu6g/dJMcaaxKmuc/twzr1e8A4mf4ukyt4J6jak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBlu6g%2FdJMcaaxKmuc%2Ftwzr1e8A4mf4ukyt4J6jak%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1730&quot; height=&quot;924&quot; data-origin-width=&quot;1730&quot; data-origin-height=&quot;924&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1262&quot; data-origin-height=&quot;1938&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ufk1E/dJMcaio0LyM/bYIBLz5oJ7gPbZSWKzmW71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ufk1E/dJMcaio0LyM/bYIBLz5oJ7gPbZSWKzmW71/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ufk1E/dJMcaio0LyM/bYIBLz5oJ7gPbZSWKzmW71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUfk1E%2FdJMcaio0LyM%2FbYIBLz5oJ7gPbZSWKzmW71%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1262&quot; height=&quot;1938&quot; data-origin-width=&quot;1262&quot; data-origin-height=&quot;1938&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;center&gt;
&lt;script src=&quot;https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js&quot;&gt;&lt;/script&gt;
&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block; text-align: center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;ca-pub-7506419418365672&quot; data-ad-slot=&quot;5217671153&quot;&gt;&lt;/ins&gt;
&lt;script&gt;     (adsbygoogle = window.adsbygoogle || []).push({});&lt;/script&gt;
&lt;/center&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1773277232242&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;opencv2/opencv.hpp&amp;gt;

std::vector&amp;lt;cv::Point&amp;gt; points;

void onMouse(int event, int x, int y, int flags, void* userdata)
{
	cv::Scalar color(0, 255, 0); // Green color for drawing
	cv::Mat* img = (cv::Mat*)userdata; // Cast the user data to a pointer to cv::Mat

	switch (event)
	{
	case cv::EVENT_LBUTTONDOWN:
		points.push_back(cv::Point(x, y));
		std::cout &amp;lt;&amp;lt; &quot;Point added: (&quot; &amp;lt;&amp;lt; x &amp;lt;&amp;lt; &quot;, &quot; &amp;lt;&amp;lt; y &amp;lt;&amp;lt; &quot;)&quot; &amp;lt;&amp;lt; std::endl;

		break;

	case cv::EVENT_RBUTTONDOWN:
		if (!points.empty())
		{
			points.pop_back(); // Remove the last point if right button is clicked
			std::cout &amp;lt;&amp;lt; &quot;Last point removed&quot; &amp;lt;&amp;lt; std::endl;
			*img = cv::imread(&quot;palvin1.png&quot;); // Reload the original image to clear drawings
		}
		break;
	}

	// Draw points
	for (const cv::Point&amp;amp; point : points)
		cv::circle(*(cv::Mat*)userdata, point, 5, color, -1);
	// userdata로도 접근 가능하지만, img 포인터로도 접근 가능

	// Draw lines between points
	if (points.size() &amp;gt;= 2)
		for (int i = 0; i &amp;lt; points.size() - 1; i++)
			cv::line(*img, points[i], points[i + 1], cv::Scalar(0, 0, 255), 2);

	cv::imshow(&quot;image&quot;, *(cv::Mat*)userdata);
}

int main()
{
	cv::Mat image = cv::imread(&quot;palvin1.png&quot;);
	cv::Mat mask = cv::Mat::zeros(image.size(), CV_8UC1);
	cv::Mat result = cv::Mat::zeros(image.size(), image.type());
	cv::Mat roi;

	cv::Rect rect;

	cv::namedWindow(&quot;image&quot;);
	cv::setMouseCallback(&quot;image&quot;, onMouse, (void*)&amp;amp;image);

	cv::imshow(&quot;image&quot;, image);

	while (true)
	{
		int key = cv::waitKey(10);
		if (key == 27) // ESC key
			break;

		switch (key)
		{
			// c키를 누르면 모든 점을 지우고 원본 이미지를 다시 불러와서 초기화
		case 'c':
			points.clear(); // Clear all points if 'c' key is pressed
			image = cv::imread(&quot;palvin1.png&quot;); // Reload the original image to clear drawings

			mask.setTo(0); // Clear the mask as well
			cv::imshow(&quot;image&quot;, image);
			if (cv::getWindowProperty(&quot;mask&quot;, cv::WND_PROP_VISIBLE) == 1) // Check if the mask window is open
				cv::imshow(&quot;mask&quot;, mask); // Clear the mask display as well

			result.setTo(0); // Clear the result image as well
			if (cv::getWindowProperty(&quot;result&quot;, cv::WND_PROP_VISIBLE) == 1) // Check if the result window is open
				cv::imshow(&quot;result&quot;, result); // Clear the result display as well

			roi.setTo(0); // Clear the ROI image as well
			if (cv::getWindowProperty(&quot;ROI&quot;, cv::WND_PROP_VISIBLE) == 1) // Check if the ROI window is open
				cv::imshow(&quot;ROI&quot;, roi); // Clear the ROI display as well

			break;

			// m키를 누르면 points 벡터에 저장된 점들을 이용하여 mask 이미지를 채우고, mask 이미지를 화면에 표시
		case 'm':
			mask.setTo(0); // Clear the mask before filling
			cv::fillPoly(mask, points, cv::Scalar(255)); // Fill the polygon defined by points
			cv::imshow(&quot;mask&quot;, mask);

			break;

			// r키를 누르면 mask 이미지를 이용하여 원본 이미지에서 해당 영역을 추출하여 result 이미지에 저장하고, result 이미지를 화면에 표시
		case 'r':
			image = cv::imread(&quot;palvin1.png&quot;); // Reload the original image to clear drawings
			result.setTo(0); // Clear the result image before copying
			image.copyTo(result, mask); // Copy the masked area to the result image
			//cv::bitwise_and(image, image, result, mask); // Apply the mask to the image
			cv::imshow(&quot;result&quot;, result);

			break;

		case 'i':
			rect = cv::boundingRect(points); // Get the bounding rectangle of the points			
			roi = result(rect);
			cv::imshow(&quot;ROI&quot;, roi);

			break;

		default:
			break;
		}
	}

	cv::destroyAllWindows();

	return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1262&quot; data-origin-height=&quot;1938&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCKjae/dJMcac96TpO/lWLxLC7ek3U6J09MSpkN0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCKjae/dJMcac96TpO/lWLxLC7ek3U6J09MSpkN0k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCKjae/dJMcac96TpO/lWLxLC7ek3U6J09MSpkN0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCKjae%2FdJMcac96TpO%2FlWLxLC7ek3U6J09MSpkN0k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1262&quot; height=&quot;1938&quot; data-origin-width=&quot;1262&quot; data-origin-height=&quot;1938&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1262&quot; data-origin-height=&quot;1938&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bG9U3u/dJMcagrbK0z/PKNeAjN5RAdLZX8rNm3xhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bG9U3u/dJMcagrbK0z/PKNeAjN5RAdLZX8rNm3xhK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bG9U3u/dJMcagrbK0z/PKNeAjN5RAdLZX8rNm3xhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbG9U3u%2FdJMcagrbK0z%2FPKNeAjN5RAdLZX8rNm3xhK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1262&quot; height=&quot;1938&quot; data-origin-width=&quot;1262&quot; data-origin-height=&quot;1938&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1262&quot; data-origin-height=&quot;1938&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mr61t/dJMcaadsw9i/UNnvRRy4tqk4o84TPmugdK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mr61t/dJMcaadsw9i/UNnvRRy4tqk4o84TPmugdK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mr61t/dJMcaadsw9i/UNnvRRy4tqk4o84TPmugdK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fmr61t%2FdJMcaadsw9i%2FUNnvRRy4tqk4o84TPmugdK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1262&quot; height=&quot;1938&quot; data-origin-width=&quot;1262&quot; data-origin-height=&quot;1938&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;643&quot; data-origin-height=&quot;828&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/z4PuO/dJMcagkpIuc/WNKSKAkHN5MP6YAtnvYo91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/z4PuO/dJMcagkpIuc/WNKSKAkHN5MP6YAtnvYo91/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/z4PuO/dJMcagkpIuc/WNKSKAkHN5MP6YAtnvYo91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fz4PuO%2FdJMcagkpIuc%2FWNKSKAkHN5MP6YAtnvYo91%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;643&quot; height=&quot;828&quot; data-origin-width=&quot;643&quot; data-origin-height=&quot;828&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ 참고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;a style=&quot;color: #006dd7;&quot; href=&quot;https://s-engineer.tistory.com/653&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2026.03.13 - [분류 전체보기] - [OpenCV] Select Region of Interest ROI 선택&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;a style=&quot;color: #006dd7;&quot; href=&quot;https://s-engineer.tistory.com/651&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2026.03.11 - [OpenCV] - [OpenCV] Polygon Mask 폴리곤 마스크 1&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Computer Vision</category>
      <category>mask</category>
      <category>polygon</category>
      <category>ROI</category>
      <category>관심</category>
      <category>마스크</category>
      <category>영역</category>
      <category>폴리곤</category>
      <author>J-sean</author>
      <guid isPermaLink="true">https://s-engineer.tistory.com/652</guid>
      <comments>https://s-engineer.tistory.com/652#entry652comment</comments>
      <pubDate>Wed, 11 Mar 2026 23:31:56 +0900</pubDate>
    </item>
  </channel>
</rss>