<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>라즈베리파이 보관 - 하우인포-IT·테크</title>
	<atom:link href="https://howinfo.kr/tag/%eb%9d%bc%ec%a6%88%eb%b2%a0%eb%a6%ac%ed%8c%8c%ec%9d%b4/feed/" rel="self" type="application/rss+xml" />
	<link>https://howinfo.kr/tag/라즈베리파이/</link>
	<description>IT·AI 자동화 &#38; 인프라 전문 블로그 (하우인포)</description>
	<lastBuildDate>Sun, 22 Feb 2026 04:48:04 +0000</lastBuildDate>
	<language>ko-KR</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>

<image>
	<url>https://howinfo.kr/wp-content/uploads/2026/02/cropped-ChatGPT-Image-2026년-2월-12일-오후-05_39_40-32x32.png</url>
	<title>라즈베리파이 보관 - 하우인포-IT·테크</title>
	<link>https://howinfo.kr/tag/라즈베리파이/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>내가 라즈베리파이에서 NAS를 산 이유</title>
		<link>https://howinfo.kr/%eb%82%b4%ea%b0%80-%eb%9d%bc%ec%a6%88%eb%b2%a0%eb%a6%ac%ed%8c%8c%ec%9d%b4%ec%97%90%ec%84%9c-nas%eb%a5%bc-%ec%82%b0-%ec%9d%b4%ec%9c%a0/</link>
					<comments>https://howinfo.kr/%eb%82%b4%ea%b0%80-%eb%9d%bc%ec%a6%88%eb%b2%a0%eb%a6%ac%ed%8c%8c%ec%9d%b4%ec%97%90%ec%84%9c-nas%eb%a5%bc-%ec%82%b0-%ec%9d%b4%ec%9c%a0/#respond</comments>
		
		<dc:creator><![CDATA[hong]]></dc:creator>
		<pubDate>Sun, 22 Feb 2026 04:48:02 +0000</pubDate>
				<category><![CDATA[서버·인프라]]></category>
		<category><![CDATA[Docker실전]]></category>
		<category><![CDATA[NAS블로그운영]]></category>
		<category><![CDATA[개인데이터센터]]></category>
		<category><![CDATA[라즈베리파이]]></category>
		<category><![CDATA[시놀로지NAS]]></category>
		<category><![CDATA[오렌지파이]]></category>
		<category><![CDATA[자동화서버]]></category>
		<category><![CDATA[홈서버운영기]]></category>
		<guid isPermaLink="false">https://howinfo.kr/?p=1914</guid>

					<description><![CDATA[<p>집에서 비용 적게 들이면서 홈서버를 확장한 실제 경험기 1. 시작은 라즈베리파이였다 처음부터 NAS를 산 건 아닙니다. 처음에는 Raspberry Pi 4로...</p>
<p>게시물 <a href="https://howinfo.kr/%eb%82%b4%ea%b0%80-%eb%9d%bc%ec%a6%88%eb%b2%a0%eb%a6%ac%ed%8c%8c%ec%9d%b4%ec%97%90%ec%84%9c-nas%eb%a5%bc-%ec%82%b0-%ec%9d%b4%ec%9c%a0/">내가 라즈베리파이에서 NAS를 산 이유</a>이 <a href="https://howinfo.kr">하우인포-IT·테크</a>에 처음 등장했습니다.</p>
]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">집에서 비용 적게 들이면서 홈서버를 확장한 실제 경험기</h2>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">1. 시작은 라즈베리파이였다</h2>



<p>처음부터 NAS를 산 건 아닙니다.</p>



<p>처음에는 <strong>Raspberry Pi 4</strong>로 시작했습니다.</p>



<p>현재는 먼지 투성이 인 라즈베리파이4 .. 카메라 및 7인치 터치 스크린이 있는 ^^ </p>



<figure class="wp-block-image size-full is-resized"><img fetchpriority="high" decoding="async" width="842" height="437" src="https://howinfo.kr/wp-content/uploads/2026/02/synology2.png" alt="" class="wp-image-1916" style="aspect-ratio:1.9268817204301076;width:569px;height:auto" srcset="https://howinfo.kr/wp-content/uploads/2026/02/synology2.png 842w, https://howinfo.kr/wp-content/uploads/2026/02/synology2-300x156.png 300w, https://howinfo.kr/wp-content/uploads/2026/02/synology2-768x399.png 768w" sizes="(max-width: 842px) 100vw, 842px" /></figure>



<p>이유는 단순했습니다.</p>



<ul class="wp-block-list">
<li>가격이 저렴하다.</li>



<li>리눅스를 직접 다뤄보고 싶었다</li>



<li>서버라는 걸 직접 만들어보고 싶었다</li>



<li>집에서 자유롭게 실험하고 싶었다</li>
</ul>



<p>라즈베리파이는 정말 “입문용 서버”로 최고였습니다.</p>



<p>64기가 메모리 하나면 우분투 및 여러개의 os를 다뤄볼 수 있었고 설치도 인터넷에 많이 있어 어려움이 없었음 </p>



<h3 class="wp-block-heading">라즈베리파이에서 했던 것</h3>



<ul class="wp-block-list">
<li>SSH 원격 접속 연습</li>



<li>간단한 웹서버 구축</li>



<li>Node.js 테스트</li>



<li>Python 자동화 스크립트 실행</li>



<li>GPIO 실험</li>
</ul>



<p>이때 많은것을 알게 되었습니다. 그리고 문제가 생겨도 다시 설치하면 되고 이미지 구운것을 다시 올리면 되어서 부담이 없었음 그래도 많은 데이터가 안정적이지 못해 이런 부문을 어떻게 해결해야 하나 고민도 해봤고 운영에서의 안정성이 보장되어야 겠구나 했습니다.</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>서버는 설치가 아니라 운영이다.</p>
</blockquote>



<p>하지만 곧 한계를 느꼈습니다.</p>



<ul class="wp-block-list">
<li>SD카드 안정성 문제</li>



<li>I/O 속도 병목</li>



<li>메모리 부족</li>



<li>여러 서비스를 동시에 돌리기 어려움</li>
</ul>



<p>데이터를 오래 보관하기엔 불안했습니다. 성능이 좀더 필요한데 .. 음 뭐가 없을까.. </p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">2. 성능이 아쉬워서 오렌지파이로</h2>



<p>그래서 다음 단계로 넘어갔습니다.</p>



<p><strong>Orange Pi 5</strong></p>



<p>현재도 잘 사용하고 있는 오렌지 파이5  오디오 파일을 STT로 돌리려고 했더니 열이 나서 갑옷을 입었음^^</p>



<figure class="wp-block-image size-full is-resized"><img decoding="async" width="681" height="413" src="https://howinfo.kr/wp-content/uploads/2026/02/synology3.png" alt="" class="wp-image-1917" style="width:559px;height:auto" srcset="https://howinfo.kr/wp-content/uploads/2026/02/synology3.png 681w, https://howinfo.kr/wp-content/uploads/2026/02/synology3-300x182.png 300w" sizes="(max-width: 681px) 100vw, 681px" /></figure>



<p>라즈베리파이보다 성능이 훨씬 좋았습니다.</p>



<ul class="wp-block-list">
<li>더 빠른 CPU</li>



<li>NVMe 연결 가능</li>



<li>Ubuntu 환경</li>



<li>Docker 운영 가능</li>



<li>AI 테스트 가능</li>
</ul>



<p>이제는 단순 실습이 아니라<br>조금 더 “실전 테스트 환경”이 되었습니다.</p>



<h3 class="wp-block-heading">오렌지파이에서 했던 것</h3>



<ul class="wp-block-list">
<li>Docker 컨테이너 실험</li>



<li>n8n 테스트</li>



<li>Python 자동화 서버</li>



<li>TTS 음성 시스템</li>



<li>로컬 AI 모델 테스트</li>
</ul>



<p>성능은 만족스러웠습니다. 하지만 AI 성능 등 좀더 성능이 좋은 장비가 필요해 보입니다. ㅜㅜ </p>



<p>하지만 제일 중요한것은 성능도 좋지만 데이터에 대한 고민이 생겼습니다. </p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>“데이터를 장기 보관해도 괜찮을까?”</p>
</blockquote>



<p>SBC는 실험에는 좋지만, -> 실제 오렌지파이 5는 USB C타입으로 평균 5W정도 먹어서 24시간 돌리기에는 너무 좋은 싱글 보드입니다. 짱. <br>그러나 24시간 안정적인 데이터 서버로 쓰기엔 조금 불안했습니다.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">3. 결국 NAS로 넘어오게 된 이유</h2>



<p>그래서 최종적으로 선택한 게 시놀로지 NAS였습니다.</p>



<p>1년정도 사용중인데 너무 마음에 든다 현재는 14테라 2개에 미러링으로 운영중인데 사진 등 많은것을 해주고 있다.</p>



<figure class="wp-block-image size-full is-resized"><img decoding="async" width="448" height="526" src="https://howinfo.kr/wp-content/uploads/2026/02/synology1.png" alt="" class="wp-image-1918" style="width:397px;height:auto" srcset="https://howinfo.kr/wp-content/uploads/2026/02/synology1.png 448w, https://howinfo.kr/wp-content/uploads/2026/02/synology1-256x300.png 256w" sizes="(max-width: 448px) 100vw, 448px" /></figure>



<p>이제 목적이 명확해졌습니다.</p>



<ul class="wp-block-list">
<li>데이터를 안정적으로 보관</li>



<li>장기 서비스 운영</li>



<li>WordPress 상시 운영</li>



<li>자동화 서버 운영</li>



<li>백업 시스템 구축</li>
</ul>



<p>NAS는 확실히 다릅니다.</p>



<ul class="wp-block-list">
<li>RAID 구성 가능</li>



<li>하드디스크 안정성</li>



<li>DSM 기반 관리 UI</li>



<li>Docker 안정 운영</li>



<li>Reverse Proxy + SSL 관리</li>
</ul>



<p>이제는 실험이 아니라<br>“운영”이라는 개념으로 넘어왔습니다.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">4. 현재 운영 구조 (비용 최소화 방향)</h2>



<p>지금은 이렇게 구성했습니다.</p>



<pre class="wp-block-preformatted">[Orange Pi 5]<br>- 네트워크 실험<br>- 센서 실험<br>- 가벼운 테스트<br>- AI 실험<br>- 고부하 테스트<br>- Docker 실험<br>[시놀로지 나스 720+]<br>- WordPress 운영<br>- n8n 자동화<br>- 데이터 저장<br>- 백업<br>- Reverse Proxy</pre>



<p>핵심 전략은:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>실험은 SBC<br>운영은 NAS</p>
</blockquote>



<p>이 구조가 비용 대비 가장 효율적이었습니다.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">5. 비용 관점에서 보면</h2>



<h3 class="wp-block-heading">클라우드와 비교</h3>



<p>클라우드는 초기비용이 없습니다.<br>하지만:</p>



<ul class="wp-block-list">
<li>24시간 서버 비용</li>



<li>트래픽 증가 시 과금</li>



<li>스토리지 비용</li>



<li>백업 비용</li>
</ul>



<p>이게 계속 누적됩니다.</p>



<p>반면 홈서버는:</p>



<ul class="wp-block-list">
<li>초기 장비 비용</li>



<li>월 전기요금 소액</li>



<li>트래픽 제한 없음</li>
</ul>



<p>장기 운영 기준으로 보면<br>홈서버가 생각보다 경제적입니다.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">6. 단계별 깨달음</h2>



<h3 class="wp-block-heading">라즈베리파이 → 서버 개념 이해</h3>



<h3 class="wp-block-heading">오렌지파이 → 성능과 한계 체감</h3>



<h3 class="wp-block-heading">NAS → 안정성과 운영 책임</h3>



<p>이 과정을 거치니:</p>



<ul class="wp-block-list">
<li>Docker 이해도 상승</li>



<li>네트워크 개념 정리</li>



<li>SSL/Reverse Proxy 체득</li>



<li>서버 장애 대응 경험 축적</li>
</ul>



<p>이건 단순 장비 사용이 아니라<br><strong>운영 경험의 축적</strong>이었습니다.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">7. 이런 분들께 추천</h2>



<ul class="wp-block-list">
<li>비용 적게 시작 → 라즈베리파이 or 오렌지파이 </li>



<li>성능 테스트 &amp; AI 실험 → 오렌지파이</li>



<li>데이터 보관 &amp; 블로그 운영 → NAS</li>
</ul>



<p>처음부터 NAS를 사는 것도 좋지만,<br>단계적으로 경험해보는 게 훨씬 도움이 됩니다.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">마무리</h2>



<p>저는 이렇게 확장했습니다.</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>라즈베리파이 → 오렌지파이 → NAS</p>
</blockquote>



<p>점점 더 안정적으로,<br>점점 더 운영 중심으로,<br>점점 더 데이터 안전을 우선으로.</p>



<p>지금은 집 안에 작은 데이터센터를 운영하는 느낌입니다.</p>



<p>다음 글에서는<br>“NAS에서 WordPress 실제 운영하면서 겪은 장애 사례”를 기록해보겠습니다.</p>



<p></p>



<p></p>
<p>게시물 <a href="https://howinfo.kr/%eb%82%b4%ea%b0%80-%eb%9d%bc%ec%a6%88%eb%b2%a0%eb%a6%ac%ed%8c%8c%ec%9d%b4%ec%97%90%ec%84%9c-nas%eb%a5%bc-%ec%82%b0-%ec%9d%b4%ec%9c%a0/">내가 라즈베리파이에서 NAS를 산 이유</a>이 <a href="https://howinfo.kr">하우인포-IT·테크</a>에 처음 등장했습니다.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://howinfo.kr/%eb%82%b4%ea%b0%80-%eb%9d%bc%ec%a6%88%eb%b2%a0%eb%a6%ac%ed%8c%8c%ec%9d%b4%ec%97%90%ec%84%9c-nas%eb%a5%bc-%ec%82%b0-%ec%9d%b4%ec%9c%a0/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>[라즈베리파이] 나만의 스마트 보안 카메라 만들기 (Python + OpenCV + Edge-TTS)</title>
		<link>https://howinfo.kr/%eb%9d%bc%ec%a6%88%eb%b2%a0%eb%a6%ac%ed%8c%8c%ec%9d%b4-%eb%82%98%eb%a7%8c%ec%9d%98-%ec%8a%a4%eb%a7%88%ed%8a%b8-%eb%b3%b4%ec%95%88-%ec%b9%b4%eb%a9%94%eb%9d%bc-%eb%a7%8c%eb%93%a4%ea%b8%b0-python-op/</link>
					<comments>https://howinfo.kr/%eb%9d%bc%ec%a6%88%eb%b2%a0%eb%a6%ac%ed%8c%8c%ec%9d%b4-%eb%82%98%eb%a7%8c%ec%9d%98-%ec%8a%a4%eb%a7%88%ed%8a%b8-%eb%b3%b4%ec%95%88-%ec%b9%b4%eb%a9%94%eb%9d%bc-%eb%a7%8c%eb%93%a4%ea%b8%b0-python-op/#respond</comments>
		
		<dc:creator><![CDATA[hong]]></dc:creator>
		<pubDate>Mon, 09 Feb 2026 06:45:24 +0000</pubDate>
				<category><![CDATA[개발·코딩]]></category>
		<category><![CDATA[AI음성]]></category>
		<category><![CDATA[DIY프로젝트]]></category>
		<category><![CDATA[EdgeTTS]]></category>
		<category><![CDATA[OpenCV]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[RaspberryPi]]></category>
		<category><![CDATA[라즈베리파이]]></category>
		<category><![CDATA[모션감지]]></category>
		<category><![CDATA[보안카메라]]></category>
		<category><![CDATA[스마트홈]]></category>
		<category><![CDATA[알림시스템]]></category>
		<category><![CDATA[임베디드]]></category>
		<category><![CDATA[파이썬]]></category>
		<category><![CDATA[홈네트워크]]></category>
		<category><![CDATA[홈캠만들기]]></category>
		<guid isPermaLink="false">https://howinfo.kr/?p=1462</guid>

					<description><![CDATA[<p>집을 비울 때 누군가 들어오는지 궁금하신가요? 시중의 비싼 홈캠 대신, 라즈베리파이와 파이썬을 활용해 움직임을 감지하고 목소리로 경고를 날리는 스마트 감시...</p>
<p>게시물 <a href="https://howinfo.kr/%eb%9d%bc%ec%a6%88%eb%b2%a0%eb%a6%ac%ed%8c%8c%ec%9d%b4-%eb%82%98%eb%a7%8c%ec%9d%98-%ec%8a%a4%eb%a7%88%ed%8a%b8-%eb%b3%b4%ec%95%88-%ec%b9%b4%eb%a9%94%eb%9d%bc-%eb%a7%8c%eb%93%a4%ea%b8%b0-python-op/">[라즈베리파이] 나만의 스마트 보안 카메라 만들기 (Python + OpenCV + Edge-TTS)</a>이 <a href="https://howinfo.kr">하우인포-IT·테크</a>에 처음 등장했습니다.</p>
]]></description>
										<content:encoded><![CDATA[
<p>집을 비울 때 누군가 들어오는지 궁금하신가요? 시중의 비싼 홈캠 대신, 라즈베리파이와 파이썬을 활용해 <strong>움직임을 감지하고 목소리로 경고를 날리는 스마트 감시 시스템</strong>을 직접 만들어보았습니다. AI를 활용한 고품질 TTS 기능까지 더해 더욱 강력해진 &#8216;모션 가드&#8217; 제작기를 공유합니다.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">1. 주요 기능 및 특징: 이 프로젝트가 특별한 이유</h3>



<p>단순히 녹화만 하는 카메라가 아닙니다. 상황에 맞춰 즉각 대응하는 지능형 시스템입니다.</p>



<ul class="wp-block-list">
<li><strong>실시간 모션 감지:</strong> OpenCV를 활용해 지정된 ROI(관심 영역) 내의 움직임을 픽셀 단위로 분석하여 작은 변화도 놓치지 않습니다.</li>



<li><strong>고품질 AI 음성 안내:</strong> <code>edge-tts</code>를 연동하여 기계음이 아닌 자연스러운 한국어 목소리로 침입 경고 멘트를 송출합니다.</li>



<li><strong>오탐 방지 알고리즘:</strong> 연속 프레임 감지(Confirm Frames)와 쿨다운 타임을 적용해 조명 변화나 미세한 노이즈로 인한 오작동을 최소화했습니다.</li>



<li><strong>강력한 비프음 발생:</strong> 경고 멘트 후 강렬한 &#8216;삐삐삐&#8217; 패턴의 비프음을 재생해 청각적인 보안 효과를 극대화합니다.</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">2. 준비물 및 환경 설정: 시작하기 전에</h3>



<p>이 프로젝트를 실행하기 위해 라즈베리파이에 몇 가지 하드웨어와 라이브러리 설치가 필요합니다.</p>



<ul class="wp-block-list">
<li><strong>하드웨어:</strong>
<ul class="wp-block-list">
<li>Raspberry Pi (Zero W, 3, 4 등 모든 모델 가능)</li>



<li>USB 웹캠 또는 라즈베리파이 카메라 모듈</li>



<li>스피커 (3.5mm 오디오 잭 또는 USB 스피커)</li>
</ul>
</li>



<li><strong>소프트웨어 설치:</strong>Bash<code># 1. 시스템 의존성 설치 (음성 재생을 위한 mpg123, alsa-utils) sudo apt-get update &amp;&amp; sudo apt-get install -y mpg123 alsa-utils # 2. 파이썬 라이브러리 설치 (OpenCV, NumPy, Edge-TTS, Asyncio) pip install opencv-python numpy edge-tts asyncio </code><strong>💡 Tip:</strong> <code>pip</code> 명령어가 오류난다면 <code>pip3 install ...</code>을 시도해 보세요.</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">3. 핵심 코드 분석: 어떻게 움직임을 감지할까?</h3>



<p>코드의 핵심은 <strong>이전 프레임과 현재 프레임 간의 픽셀 차이를 계산하여 움직임을 수치화</strong>하는 것입니다.</p>



<h4 class="wp-block-heading">🔍 모션 감지 알고리즘 (<code>motion_ratio</code> 함수)</h4>



<p>Python</p>



<pre class="wp-block-code"><code>import cv2
import numpy as np
import os
import asyncio
import edge_tts
import time

# --- 환경 변수 설정 (값을 변경하여 감지 민감도를 조절할 수 있습니다) ---
MOTION_RATIO_THRESH = float(os.environ.get("MOTION_RATIO_THRESH", "0.03")) # 움직임 감지 임계값 (0.01~0.1 사이 권장)
CONFIRM_FRAMES = int(os.environ.get("CONFIRM_FRAMES", "5"))                 # 연속 감지 확인 프레임 수
ALERT_COOLDOWN_SEC = int(os.environ.get("ALERT_COOLDOWN_SEC", "30"))        # 경고 후 쿨다운 시간(초)
TTS_MP3_PATH = os.environ.get("TTS_MP3_PATH", "/tmp/alert.mp3")            # TTS 음성 파일 저장 경로
ALERT_MESSAGE = os.environ.get("ALERT_MESSAGE", "경고! 움직임이 감지되었습니다.") # 경고 음성 메시지
ROI_X, ROI_Y, ROI_W, ROI_H = map(int, os.environ.get("ROI", "0,0,0,0").split(',')) # 관심 영역 (x,y,width,height)

async def tts_save_mp3(text, mp3_path, voice="ko-KR-SunHiNeural"):
    """
    Edge-TTS를 사용하여 텍스트를 mp3 파일로 변환하여 저장합니다.
    """
    try:
        communicate = edge_tts.Communicate(text=text, voice=voice)
        await communicate.save(mp3_path)
    except Exception as e:
        print(f"TTS 생성 중 오류 발생: {e}")

def motion_ratio(prev_gray, gray):
    """
    두 회색조 이미지 간의 움직임 비율을 계산합니다.
    """
    diff = cv2.absdiff(prev_gray, gray) # 이전 프레임과 현재 프레임의 픽셀 차이 계산
    _, th = cv2.threshold(diff, 25, 255, cv2.THRESH_BINARY) # 임계값 처리 (차이가 25 이상인 픽셀만 흰색으로)
    th = cv2.medianBlur(th, 5) # 노이즈 제거를 위한 미디언 블러 적용
    changed_pixels = np.count_nonzero(th) # 변경된 픽셀 수 계산
    return changed_pixels / th.size # 전체 픽셀 대비 변경된 픽셀 비율 반환

def play_alert_sound(tts_path, beep_count=3, beep_duration=0.2):
    """
    경고 음성 메시지와 비프음을 재생합니다.
    """
    print("경고음 재생...")
    if os.path.exists(tts_path):
        os.system(f"mpg123 {tts_path}") # TTS 음성 재생
    
    # 비프음 재생
    for _ in range(beep_count):
        os.system(f"aplay -q -c 1 -t raw -f S16_LE -r 44100 /dev/zero") # 기본 비프음 (라즈비안에서 작동 확인)
        time.sleep(beep_duration)
        os.system(f"aplay -q -c 1 -t raw -f S16_LE -r 44100 /dev/zero") # 종료 비프음
        time.sleep(beep_duration)
    print("경고음 재생 완료.")

async def main():
    cap = cv2.VideoCapture(0) # 웹캠 (0번 장치) 초기화
    if not cap.isOpened():
        print("카메라를 열 수 없습니다.")
        return

    ret, frame = cap.read()
    if not ret:
        print("첫 프레임을 읽을 수 없습니다.")
        cap.release()
        return

    # ROI 설정이 유효하면 해당 영역으로 프레임을 자름
    if ROI_W &gt; 0 and ROI_H &gt; 0:
        frame = frame&#91;ROI_Y:ROI_Y+ROI_H, ROI_X:ROI_X+ROI_W]
        
    prev_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    
    motion_detected_count = 0
    last_alert_time = 0

    # TTS 파일 미리 생성
    await tts_save_mp3(ALERT_MESSAGE, TTS_MP3_PATH)

    print(f"모션 감지 시작. 임계값: {MOTION_RATIO_THRESH}, 확인 프레임: {CONFIRM_FRAMES}, 쿨다운: {ALERT_COOLDOWN_SEC}초")
    print(f"관심 영역(ROI): X={ROI_X}, Y={ROI_Y}, W={ROI_W}, H={ROI_H}")

    try:
        while True:
            ret, frame = cap.read()
            if not ret:
                break

            display_frame = frame.copy() # 화면 표시용 원본 프레임 복사

            # ROI 설정이 유효하면 해당 영역으로 프레임을 자르고 ROI 표시
            if ROI_W &gt; 0 and ROI_H &gt; 0:
                frame_for_detection = frame&#91;ROI_Y:ROI_Y+ROI_H, ROI_X:ROI_X+ROI_W]
                cv2.rectangle(display_frame, (ROI_X, ROI_Y), (ROI_X+ROI_W, ROI_Y+ROI_H), (0, 255, 0), 2) # ROI 박스 그리기
            else:
                frame_for_detection = frame

            gray = cv2.cvtColor(frame_for_detection, cv2.COLOR_BGR2GRAY)
            
            ratio = motion_ratio(prev_gray, gray)
            
            current_time = time.time()

            if ratio &gt; MOTION_RATIO_THRESH:
                motion_detected_count += 1
                if motion_detected_count &gt;= CONFIRM_FRAMES and (current_time - last_alert_time) &gt; ALERT_COOLDOWN_SEC:
                    print(f"!!! 움직임 감지됨 (비율: {ratio:.4f}) !!!")
                    play_alert_sound(TTS_MP3_PATH)
                    last_alert_time = current_time
                    motion_detected_count = 0 # 알림 후 카운트 초기화
            else:
                motion_detected_count = 0 # 움직임이 없으면 카운트 초기화

            # 프레임에 감지 정보 표시 (선택 사항, 라즈베리파이 성능 고려하여 주석 처리 가능)
            # cv2.putText(display_frame, f"Motion Ratio: {ratio:.4f}", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
            # cv2.putText(display_frame, f"Alerts: {current_time - last_alert_time:.0f}s cooldown", (10, 70), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)

            # cv2.imshow('Motion Guard Cam', display_frame) # 화면에 표시 (GUI 환경에서만 작동)
            
            prev_gray = gray # 현재 프레임을 다음 반복의 이전 프레임으로 저장

            if cv2.waitKey(1) &amp; 0xFF == ord('q'):
                break

    finally:
        cap.release()
        cv2.destroyAllWindows()
        print("프로그램 종료.")

if __name__ == '__main__':
    asyncio.run(main())

</code></pre>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><strong>코드 설명:</strong> 단순 픽셀 차이 외에도 <code>cv2.medianBlur</code>를 적용해 미세한 노이즈를 제거하여 오작동을 줄였습니다. 또한 <code>CONFIRM_FRAMES</code>로 여러 프레임에 걸쳐 움직임이 지속될 때만 감지하도록 설정하여 신뢰도를 높였습니다.</p>
</blockquote>



<h4 class="wp-block-heading">🗣 AI 음성 경고 (<code>edge_tts</code> 활용)</h4>



<p>구글 TTS(gTTS)보다 훨씬 자연스러운 Microsoft Edge의 TTS 엔진을 활용하여 고품질의 한국어 음성 경고를 구현했습니다. <code>asyncio</code>를 통해 비동기적으로 음성을 생성합니다.</p>



<p>Python</p>



<pre class="wp-block-code"><code>async def tts_save_mp3(text, mp3_path, voice="ko-KR-SunHiNeural"):
    """
    Edge-TTS를 사용하여 텍스트를 mp3 파일로 변환하여 저장합니다.
    """
    try:
        communicate = edge_tts.Communicate(text=text, voice=voice)
        await communicate.save(mp3_path)
    except Exception as e:
        print(f"TTS 생성 중 오류 발생: {e}")
</code></pre>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><strong>코드 설명:</strong> <code>ko-KR-SunHiNeural</code>은 한국어 여성 목소리입니다. 이 외에도 다양한 목소리가 있으니 <code>edge-tts --list-voices</code> 명령어로 확인 후 변경해 볼 수 있습니다.</p>
</blockquote>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">4. 실제 구동 팁: 나만의 환경에 최적화하기</h3>



<p>환경에 따라 설정을 미세하게 조정하면 훨씬 똑똑해집니다. 코드 상단의 <strong>환경 변수</strong>를 통해 조절할 수 있습니다.</p>



<ul class="wp-block-list">
<li><strong>민감도 조절 (<code>MOTION_RATIO_THRESH</code>):</strong> 기본값 <code>0.03</code>은 실내에서 적합합니다. 바람에 흔들리는 나뭇잎이 보이거나 외부 환경이라면 <code>0.05</code> ~ <code>0.1</code> 정도로 높여 오탐을 줄일 수 있습니다.</li>



<li><strong>관심 영역 지정 (<code>ROI_X, ROI_Y, ROI_W, ROI_H</code>):</strong>
<ul class="wp-block-list">
<li><code>ROI="0,0,0,0"</code> (기본값) : 전체 화면을 감지합니다.</li>



<li><code>ROI="100,50,400,300"</code> : X좌표 100, Y좌표 50에서 시작하여 가로 400, 세로 300 픽셀 영역만 감지합니다. 이 기능은 문이나 창문 쪽만 집중적으로 감시하게 설정할 수 있어 효율적입니다.</li>
</ul>
</li>



<li><strong>쿨다운 시간 (<code>ALERT_COOLDOWN_SEC</code>):</strong> 한 번 알림이 울린 후 지정된 시간 동안은 재알림을 하지 않습니다. 반복적인 알림으로 인한 소음 공해를 방지해 줍니다.</li>



<li><strong>연속 감지 프레임 (<code>CONFIRM_FRAMES</code>):</strong> 짧은 순간의 노이즈로 인한 오탐을 줄이기 위해, 움직임이 N 프레임 이상 연속될 때만 실제 움직임으로 간주합니다.</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">5. 마치며: 나만의 스마트 홈 시큐리티</h3>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><strong>💡 직접 구현해보니:</strong> 처음에는 <code>cv2.medianBlur</code>나 <code>CONFIRM_FRAMES</code>를 적용하지 않아 바람에 흔들리는 커튼, 혹은 갑작스러운 조명 변화 때문에 알람이 계속 울려 고생했습니다. 하지만 이러한 &#8216;오탐 방지&#8217; 로직을 추가하니 시스템의 신뢰도가 비약적으로 향상되었습니다. 여러분도 환경에 맞춰 임계값이나 ROI를 조금씩 바꿔보며 최적의 보안 환경을 구축해 보세요!</p>
</blockquote>



<p>이 프로젝트는 간단하지만 매우 실용적인 라즈베리파이 활용 예시입니다. 여기에서 더 나아가 감지된 영상을 텔레그램으로 전송하거나, 특정 시간대에만 작동하도록 스케줄링하는 등 다양한 기능으로 확장할 수 있습니다.</p>



<p></p>



<p>전체소스 참고</p>



<p><strong>&#8220;이 코드는 별도의 유료 API 키 없이도 작동하며, 환경 변수만으로 간편하게 설정할 수 있도록 설계했습니다.&#8221;</strong></p>



<pre class="wp-block-code"><code>#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import os
import time
import asyncio
import tempfile
import subprocess

import cv2
import numpy as np
import edge_tts

# =========================
# 설정(튜닝 포인트)
# =========================
CAM_INDEX = int(os.environ.get("CAM_INDEX", "0"))     # /dev/video0
FRAME_W   = int(os.environ.get("FRAME_W", "640"))
FRAME_H   = int(os.environ.get("FRAME_H", "480"))

# 모션 감지 ROI (전체 화면이면 기본값 그대로 두세요)
# x,y,w,h
ROI = (
    int(os.environ.get("ROI_X", "0")),
    int(os.environ.get("ROI_Y", "0")),
    int(os.environ.get("ROI_W", str(FRAME_W))),
    int(os.environ.get("ROI_H", str(FRAME_H))),
)

# 모션 민감도: "변화한 픽셀 비율"
# - 너무 잘 울리면 ↑ (0.08~0.20)
# - 잘 안 울리면 ↓ (0.01~0.06)
MOTION_RATIO_THRESH = float(os.environ.get("MOTION_RATIO_THRESH", "0.03"))

# 픽셀 차이 임계치(조명 변화에 민감하면 ↑)
DIFF_THRESH = int(os.environ.get("DIFF_THRESH", "25"))

# 모션을 몇 프레임 연속 감지해야 트리거할지(오탐 줄임)
MOTION_CONFIRM_FRAMES = int(os.environ.get("MOTION_CONFIRM_FRAMES", "3"))

# 경고 후 쿨다운(연속 재생 방지)
ALERT_COOLDOWN_SEC = float(os.environ.get("ALERT_COOLDOWN_SEC", "15.0"))

# 프레임 처리 간격(부하 조절)
SLEEP_SEC = float(os.environ.get("SLEEP_SEC", "0.01"))

# TTS
TTS_VOICE  = os.environ.get("TTS_VOICE", "ko-KR-SunHiNeural")
TTS_RATE   = os.environ.get("TTS_RATE", "+0%")
TTS_VOLUME = os.environ.get("TTS_VOLUME", "+0%")

ALERT_TEXT = "여기 들어오시면 안됩니다. 허가된 주인님만 입장 가능합니다."

# 비프 패턴: "3초짜리 삐삐삐"를 3번 반복
BEEP_FREQ = int(os.environ.get("BEEP_FREQ", "1100"))     # Hz
BEEP_MS   = int(os.environ.get("BEEP_MS", "180"))        # beep 1회 길이
BEEP_GAP_MS = int(os.environ.get("BEEP_GAP_MS", "120"))  # beep 사이 간격
BEEP_CYCLE_SEC = float(os.environ.get("BEEP_CYCLE_SEC", "3.0"))  # 3초
BEEP_REPEAT = int(os.environ.get("BEEP_REPEAT", "3"))    # 3회 반복

# =========================
# 오디오 유틸
# =========================
def require_bins():
    for binname, pkg in &#91;("mpg123", "mpg123"), ("aplay", "alsa-utils")]:
        try:
            subprocess.run(&#91;binname, "--version"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=False)
        except FileNotFoundError:
            raise RuntimeError(f"{binname}가 없습니다. 설치: sudo apt-get install -y {pkg}")

def play_mp3(path: str):
    subprocess.run(&#91;"mpg123", "-q", path], check=False)

async def tts_save_mp3(text: str, mp3_path: str):
    communicate = edge_tts.Communicate(text=text, voice=TTS_VOICE, rate=TTS_RATE, volume=TTS_VOLUME)
    await communicate.save(mp3_path)

def speak(text: str):
    text = " ".join(text.split()).strip()
    if not text:
        return
    with tempfile.NamedTemporaryFile(suffix=".mp3", delete=True) as tf:
        asyncio.run(tts_save_mp3(text, tf.name))
        play_mp3(tf.name)

def gen_beep_wav(path: str, freq_hz: int, ms: int, volume: float = 0.4, sr: int = 16000):
    t = np.linspace(0, ms/1000.0, int(sr*ms/1000.0), endpoint=False)
    wave = (np.sin(2*np.pi*freq_hz*t) * volume).astype(np.float32)
    pcm_i16 = (np.clip(wave, -1.0, 1.0) * 32767).astype(np.int16)

    import wave as _wave
    with _wave.open(path, "wb") as wf:
        wf.setnchannels(1)
        wf.setsampwidth(2)
        wf.setframerate(sr)
        wf.writeframes(pcm_i16.tobytes())

def play_wav(path: str):
    subprocess.run(&#91;"aplay", "-q", path], check=False)

def beep_3sec_triple_repeat3():
    """
    3초 사이클 안에 '삐삐삐'(3회) 후 남는 시간 쉬기.
    그 3초 사이클을 3번 반복.
    """
    with tempfile.NamedTemporaryFile(suffix=".wav", delete=True) as tf:
        gen_beep_wav(tf.name, BEEP_FREQ, BEEP_MS)

        for _ in range(BEEP_REPEAT):
            start = time.time()

            # 삐삐삐
            for i in range(3):
                play_wav(tf.name)
                if i &lt; 2:
                    time.sleep(BEEP_GAP_MS / 1000.0)

            # 3초 맞추기
            elapsed = time.time() - start
            remain = max(0.0, BEEP_CYCLE_SEC - elapsed)
            time.sleep(remain)

# =========================
# 모션 감지 유틸
# =========================
def motion_ratio(prev_gray: np.ndarray, gray: np.ndarray) -&gt; float:
    diff = cv2.absdiff(prev_gray, gray)
    _, th = cv2.threshold(diff, DIFF_THRESH, 255, cv2.THRESH_BINARY)

    # 작은 노이즈 제거(조금만)
    th = cv2.medianBlur(th, 5)

    changed = np.count_nonzero(th)
    total = th.size
    return changed / max(1, total)

# =========================
# main
# =========================
def main():
    require_bins()

    cap = cv2.VideoCapture(CAM_INDEX)
    if not cap.isOpened():
        raise RuntimeError(f"카메라 열기 실패: CAM_INDEX={CAM_INDEX} (/dev/video{CAM_INDEX})")

    cap.set(cv2.CAP_PROP_FRAME_WIDTH, FRAME_W)
    cap.set(cv2.CAP_PROP_FRAME_HEIGHT, FRAME_H)

    x, y, w, h = ROI

    print("=== Motion Guard (Camera) ===")
    print("CAM_INDEX:", CAM_INDEX)
    print("FRAME:", FRAME_W, "x", FRAME_H)
    print("ROI:", ROI)
    print("MOTION_RATIO_THRESH:", MOTION_RATIO_THRESH, "DIFF_THRESH:", DIFF_THRESH)
    print("CONFIRM_FRAMES:", MOTION_CONFIRM_FRAMES, "COOLDOWN:", ALERT_COOLDOWN_SEC)
    print("종료: Ctrl+C\n")

    prev_gray = None
    motion_hits = 0
    last_alert = 0.0

    try:
        while True:
            ok, frame = cap.read()
            if not ok or frame is None:
                time.sleep(0.05)
                continue

            roi = frame&#91;y:y+h, x:x+w]
            gray = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY)

            # 조명변화 완화(살짝 블러)
            gray = cv2.GaussianBlur(gray, (5, 5), 0)

            if prev_gray is None:
                prev_gray = gray
                time.sleep(SLEEP_SEC)
                continue

            ratio = motion_ratio(prev_gray, gray)
            prev_gray = gray

            if ratio &gt;= MOTION_RATIO_THRESH:
                motion_hits += 1
            else:
                motion_hits = max(0, motion_hits - 1)

            now = time.time()

            # 트리거 조건: 모션 연속 감지 + 쿨다운 지난 후
            if motion_hits &gt;= MOTION_CONFIRM_FRAMES and (now - last_alert) &gt; ALERT_COOLDOWN_SEC:
                last_alert = now
                motion_hits = 0

                print(f"&#91;ALERT] motion detected! ratio={ratio:.4f}")

                # 1) 경고 멘트
                speak(ALERT_TEXT)

                # 2) 삐삐삐(3초) x 3회
                beep_3sec_triple_repeat3()

            time.sleep(SLEEP_SEC)

    except KeyboardInterrupt:
        print("\n종료합니다.")
    finally:
        cap.release()

if __name__ == "__main__":
    main()

</code></pre>
<p>게시물 <a href="https://howinfo.kr/%eb%9d%bc%ec%a6%88%eb%b2%a0%eb%a6%ac%ed%8c%8c%ec%9d%b4-%eb%82%98%eb%a7%8c%ec%9d%98-%ec%8a%a4%eb%a7%88%ed%8a%b8-%eb%b3%b4%ec%95%88-%ec%b9%b4%eb%a9%94%eb%9d%bc-%eb%a7%8c%eb%93%a4%ea%b8%b0-python-op/">[라즈베리파이] 나만의 스마트 보안 카메라 만들기 (Python + OpenCV + Edge-TTS)</a>이 <a href="https://howinfo.kr">하우인포-IT·테크</a>에 처음 등장했습니다.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://howinfo.kr/%eb%9d%bc%ec%a6%88%eb%b2%a0%eb%a6%ac%ed%8c%8c%ec%9d%b4-%eb%82%98%eb%a7%8c%ec%9d%98-%ec%8a%a4%eb%a7%88%ed%8a%b8-%eb%b3%b4%ec%95%88-%ec%b9%b4%eb%a9%94%eb%9d%bc-%eb%a7%8c%eb%93%a4%ea%b8%b0-python-op/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
