Source: https://oliviercouthaud.com/house23d · Status: In progress · 2026 · Tags: COLMAP, Blender, Depth Anything 3, ARCore, 3D printing # House23D Phone video and photos of my room in, a measured Blender model and a printable 1:100 version out. [Image: Isometric render of a modelled bedroom with bed, wardrobe, desk and 3D printer] Figure: My bedroom as a Blender model, clutter hidden, night lighting. Each piece of furniture is its own object. Some surfaces use textures cut from my photos; the floor is generated. ## What I wanted This is my own home, and the bedroom I live in is where I started. I wanted it as a clean model: real dimensions, every object separate so it can be moved or hidden, and something I could print as a dollhouse. A fused scan mesh gives you none of that. In this room the dense point cloud also has almost no points on the plain walls (figure below), so I use the scan as a measuring instrument and rebuild the room in Blender. [Image: Top-down density plot of the dense point cloud between 1 and 2 metres height, with a dashed rectangle for the tape-measured room. Dense points sit along the left side, where the armoire and clothes rack stand, and in two blocks on the right, the ladder desk below and a bookshelf in the wall niche above; the other walls are almost empty.] Figure: The dense cloud from above, only points between 1.0 and 2.0 m high, 2 cm cells. Dashed: the tape-measured 4.21 by 3.50 m room. Points exist where there is furniture: the armoire and clothes rack on the west wall (left), and the ladder desk and a bookshelf in the wall niche (right). The plain walls are nearly empty, which is why I don't mesh this cloud. **Why are plain walls empty?** Matching needs something to match. A flat painted wall looks the same everywhere, so the software can't tell which patch in one photo is the same patch in another, and it throws those points away. Furniture, edges and anything with texture survive, which is why the cloud is dense on the wardrobe and in the niche and thin on the walls. ## How it works I walked through the room with my phone filming and took 16 photos. The video became about 730 frames at 6 fps, and I kept the sharper frame of each pair, 366 frames. COLMAP 4.2.1 on the GPU registered 374 of the 382 images, with 64,606 sparse points, then fused a dense cloud of about 1.9 million points. **What is COLMAP doing?** COLMAP is free structure-from-motion software. It finds the same distinctive spots in many photos, and from how those spots shift between views it solves for where each camera stood and where each spot sits in 3D. That first result is the sparse cloud. A second stage estimates depth for nearly every pixel and merges it all into the dense cloud. It can never know real-world size, because a room twice as big seen from twice as far looks identical, which is why I need a scale step. ### The pipeline, step by step (tap or click a box) Figure: Diagram. Boxes with a file name are scripts in the pipeline; the others are stages. The scale comes from my tape measure, which matters for the accuracy numbers further down. COLMAP's scale is arbitrary. align_room.py levels the cloud from the large horizontal planes and finds the yaw by making wall and furniture edges line up with the axes. fit_dense.py then refines tilt and yaw on the dense cloud, reads the wall positions off it, and sets the scale from my tape-measured room size, 4.21 by 3.50 m. Dimensions the cloud can't give me I measure on the photos: triangulating one point across several views, building a 1 mm per pixel orthophoto of a plane from one photo's exact pose, or back-projecting pixels onto a known plane. Each number goes into layout.py or a place_*.py file, marked as tape or scan. **What is an orthophoto here?** An ordinary photo has perspective: things further away look smaller. If I know the exact camera pose and that a surface is flat, like a door or a desk top, I can project every pixel onto that plane. The result is a flat picture with no perspective where one pixel is 1 mm, so I can read distances straight off it. The Blender side is a Python library, h3d, with 23 item modules (wardrobe, bed, desk, folding chair, fan, 3D printer and so on). Each takes parameters and returns one root object standing on the floor, so I can place it and check it alone. I note where each number came from, and the picker below shows that for a few of them. **Six items from the library: size, and how I got it.** | Item | Size | Where the size came from | |---|---|---| | Armoire | 1.585 × 0.53 × 2.12 m | The doors and cornice from the scan. The 0.45 m body depth is my tape, because I can't see the back. | | Pine desk | 1.20 × 0.75 × 0.77 m | Width and depth from my tape, the top height from the scan. | | Ladder desk | 0.82 × 0.59 × 1.01 m | From the scan and orthophotos of two photos. It is the black metal one with the slanted legs. | | Pedestal fan | grille 0.40 m, head 0.73 m up | Back-projected from one photo. | | 3D printer | 0.415 × 0.444 × 0.555 m | The maker's published outer size. | | Wake-up light | 0.18 × 0.18 × 0.115 m | Its published size. | ## What came out **Counts and sizes for the bedroom. Print time is the slicer's estimate.** | Item | Value | |---|---| | Images registered | 374 of 382 | | Made up of | 366 frames, 16 photos | | Sparse points | 64,606 | | Dense points | about 1.9 million | | Room, by tape | 4.21 × 3.50 m | | Room piece, 1:100 | 48 × 39 × 34 mm | | Filament, room piece | 16.3 g | | Print time | about 59 min | | Layer height | 0.15 mm | To check the model I render it from the 16 solved photo cameras and lay each render over its photo, with the render's edges in red. The errors are not measured systematically. Large objects line up well and small clutter is approximate. Drag to compare the wardrobe door. [Image: The model of the wardrobe door rendered from the photo's camera position] [Image: Photo of the wardrobe door with its brass lock and a charging cable hanging beside it] PhotoModel The finished room is a Blender file that I can fly around in with Blender's walk mode, and it exports to glTF with and without the clutter. ## The printable version printkit.py turns a room into a 1:100 dollhouse piece. Anything that would be thinner than 1.2 mm once printed is thickened, the furniture is simplified and fused into the room piece, and the roof is a separate lid. Neighbouring rooms are meant to join with bow-tie keys dropped into sockets on a 1 m grid, and stacked floors with 1.75 mm filament pins. The room prints upright, PrusaSlicer 2.9.6 adds no supports, and it puts the room piece at about an hour. I printed the 1:100 bedroom and the kitchen, and they look really cool. I also exported a 1:20 version where each piece of furniture is its own STL; its shell is 240 × 197 × 161 mm. [Image: Exploded view of the printable room and its roof] Figure: Room shell and its roof lid, with the key sockets cut into the underside. [Image: Printable bedroom seen from above with simplified furniture blocks inside] Figure: The same piece with the simplified furniture. The renders above and the underside below are not photos. The photo is the printed bedroom with its simplified furniture blocks inside; I only photographed the room, not the kitchen. [Image: Blue 3D-printed model of a room on a wooden table, open at the top, with simple blocks for the furniture inside and a white cable lying in front of it] Figure: The printed model of my room, shown without its roof lid. The furniture is simplified into blocks. A model-size print, one hundredth of the real room; the photo is a bit out of focus and cropped. [Image: The underside of the printable room, with notched key sockets along the edges and round holes for filament pins] Figure: The underside: key sockets along the edge and the round holes for the filament pins that stack floors. The blue piece at the edge is one of the keys. ## What I ran into The dense cloud is empty where the walls are plain, so the walls, doors and the wall niche come from photo measurements instead. I also tried a median mosaic of the floor from all the views. It came out smeared by furniture and cables, so the floor in the model is generated from the measured 60 by 60 cm tile layout. [Image: A smeared brown and grey top-down mosaic of the floor, with furniture and cables blurred across it] Figure: The floor mosaic I tried: every view paints over the floor with whatever stood in front of it. The tape and the scan did not always agree. The scan put the bed about 7 cm further west than my tape numbers did, and I used the scan. The two wall-to-wall distances in the scan scale to my tape within 0.07% on both axes. Since the tape set the overall scale, that only checks the proportions of the room, not its absolute size. The scan does not capture the ceiling light, so it sits where I estimated it, and most wall thicknesses are estimates. The east wall is 0.45 m in the room model and 0.465 m in the print layout, and the two are not reconciled yet. The second stage is the other rooms in the house. On a night walkthrough (24 October 2026) I ran Depth Anything 3 on chunks of frames, and used its monocular metric head to get real sizes. **What is Depth Anything 3?** A neural network that estimates depth from images. In its multi-view mode it gives a consistent shape for a chunk of frames, but not a real-world size. A separate metric head guesses depth in metres. I used that to get sizes, then checked it against my tape where I could. ### Metric scale from the depth model, room by room (tap or click a row) Figure: Diagram. One run per room, 80 sampled frames each. Dot: median. Line: 10th to 90th percentile. Room names are my own labels. Only one of the rooms could be checked against a tape. There the depth model's metric scale was 2.04 against 2.20 fitted to my tape, so it read about 7% small and I corrected by that factor. That is a single room, so it is a thin basis: the scale ranged from 1.74 to 2.50 across the rooms I ran, and inside a single room the frames spread by more than the 7% I corrected, so the correction may not carry over to other rooms. Chunks agree inside a room. The links between corridor and room did not pass the per-frame pose check (1 to 4 of 15 to 30 shared frames agreed), so the rooms stay unlinked for now and the multi-room result is not usable yet. ## Where it stands Only the bedroom is a complete, checked model. The other rooms are rougher: layouts taken from the depth model and printable shells, not models I have checked. I printed the kitchen at 1:100 as well as the bedroom and I like how both turned out, but only the bedroom has been checked against the photos. To fix the linking problem at the source I built an Android app, scanner-app, in Kotlin on ARCore. It saves a keyframe with a metric pose whenever the phone has moved more than 10 cm or turned more than 8 degrees and isn't rotating faster than 60 degrees per second, and it shows a live top-down map of what is still unscanned. It builds, and the map logic has a unit test. I have not done a real walkthrough scan with it yet, because I'm still improving it first, so whether it fixes the joining is the next thing to find out. How the 1:100 joints between rooms behave is open until more rooms are finished. - **Stack**: COLMAP, Python (NumPy, OpenCV, Open3D), Blender 5.2 with Python, Depth Anything 3, Kotlin and ARCore, PrusaSlicer - **Reconstruction**: 366 video frames and 16 photos, 374 registered - **Printable room**: 48 × 39 × 34 mm (model size) - **Scope**: One room finished and checked; bedroom and kitchen printed at 1:100; joining rooms is the next step - **Status**: In progress - **Credits**: Built by me. COLMAP, Blender, Depth Anything 3, ARCore and PrusaSlicer are other people's tools. - **Code**: Not public